diff --git a/dist/index.html b/dist/index.html
index 02f85042..820cd730 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -48,6 +48,8 @@
Maps JSAPI Samples
advanced-markers-html-simple
advanced-markers-simple
advanced-markers-zoom
+ ai-powered-summaries
+ ai-powered-summaries-basic
boundaries-choropleth
boundaries-click
boundaries-simple
diff --git a/dist/samples/ai-powered-summaries-basic/app/.eslintsrc.json b/dist/samples/ai-powered-summaries-basic/app/.eslintsrc.json
new file mode 100644
index 00000000..4c44dab0
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/app/.eslintsrc.json
@@ -0,0 +1,13 @@
+{
+ "extends": [
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "parser": "@typescript-eslint/parser",
+ "rules": {
+ "@typescript-eslint/ban-ts-comment": 0,
+ "@typescript-eslint/no-this-alias": 1,
+ "@typescript-eslint/no-empty-function": 1,
+ "@typescript-eslint/explicit-module-boundary-types": 1,
+ "@typescript-eslint/no-unused-vars": 1
+ }
+}
diff --git a/dist/samples/ai-powered-summaries-basic/app/README.md b/dist/samples/ai-powered-summaries-basic/app/README.md
new file mode 100644
index 00000000..1a4b6847
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/app/README.md
@@ -0,0 +1,36 @@
+# Google Maps JavaScript Sample
+
+## ai-powered-summaries-basic
+
+The ai-powered-summaries-basic sample demonstrates how to retrieve AI-powered summaries.
+
+Follow these instructions to set up and run ai-powered-summaries-basic sample on your local computer.
+
+## Setup
+
+### Before starting run:
+
+`npm i`
+
+### Run an example on a local web server
+
+First `cd` to the folder for the sample to run, then:
+
+`npm start`
+
+### Build an individual example
+
+From `samples/`:
+
+`npm run build --workspace=ai-powered-summaries-basic/`
+
+### Build all of the examples.
+
+From `samples/`:
+
+`npm run build-all`
+
+## Feedback
+
+For feedback related to this sample, please open a new issue on
+[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues).
diff --git a/dist/samples/ai-powered-summaries-basic/app/index.html b/dist/samples/ai-powered-summaries-basic/app/index.html
new file mode 100644
index 00000000..6def208f
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/app/index.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+ AI-powered Summaries Basic Sample
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/samples/ai-powered-summaries-basic/app/index.ts b/dist/samples/ai-powered-summaries-basic/app/index.ts
new file mode 100644
index 00000000..58ab067d
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/app/index.ts
@@ -0,0 +1,110 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+// [START maps_ai_powered_summaries_basic]
+const mapElement = document.querySelector('gmp-map') as google.maps.MapElement;
+let innerMap;
+let infoWindow;
+
+async function initMap() {
+ const { Map, InfoWindow } = (await google.maps.importLibrary(
+ 'maps'
+ )) as google.maps.MapsLibrary;
+
+ innerMap = mapElement.innerMap;
+ innerMap.setOptions({
+ mapTypeControl: false
+ });
+
+ infoWindow = new InfoWindow();
+ getPlaceDetails();
+}
+
+async function getPlaceDetails() {
+ // Request needed libraries.
+ const [ {AdvancedMarkerElement}, { Place } ] = await Promise.all([
+ google.maps.importLibrary('marker') as Promise,
+ google.maps.importLibrary('places') as Promise,
+ ]);
+
+ // [START maps_ai_powered_summaries_basic_placeid]
+ // Use place ID to create a new Place instance.
+ const place = new Place({
+ id: 'ChIJzzc-aWUM3IARPOQr9sA6vfY', // San Diego Botanic Garden
+ });
+ // [END maps_ai_powered_summaries_basic_placeid]
+
+ // Call fetchFields, passing the needed data fields.
+ // [START maps_ai_powered_summaries_basic_fetchfields]
+ await place.fetchFields({
+ fields: [
+ 'displayName',
+ 'formattedAddress',
+ 'location',
+ 'generativeSummary',
+ ],
+ });
+ // [END maps_ai_powered_summaries_basic_fetchfields]
+
+ // Add an Advanced Marker
+ const marker = new AdvancedMarkerElement({
+ map: innerMap,
+ position: place.location,
+ title: place.displayName,
+ });
+
+ // Create a content container.
+ const content = document.createElement('div');
+ // Populate the container with data.
+ const address = document.createElement('div');
+ const summary = document.createElement('div');
+ const lineBreak = document.createElement('br');
+ const attribution = document.createElement('div');
+
+ // Retrieve the textual data (summary, disclosure, flag URI).
+ //@ts-ignore
+ let overviewText = place.generativeSummary.overview ?? 'No summary is available.';
+ //@ts-ignore
+ let disclosureText = place.generativeSummary.disclosureText;
+ //@ts-ignore
+ let reportingUri = place.generativeSummary.flagContentURI;
+
+ // Create HTML for reporting link.
+ const reportingLink = document.createElement('a');
+ reportingLink.href = reportingUri;
+ reportingLink.target = '_blank';
+ reportingLink.textContent = "Report a problem."
+
+ // Add text to layout.
+ address.textContent = place.formattedAddress ?? '';
+ summary.textContent = overviewText;
+ attribution.textContent = `${disclosureText} `;
+ attribution.appendChild(reportingLink);
+
+ content.append(address, lineBreak, summary, lineBreak, attribution);
+
+ innerMap.setCenter(place.location);
+
+ // Handle marker click.
+ marker.addListener('gmp-click', () => {
+ showInfoWindow(marker, place, content);
+ });
+
+ // Display the info window at load time.
+ showInfoWindow(marker, place, content);
+}
+
+function showInfoWindow(marker, place, content) {
+ // Display an info window.
+ infoWindow.setHeaderContent(place.displayName);
+ infoWindow.setContent(content);
+ infoWindow.open({
+ anchor: marker,
+ });
+}
+
+initMap();
+// [END maps_ai_powered_summaries_basic]
diff --git a/dist/samples/ai-powered-summaries-basic/app/package.json b/dist/samples/ai-powered-summaries-basic/app/package.json
new file mode 100644
index 00000000..06aa6790
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/app/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@js-api-samples/ai-powered-summaries-basic",
+ "version": "1.0.0",
+ "scripts": {
+ "build": "tsc && bash ../jsfiddle.sh ai-powered-summaries-basic && bash ../app.sh ai-powered-summaries-basic && bash ../docs.sh ai-powered-summaries-basic && npm run build:vite --workspace=. && bash ../dist.sh ai-powered-summaries-basic",
+ "test": "tsc && npm run build:vite --workspace=.",
+ "start": "tsc && vite build --base './' && vite",
+ "build:vite": "vite build --base './'",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+
+ }
+}
diff --git a/dist/samples/ai-powered-summaries-basic/app/style.css b/dist/samples/ai-powered-summaries-basic/app/style.css
new file mode 100644
index 00000000..93167d23
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/app/style.css
@@ -0,0 +1,25 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/* [START maps_ai_powered_summaries_basic] */
+/*
+ * Always set the map height explicitly to define the size of the div element
+ * that contains the map.
+ */
+#map {
+ height: 100%;
+}
+
+/*
+ * Optional: Makes the sample page fill the window.
+ */
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+/* [END maps_ai_powered_summaries_basic] */
diff --git a/dist/samples/ai-powered-summaries-basic/app/tsconfig.json b/dist/samples/ai-powered-summaries-basic/app/tsconfig.json
new file mode 100644
index 00000000..366aabb0
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/app/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "module": "esnext",
+ "target": "esnext",
+ "strict": true,
+ "noImplicitAny": false,
+ "lib": [
+ "es2015",
+ "esnext",
+ "es6",
+ "dom",
+ "dom.iterable"
+ ],
+ "moduleResolution": "Node",
+ "jsx": "preserve"
+ }
+}
diff --git a/dist/samples/ai-powered-summaries-basic/dist/assets/index-CFZ6i2WL.js b/dist/samples/ai-powered-summaries-basic/dist/assets/index-CFZ6i2WL.js
new file mode 100644
index 00000000..f2ef289f
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/dist/assets/index-CFZ6i2WL.js
@@ -0,0 +1 @@
+(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))o(e);new MutationObserver(e=>{for(const n of e)if(n.type==="childList")for(const i of n.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&o(i)}).observe(document,{childList:!0,subtree:!0});function t(e){const n={};return e.integrity&&(n.integrity=e.integrity),e.referrerPolicy&&(n.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?n.credentials="include":e.crossOrigin==="anonymous"?n.credentials="omit":n.credentials="same-origin",n}function o(e){if(e.ep)return;e.ep=!0;const n=t(e);fetch(e.href,n)}})();const g=document.querySelector("gmp-map");let l,c;async function v(){const{Map:a,InfoWindow:r}=await google.maps.importLibrary("maps");l=g.innerMap,l.setOptions({mapTypeControl:!1}),c=new r,h()}async function h(){const[{AdvancedMarkerElement:a},{Place:r}]=await Promise.all([google.maps.importLibrary("marker"),google.maps.importLibrary("places")]),t=new r({id:"ChIJzzc-aWUM3IARPOQr9sA6vfY"});await t.fetchFields({fields:["displayName","formattedAddress","location","generativeSummary"]});const o=new a({map:l,position:t.location,title:t.displayName}),e=document.createElement("div"),n=document.createElement("div"),i=document.createElement("div"),d=document.createElement("br"),m=document.createElement("div");let u=t.generativeSummary.overview??"No summary is available.",f=t.generativeSummary.disclosureText,y=t.generativeSummary.flagContentURI;const s=document.createElement("a");s.href=y,s.target="_blank",s.textContent="Report a problem.",n.textContent=t.formattedAddress??"",i.textContent=u,m.textContent=`${f} `,m.appendChild(s),e.append(n,d,i,d,m),l.setCenter(t.location),o.addListener("gmp-click",()=>{p(o,t,e)}),p(o,t,e)}function p(a,r,t){c.setHeaderContent(r.displayName),c.setContent(t),c.open({anchor:a})}v();
diff --git a/dist/samples/ai-powered-summaries-basic/dist/assets/index-DWepjxzn.css b/dist/samples/ai-powered-summaries-basic/dist/assets/index-DWepjxzn.css
new file mode 100644
index 00000000..c49b8a3f
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/dist/assets/index-DWepjxzn.css
@@ -0,0 +1 @@
+#map{height:100%}html,body{height:100%;margin:0;padding:0}
diff --git a/dist/samples/ai-powered-summaries-basic/dist/index.html b/dist/samples/ai-powered-summaries-basic/dist/index.html
new file mode 100644
index 00000000..3dbcdc6c
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/dist/index.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+ AI-powered Summaries Basic Sample
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/samples/ai-powered-summaries-basic/docs/index.html b/dist/samples/ai-powered-summaries-basic/docs/index.html
new file mode 100644
index 00000000..6def208f
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/docs/index.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+ AI-powered Summaries Basic Sample
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/samples/ai-powered-summaries-basic/docs/index.js b/dist/samples/ai-powered-summaries-basic/docs/index.js
new file mode 100644
index 00000000..ac1c6e35
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/docs/index.js
@@ -0,0 +1,91 @@
+"use strict";
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+// [START maps_ai_powered_summaries_basic]
+const mapElement = document.querySelector('gmp-map');
+let innerMap;
+let infoWindow;
+async function initMap() {
+ const { Map, InfoWindow } = (await google.maps.importLibrary('maps'));
+ innerMap = mapElement.innerMap;
+ innerMap.setOptions({
+ mapTypeControl: false
+ });
+ infoWindow = new InfoWindow();
+ getPlaceDetails();
+}
+async function getPlaceDetails() {
+ // Request needed libraries.
+ const [{ AdvancedMarkerElement }, { Place }] = await Promise.all([
+ google.maps.importLibrary('marker'),
+ google.maps.importLibrary('places'),
+ ]);
+ // [START maps_ai_powered_summaries_basic_placeid]
+ // Use place ID to create a new Place instance.
+ const place = new Place({
+ id: 'ChIJzzc-aWUM3IARPOQr9sA6vfY', // San Diego Botanic Garden
+ });
+ // [END maps_ai_powered_summaries_basic_placeid]
+ // Call fetchFields, passing the needed data fields.
+ // [START maps_ai_powered_summaries_basic_fetchfields]
+ await place.fetchFields({
+ fields: [
+ 'displayName',
+ 'formattedAddress',
+ 'location',
+ 'generativeSummary',
+ ],
+ });
+ // [END maps_ai_powered_summaries_basic_fetchfields]
+ // Add an Advanced Marker
+ const marker = new AdvancedMarkerElement({
+ map: innerMap,
+ position: place.location,
+ title: place.displayName,
+ });
+ // Create a content container.
+ const content = document.createElement('div');
+ // Populate the container with data.
+ const address = document.createElement('div');
+ const summary = document.createElement('div');
+ const lineBreak = document.createElement('br');
+ const attribution = document.createElement('div');
+ // Retrieve the textual data (summary, disclosure, flag URI).
+ //@ts-ignore
+ let overviewText = place.generativeSummary.overview ?? 'No summary is available.';
+ //@ts-ignore
+ let disclosureText = place.generativeSummary.disclosureText;
+ //@ts-ignore
+ let reportingUri = place.generativeSummary.flagContentURI;
+ // Create HTML for reporting link.
+ const reportingLink = document.createElement('a');
+ reportingLink.href = reportingUri;
+ reportingLink.target = '_blank';
+ reportingLink.textContent = "Report a problem.";
+ // Add text to layout.
+ address.textContent = place.formattedAddress ?? '';
+ summary.textContent = overviewText;
+ attribution.textContent = `${disclosureText} `;
+ attribution.appendChild(reportingLink);
+ content.append(address, lineBreak, summary, lineBreak, attribution);
+ innerMap.setCenter(place.location);
+ // Handle marker click.
+ marker.addListener('gmp-click', () => {
+ showInfoWindow(marker, place, content);
+ });
+ // Display the info window at load time.
+ showInfoWindow(marker, place, content);
+}
+function showInfoWindow(marker, place, content) {
+ // Display an info window.
+ infoWindow.setHeaderContent(place.displayName);
+ infoWindow.setContent(content);
+ infoWindow.open({
+ anchor: marker,
+ });
+}
+initMap();
+// [END maps_ai_powered_summaries_basic]
diff --git a/dist/samples/ai-powered-summaries-basic/docs/index.ts b/dist/samples/ai-powered-summaries-basic/docs/index.ts
new file mode 100644
index 00000000..58ab067d
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/docs/index.ts
@@ -0,0 +1,110 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+// [START maps_ai_powered_summaries_basic]
+const mapElement = document.querySelector('gmp-map') as google.maps.MapElement;
+let innerMap;
+let infoWindow;
+
+async function initMap() {
+ const { Map, InfoWindow } = (await google.maps.importLibrary(
+ 'maps'
+ )) as google.maps.MapsLibrary;
+
+ innerMap = mapElement.innerMap;
+ innerMap.setOptions({
+ mapTypeControl: false
+ });
+
+ infoWindow = new InfoWindow();
+ getPlaceDetails();
+}
+
+async function getPlaceDetails() {
+ // Request needed libraries.
+ const [ {AdvancedMarkerElement}, { Place } ] = await Promise.all([
+ google.maps.importLibrary('marker') as Promise,
+ google.maps.importLibrary('places') as Promise,
+ ]);
+
+ // [START maps_ai_powered_summaries_basic_placeid]
+ // Use place ID to create a new Place instance.
+ const place = new Place({
+ id: 'ChIJzzc-aWUM3IARPOQr9sA6vfY', // San Diego Botanic Garden
+ });
+ // [END maps_ai_powered_summaries_basic_placeid]
+
+ // Call fetchFields, passing the needed data fields.
+ // [START maps_ai_powered_summaries_basic_fetchfields]
+ await place.fetchFields({
+ fields: [
+ 'displayName',
+ 'formattedAddress',
+ 'location',
+ 'generativeSummary',
+ ],
+ });
+ // [END maps_ai_powered_summaries_basic_fetchfields]
+
+ // Add an Advanced Marker
+ const marker = new AdvancedMarkerElement({
+ map: innerMap,
+ position: place.location,
+ title: place.displayName,
+ });
+
+ // Create a content container.
+ const content = document.createElement('div');
+ // Populate the container with data.
+ const address = document.createElement('div');
+ const summary = document.createElement('div');
+ const lineBreak = document.createElement('br');
+ const attribution = document.createElement('div');
+
+ // Retrieve the textual data (summary, disclosure, flag URI).
+ //@ts-ignore
+ let overviewText = place.generativeSummary.overview ?? 'No summary is available.';
+ //@ts-ignore
+ let disclosureText = place.generativeSummary.disclosureText;
+ //@ts-ignore
+ let reportingUri = place.generativeSummary.flagContentURI;
+
+ // Create HTML for reporting link.
+ const reportingLink = document.createElement('a');
+ reportingLink.href = reportingUri;
+ reportingLink.target = '_blank';
+ reportingLink.textContent = "Report a problem."
+
+ // Add text to layout.
+ address.textContent = place.formattedAddress ?? '';
+ summary.textContent = overviewText;
+ attribution.textContent = `${disclosureText} `;
+ attribution.appendChild(reportingLink);
+
+ content.append(address, lineBreak, summary, lineBreak, attribution);
+
+ innerMap.setCenter(place.location);
+
+ // Handle marker click.
+ marker.addListener('gmp-click', () => {
+ showInfoWindow(marker, place, content);
+ });
+
+ // Display the info window at load time.
+ showInfoWindow(marker, place, content);
+}
+
+function showInfoWindow(marker, place, content) {
+ // Display an info window.
+ infoWindow.setHeaderContent(place.displayName);
+ infoWindow.setContent(content);
+ infoWindow.open({
+ anchor: marker,
+ });
+}
+
+initMap();
+// [END maps_ai_powered_summaries_basic]
diff --git a/dist/samples/ai-powered-summaries-basic/docs/style.css b/dist/samples/ai-powered-summaries-basic/docs/style.css
new file mode 100644
index 00000000..93167d23
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/docs/style.css
@@ -0,0 +1,25 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/* [START maps_ai_powered_summaries_basic] */
+/*
+ * Always set the map height explicitly to define the size of the div element
+ * that contains the map.
+ */
+#map {
+ height: 100%;
+}
+
+/*
+ * Optional: Makes the sample page fill the window.
+ */
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+/* [END maps_ai_powered_summaries_basic] */
diff --git a/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.css b/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.css
new file mode 100644
index 00000000..effa9067
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.css
@@ -0,0 +1,25 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/*
+ * Always set the map height explicitly to define the size of the div element
+ * that contains the map.
+ */
+#map {
+ height: 100%;
+}
+
+/*
+ * Optional: Makes the sample page fill the window.
+ */
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+
diff --git a/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.details b/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.details
new file mode 100644
index 00000000..b6e86226
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.details
@@ -0,0 +1,7 @@
+name: ai-powered-summaries-basic
+authors:
+ - Geo Developer IX Documentation Team
+tags:
+ - google maps
+load_type: h
+description: Sample code supporting Google Maps Platform JavaScript API documentation.
diff --git a/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.html b/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.html
new file mode 100644
index 00000000..5f485c7c
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+ AI-powered Summaries Basic Sample
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.js b/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.js
new file mode 100644
index 00000000..83b3ffa2
--- /dev/null
+++ b/dist/samples/ai-powered-summaries-basic/jsfiddle/demo.js
@@ -0,0 +1,91 @@
+"use strict";
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+const mapElement = document.querySelector('gmp-map');
+let innerMap;
+let infoWindow;
+async function initMap() {
+ const { Map, InfoWindow } = (await google.maps.importLibrary('maps'));
+ innerMap = mapElement.innerMap;
+ innerMap.setOptions({
+ mapTypeControl: false
+ });
+ infoWindow = new InfoWindow();
+ getPlaceDetails();
+}
+async function getPlaceDetails() {
+ // Request needed libraries.
+ const [{ AdvancedMarkerElement }, { Place }] = await Promise.all([
+ google.maps.importLibrary('marker'),
+ google.maps.importLibrary('places'),
+ ]);
+
+ // Use place ID to create a new Place instance.
+ const place = new Place({
+ id: 'ChIJzzc-aWUM3IARPOQr9sA6vfY', // San Diego Botanic Garden
+ });
+
+ // Call fetchFields, passing the needed data fields.
+
+ await place.fetchFields({
+ fields: [
+ 'displayName',
+ 'formattedAddress',
+ 'location',
+ 'generativeSummary',
+ ],
+ });
+
+ // Add an Advanced Marker
+ const marker = new AdvancedMarkerElement({
+ map: innerMap,
+ position: place.location,
+ title: place.displayName,
+ });
+ // Create a content container.
+ const content = document.createElement('div');
+ // Populate the container with data.
+ const address = document.createElement('div');
+ const summary = document.createElement('div');
+ const lineBreak = document.createElement('br');
+ const attribution = document.createElement('div');
+ // Retrieve the textual data (summary, disclosure, flag URI).
+ //@ts-ignore
+ let overviewText = place.generativeSummary.overview ?? 'No summary is available.';
+ //@ts-ignore
+ let disclosureText = place.generativeSummary.disclosureText;
+ //@ts-ignore
+ let reportingUri = place.generativeSummary.flagContentURI;
+ // Create HTML for reporting link.
+ const reportingLink = document.createElement('a');
+ reportingLink.href = reportingUri;
+ reportingLink.target = '_blank';
+ reportingLink.textContent = "Report a problem.";
+ // Add text to layout.
+ address.textContent = place.formattedAddress ?? '';
+ summary.textContent = overviewText;
+ attribution.textContent = `${disclosureText} `;
+ attribution.appendChild(reportingLink);
+ content.append(address, lineBreak, summary, lineBreak, attribution);
+ innerMap.setCenter(place.location);
+ // Handle marker click.
+ marker.addListener('gmp-click', () => {
+ showInfoWindow(marker, place, content);
+ });
+ // Display the info window at load time.
+ showInfoWindow(marker, place, content);
+}
+function showInfoWindow(marker, place, content) {
+ // Display an info window.
+ infoWindow.setHeaderContent(place.displayName);
+ infoWindow.setContent(content);
+ infoWindow.open({
+ anchor: marker,
+ });
+}
+initMap();
+
diff --git a/dist/samples/ai-powered-summaries/app/.eslintsrc.json b/dist/samples/ai-powered-summaries/app/.eslintsrc.json
new file mode 100644
index 00000000..4c44dab0
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/app/.eslintsrc.json
@@ -0,0 +1,13 @@
+{
+ "extends": [
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "parser": "@typescript-eslint/parser",
+ "rules": {
+ "@typescript-eslint/ban-ts-comment": 0,
+ "@typescript-eslint/no-this-alias": 1,
+ "@typescript-eslint/no-empty-function": 1,
+ "@typescript-eslint/explicit-module-boundary-types": 1,
+ "@typescript-eslint/no-unused-vars": 1
+ }
+}
diff --git a/dist/samples/ai-powered-summaries/app/README.md b/dist/samples/ai-powered-summaries/app/README.md
new file mode 100644
index 00000000..709d9585
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/app/README.md
@@ -0,0 +1,36 @@
+# Google Maps JavaScript Sample
+
+## ai-powered-summaries
+
+The ai-powered-summaries sample demonstrates how to show AI-powered summaries a map.
+
+Follow these instructions to set up and run ai-powered-summaries sample on your local computer.
+
+## Setup
+
+### Before starting run:
+
+`npm i`
+
+### Run an example on a local web server
+
+First `cd` to the folder for the sample to run, then:
+
+`npm start`
+
+### Build an individual example
+
+From `samples/`:
+
+`npm run build --workspace=ai-powered-summaries/`
+
+### Build all of the examples.
+
+From `samples/`:
+
+`npm run build-all`
+
+## Feedback
+
+For feedback related to this sample, please open a new issue on
+[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues).
diff --git a/dist/samples/ai-powered-summaries/app/index.html b/dist/samples/ai-powered-summaries/app/index.html
new file mode 100644
index 00000000..02da9252
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/app/index.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+ AI Place Summaries
+
+
+
+
+
+
+
+
+
+
Search for a place with AI summaries:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/samples/ai-powered-summaries/app/index.ts b/dist/samples/ai-powered-summaries/app/index.ts
new file mode 100644
index 00000000..4c44a671
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/app/index.ts
@@ -0,0 +1,257 @@
+/*
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+// [START maps_ai_powered_summaries]
+// Define DOM elements.
+const mapElement = document.querySelector('gmp-map') as google.maps.MapElement;
+const placeAutocomplete = document.querySelector(
+ 'gmp-place-autocomplete'
+) as google.maps.places.PlaceAutocompleteElement;
+const summaryPanel = document.getElementById('summary-panel') as HTMLDivElement;
+const placeName = document.getElementById('place-name') as HTMLElement;
+const placeAddress = document.getElementById('place-address') as HTMLElement;
+const tabContainer = document.getElementById('tab-container') as HTMLDivElement;
+const summaryContent = document.getElementById(
+ 'summary-content'
+) as HTMLDivElement;
+const aiDisclosure = document.getElementById('ai-disclosure') as HTMLDivElement;
+const flagContentLink = document.getElementById('flag-content-link') as HTMLAnchorElement;
+
+let innerMap;
+let marker: google.maps.marker.AdvancedMarkerElement;
+
+async function initMap(): Promise {
+ // Request needed libraries.
+ const [] = await Promise.all([
+ google.maps.importLibrary('marker'),
+ google.maps.importLibrary('places'),
+ ]);
+
+ innerMap = mapElement.innerMap;
+ innerMap.setOptions({
+ mapTypeControl: false,
+ streetViewControl: false,
+ fullscreenControl: false,
+ });
+
+ // Bind autocomplete bounds to map bounds.
+ google.maps.event.addListener(innerMap, 'bounds_changed', async () => {
+ placeAutocomplete.locationRestriction = innerMap.getBounds();
+ });
+
+ // Create the marker.
+ marker = new google.maps.marker.AdvancedMarkerElement({
+ map: innerMap,
+ });
+
+ // Handle selection of an autocomplete result.
+ // prettier-ignore
+ // @ts-ignore
+ placeAutocomplete.addEventListener('gmp-select', async ({ placePrediction }) => {
+ const place = placePrediction.toPlace();
+
+ // Fetch all summary fields.
+ // [START maps_ai_powered_summaries_fetchfields]
+ await place.fetchFields({
+ fields: [
+ 'displayName',
+ 'formattedAddress',
+ 'location',
+ 'generativeSummary',
+ 'neighborhoodSummary',
+ 'reviewSummary',
+ 'evChargeAmenitySummary',
+ ],
+ });
+ // [END maps_ai_powered_summaries_fetchfields]
+
+ // Update the map viewport and position the marker.
+ if (place.viewport) {
+ innerMap.fitBounds(place.viewport);
+ } else {
+ innerMap.setCenter(place.location);
+ innerMap.setZoom(17);
+ }
+ marker.position = place.location;
+
+ // Update the panel UI.
+ updateSummaryPanel(place);
+ }
+ );
+}
+
+function updateSummaryPanel(place: google.maps.places.Place) {
+ // Reset UI
+ summaryPanel.classList.remove('hidden');
+ tabContainer.innerHTML = ''; // innerHTML is OK here since we're clearing known child elements.
+ summaryContent.textContent = '';
+ aiDisclosure.textContent = '';
+
+ placeName.textContent = place.displayName || '';
+ placeAddress.textContent = place.formattedAddress || '';
+
+ let firstTabActivated = false;
+
+ /**
+ * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment).
+ */
+ const createTab = (
+ label: string,
+ content: string | Node,
+ disclosure: string,
+ flagUrl: string
+ ) => {
+ const btn = document.createElement('button');
+ btn.className = 'tab-button';
+ btn.textContent = label;
+
+ btn.onclick = () => {
+ // Do nothing if the tab is already active.
+ if (btn.classList.contains('active')) {
+ return;
+ }
+
+ // Manage the active class state.
+ document
+ .querySelectorAll('.tab-button')
+ .forEach((b) => b.classList.remove('active'));
+ btn.classList.add('active');
+
+ if (typeof content === 'string') {
+ summaryContent.textContent = content;
+ } else {
+ summaryContent.replaceChildren(content.cloneNode(true));
+ }
+
+ // Set the disclosure text.
+ aiDisclosure.textContent = disclosure || 'AI-generated content.';
+
+ // Add the content flag URI.
+ if (flagUrl) {
+ flagContentLink.href = flagUrl;
+ flagContentLink.textContent = "Report an issue"
+ }
+ };
+
+ tabContainer.appendChild(btn);
+
+ // Auto-select the first available summary.
+ if (!firstTabActivated) {
+ btn.click();
+ firstTabActivated = true;
+ }
+ };
+
+ // --- 1. Generative Summary (Place) ---
+ //@ts-ignore
+ if (place.generativeSummary?.overview) {
+ createTab(
+ 'Overview',
+ //@ts-ignore
+ place.generativeSummary.overview,
+ //@ts-ignore
+ place.generativeSummary.disclosureText,
+ //@ts-ignore
+ place.generativeSummary.flagContentURI
+ );
+ }
+
+ // --- 2. Review Summary ---
+ //@ts-ignore
+ if (place.reviewSummary?.text) {
+ createTab(
+ 'Reviews',
+ //@ts-ignore
+ place.reviewSummary.text,
+ //@ts-ignore
+ place.reviewSummary.disclosureText,
+ //@ts-ignore
+ place.reviewSummary.flagContentURI
+ );
+ }
+
+ // --- 3. Neighborhood Summary ---
+ //@ts-ignore
+ if (place.neighborhoodSummary?.overview?.content) {
+ createTab(
+ 'Neighborhood',
+ //@ts-ignore
+ place.neighborhoodSummary.overview.content,
+ //@ts-ignore
+ place.neighborhoodSummary.disclosureText,
+ //@ts-ignore
+ place.neighborhoodSummary.flagContentURI
+ );
+ }
+
+ // --- 4. EV Amenity Summary (uses content blocks)) ---
+ //@ts-ignore
+ if (place.evChargeAmenitySummary) {
+ //@ts-ignore
+ const evSummary = place.evChargeAmenitySummary;
+ const evContainer = document.createDocumentFragment();
+
+ // Helper to build a safe DOM section for EV categories.
+ const createSection = (title: string, text: string) => {
+ const wrapper = document.createElement('div');
+ wrapper.style.marginBottom = '15px'; // Or use a CSS class
+
+ const titleEl = document.createElement('strong');
+ titleEl.textContent = title;
+
+ const textEl = document.createElement('div');
+ textEl.textContent = text;
+
+ wrapper.appendChild(titleEl);
+ wrapper.appendChild(textEl);
+ return wrapper;
+ };
+
+ // Check and append each potential section
+ if (evSummary.overview?.content) {
+ evContainer.appendChild(
+ createSection('Overview', evSummary.overview.content)
+ );
+ }
+ if (evSummary.coffee?.content) {
+ evContainer.appendChild(
+ createSection('Coffee', evSummary.coffee.content)
+ );
+ }
+ if (evSummary.restaurant?.content) {
+ evContainer.appendChild(
+ createSection('Food', evSummary.restaurant.content)
+ );
+ }
+ if (evSummary.store?.content) {
+ evContainer.appendChild(
+ createSection('Shopping', evSummary.store.content)
+ );
+ }
+
+ // Only add the tab if the container has children
+ if (evContainer.hasChildNodes()) {
+ createTab(
+ 'EV Amenities',
+ evContainer, // Passing a Node instead of string
+ evSummary.disclosureText,
+ evSummary.flagContentURI
+ );
+ }
+ }
+
+ // Safely handle the empty state.
+ if (!firstTabActivated) {
+ const msg = document.createElement('em');
+ msg.textContent =
+ 'No AI summaries are available for this specific location.';
+ summaryContent.replaceChildren(msg);
+ aiDisclosure.textContent = '';
+ }
+}
+
+initMap();
+// [END maps_ai_powered_summaries]
diff --git a/dist/samples/ai-powered-summaries/app/package.json b/dist/samples/ai-powered-summaries/app/package.json
new file mode 100644
index 00000000..e9df893e
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/app/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@js-api-samples/ai-powered-summaries",
+ "version": "1.0.0",
+ "scripts": {
+ "build": "tsc && bash ../jsfiddle.sh ai-powered-summaries && bash ../app.sh ai-powered-summaries && bash ../docs.sh ai-powered-summaries && npm run build:vite --workspace=. && bash ../dist.sh ai-powered-summaries",
+ "test": "tsc && npm run build:vite --workspace=.",
+ "start": "tsc && vite build --base './' && vite",
+ "build:vite": "vite build --base './'",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+
+ }
+}
diff --git a/dist/samples/ai-powered-summaries/app/style.css b/dist/samples/ai-powered-summaries/app/style.css
new file mode 100644
index 00000000..31092e63
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/app/style.css
@@ -0,0 +1,247 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/* [START maps_ai_powered_summaries] */
+/* Reuse existing map height */
+gmp-map {
+ height: 100%;
+}
+
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+/* Existing Autocomplete Card Style */
+.place-autocomplete-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 15px;
+ font-family: Roboto, sans-serif;
+ font-size: 1rem;
+}
+
+gmp-place-autocomplete {
+ width: 300px;
+}
+
+/* New: Summary Panel Styles */
+.summary-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 0; /* Padding handled by children */
+ font-family: Roboto, sans-serif;
+ width: 350px;
+ max-height: 80vh; /* Prevent overflow on small screens */
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+}
+
+.hidden {
+ display: none;
+}
+
+#place-header {
+ padding: 15px;
+ background-color: #f8f9fa;
+ border-bottom: 1px solid #ddd;
+}
+
+#place-header h2 {
+ margin: 0 0 5px 0;
+ font-size: 1.2rem;
+}
+
+#place-address {
+ margin: 0;
+ color: #555;
+ font-size: 0.9rem;
+}
+
+/* Tab Navigation */
+.tab-container {
+ display: flex;
+ border-bottom: 1px solid #ddd;
+ background-color: #fff;
+}
+
+.tab-button {
+ flex: 1;
+ background: none;
+ border: none;
+ padding: 10px;
+ cursor: pointer;
+ font-weight: 500;
+ color: #555;
+ border-bottom: 3px solid transparent;
+}
+
+.tab-button:hover {
+ background-color: #f1f1f1;
+}
+
+.tab-button.active {
+ font-weight: bold;
+ border-bottom: 3px solid #000000;
+}
+
+.tab-button.active:hover {
+ background-color: #ffffff;
+ cursor: default;
+}
+
+/* Content Area */
+.content-area {
+ padding: 15px;
+ line-height: 1.5;
+ font-size: 0.95rem;
+ color: #333;
+}
+
+.disclosure-footer {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+ border-top: 1px solid #eee;
+ font-style: italic;
+}
+
+.flag-content-link {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+ border-top: 1px solid #eee;
+}
+/* [END maps_ai_powered_summaries] */
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/* [START maps_ai_powered_summaries] */
+/* Reuse existing map height */
+gmp-map {
+ height: 100%;
+}
+
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+/* Existing Autocomplete Card Style */
+.place-autocomplete-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 15px;
+ font-family: Roboto, sans-serif;
+ font-size: 1rem;
+}
+
+gmp-place-autocomplete {
+ width: 300px;
+}
+
+/* New: Summary Panel Styles */
+.summary-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 0; /* Padding handled by children */
+ font-family: Roboto, sans-serif;
+ width: 350px;
+ max-height: 80vh; /* Prevent overflow on small screens */
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+}
+
+.hidden {
+ display: none;
+}
+
+#place-header {
+ padding: 15px;
+ background-color: #f8f9fa;
+ border-bottom: 1px solid #ddd;
+}
+
+#place-header h2 {
+ margin: 0 0 5px 0;
+ font-size: 1.2rem;
+}
+
+#place-address {
+ margin: 0;
+ color: #555;
+ font-size: 0.9rem;
+}
+
+/* Tab Navigation */
+.tab-container {
+ display: flex;
+ border-bottom: 1px solid #ddd;
+ background-color: #fff;
+}
+
+.tab-button {
+ flex: 1;
+ background: none;
+ border: none;
+ padding: 10px;
+ cursor: pointer;
+ font-weight: 500;
+ color: #555;
+ border-bottom: 3px solid transparent;
+}
+
+.tab-button:hover {
+ background-color: #f1f1f1;
+}
+
+.tab-button.active {
+ font-weight: bold;
+ border-bottom: 3px solid #000000;
+}
+
+.tab-button.active:hover {
+ background-color: #ffffff;
+ cursor: default;
+}
+
+/* Content Area */
+.content-area {
+ padding: 15px;
+ line-height: 1.5;
+ font-size: 0.95rem;
+ color: #333;
+}
+
+.disclosure-footer {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+ border-top: 1px solid #eee;
+ font-style: italic;
+}
+
+.flag-content-link {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+}
+/* [END maps_ai_powered_summaries] */
diff --git a/dist/samples/ai-powered-summaries/app/tsconfig.json b/dist/samples/ai-powered-summaries/app/tsconfig.json
new file mode 100644
index 00000000..366aabb0
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/app/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "module": "esnext",
+ "target": "esnext",
+ "strict": true,
+ "noImplicitAny": false,
+ "lib": [
+ "es2015",
+ "esnext",
+ "es6",
+ "dom",
+ "dom.iterable"
+ ],
+ "moduleResolution": "Node",
+ "jsx": "preserve"
+ }
+}
diff --git a/dist/samples/ai-powered-summaries/dist/assets/index-D-fh7R-p.js b/dist/samples/ai-powered-summaries/dist/assets/index-D-fh7R-p.js
new file mode 100644
index 00000000..0ebbd6be
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/dist/assets/index-D-fh7R-p.js
@@ -0,0 +1 @@
+(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))n(e);new MutationObserver(e=>{for(const o of e)if(o.type==="childList")for(const a of o.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&n(a)}).observe(document,{childList:!0,subtree:!0});function c(e){const o={};return e.integrity&&(o.integrity=e.integrity),e.referrerPolicy&&(o.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?o.credentials="include":e.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function n(e){if(e.ep)return;e.ep=!0;const o=c(e);fetch(e.href,o)}})();const h=document.querySelector("gmp-map"),p=document.querySelector("gmp-place-autocomplete"),C=document.getElementById("summary-panel"),S=document.getElementById("place-name"),b=document.getElementById("place-address"),y=document.getElementById("tab-container"),l=document.getElementById("summary-content"),d=document.getElementById("ai-disclosure"),g=document.getElementById("flag-content-link");let s,v;async function w(){await Promise.all([google.maps.importLibrary("marker"),google.maps.importLibrary("places")]),s=h.innerMap,s.setOptions({mapTypeControl:!1,streetViewControl:!1,fullscreenControl:!1}),google.maps.event.addListener(s,"bounds_changed",async()=>{p.locationRestriction=s.getBounds()}),v=new google.maps.marker.AdvancedMarkerElement({map:s}),p.addEventListener("gmp-select",async({placePrediction:t})=>{const r=t.toPlace();await r.fetchFields({fields:["displayName","formattedAddress","location","generativeSummary","neighborhoodSummary","reviewSummary","evChargeAmenitySummary"]}),r.viewport?s.fitBounds(r.viewport):(s.setCenter(r.location),s.setZoom(17)),v.position=r.location,x(r)})}function x(t){C.classList.remove("hidden"),y.innerHTML="",l.textContent="",d.textContent="",S.textContent=t.displayName||"",b.textContent=t.formattedAddress||"";let r=!1;const c=(n,e,o,a)=>{const i=document.createElement("button");i.className="tab-button",i.textContent=n,i.onclick=()=>{i.classList.contains("active")||(document.querySelectorAll(".tab-button").forEach(m=>m.classList.remove("active")),i.classList.add("active"),typeof e=="string"?l.textContent=e:l.replaceChildren(e.cloneNode(!0)),d.textContent=o||"AI-generated content.",a&&(g.href=a,g.textContent="Report an issue"))},y.appendChild(i),r||(i.click(),r=!0)};if(t.generativeSummary?.overview&&c("Overview",t.generativeSummary.overview,t.generativeSummary.disclosureText,t.generativeSummary.flagContentURI),t.reviewSummary?.text&&c("Reviews",t.reviewSummary.text,t.reviewSummary.disclosureText,t.reviewSummary.flagContentURI),t.neighborhoodSummary?.overview?.content&&c("Neighborhood",t.neighborhoodSummary.overview.content,t.neighborhoodSummary.disclosureText,t.neighborhoodSummary.flagContentURI),t.evChargeAmenitySummary){const n=t.evChargeAmenitySummary,e=document.createDocumentFragment(),o=(a,i)=>{const m=document.createElement("div");m.style.marginBottom="15px";const u=document.createElement("strong");u.textContent=a;const f=document.createElement("div");return f.textContent=i,m.appendChild(u),m.appendChild(f),m};n.overview?.content&&e.appendChild(o("Overview",n.overview.content)),n.coffee?.content&&e.appendChild(o("Coffee",n.coffee.content)),n.restaurant?.content&&e.appendChild(o("Food",n.restaurant.content)),n.store?.content&&e.appendChild(o("Shopping",n.store.content)),e.hasChildNodes()&&c("EV Amenities",e,n.disclosureText,n.flagContentURI)}if(!r){const n=document.createElement("em");n.textContent="No AI summaries are available for this specific location.",l.replaceChildren(n),d.textContent=""}}w();
diff --git a/dist/samples/ai-powered-summaries/dist/assets/index-qoXx_FcE.css b/dist/samples/ai-powered-summaries/dist/assets/index-qoXx_FcE.css
new file mode 100644
index 00000000..c41b9d46
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/dist/assets/index-qoXx_FcE.css
@@ -0,0 +1 @@
+.flag-content-link{font-size:.75rem;color:#666;padding:10px 15px;border-top:1px solid #eee}gmp-map{height:100%}html,body{height:100%;margin:0;padding:0}.place-autocomplete-card{background-color:#fff;border-radius:5px;box-shadow:#00000059 0 5px 15px;margin:10px;padding:15px;font-family:Roboto,sans-serif;font-size:1rem}gmp-place-autocomplete{width:300px}.summary-card{background-color:#fff;border-radius:5px;box-shadow:#00000059 0 5px 15px;margin:10px;padding:0;font-family:Roboto,sans-serif;width:350px;max-height:80vh;overflow-y:auto;display:flex;flex-direction:column}.hidden{display:none}#place-header{padding:15px;background-color:#f8f9fa;border-bottom:1px solid #ddd}#place-header h2{margin:0 0 5px;font-size:1.2rem}#place-address{margin:0;color:#555;font-size:.9rem}.tab-container{display:flex;border-bottom:1px solid #ddd;background-color:#fff}.tab-button{flex:1;background:none;border:none;padding:10px;cursor:pointer;font-weight:500;color:#555;border-bottom:3px solid transparent}.tab-button:hover{background-color:#f1f1f1}.tab-button.active{font-weight:700;border-bottom:3px solid #000000}.tab-button.active:hover{background-color:#fff;cursor:default}.content-area{padding:15px;line-height:1.5;font-size:.95rem;color:#333}.disclosure-footer{font-size:.75rem;color:#666;padding:10px 15px;border-top:1px solid #eee;font-style:italic}.flag-content-link{font-size:.75rem;color:#666;padding:10px 15px}
diff --git a/dist/samples/ai-powered-summaries/dist/index.html b/dist/samples/ai-powered-summaries/dist/index.html
new file mode 100644
index 00000000..670700e2
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/dist/index.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+ AI Place Summaries
+
+
+
+
+
+
+
+
+
+
Search for a place with AI summaries:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/samples/ai-powered-summaries/docs/index.html b/dist/samples/ai-powered-summaries/docs/index.html
new file mode 100644
index 00000000..02da9252
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/docs/index.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+ AI Place Summaries
+
+
+
+
+
+
+
+
+
+
Search for a place with AI summaries:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/samples/ai-powered-summaries/docs/index.js b/dist/samples/ai-powered-summaries/docs/index.js
new file mode 100644
index 00000000..651715d3
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/docs/index.js
@@ -0,0 +1,199 @@
+"use strict";
+/*
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+// [START maps_ai_powered_summaries]
+// Define DOM elements.
+const mapElement = document.querySelector('gmp-map');
+const placeAutocomplete = document.querySelector('gmp-place-autocomplete');
+const summaryPanel = document.getElementById('summary-panel');
+const placeName = document.getElementById('place-name');
+const placeAddress = document.getElementById('place-address');
+const tabContainer = document.getElementById('tab-container');
+const summaryContent = document.getElementById('summary-content');
+const aiDisclosure = document.getElementById('ai-disclosure');
+const flagContentLink = document.getElementById('flag-content-link');
+let innerMap;
+let marker;
+async function initMap() {
+ // Request needed libraries.
+ const [] = await Promise.all([
+ google.maps.importLibrary('marker'),
+ google.maps.importLibrary('places'),
+ ]);
+ innerMap = mapElement.innerMap;
+ innerMap.setOptions({
+ mapTypeControl: false,
+ streetViewControl: false,
+ fullscreenControl: false,
+ });
+ // Bind autocomplete bounds to map bounds.
+ google.maps.event.addListener(innerMap, 'bounds_changed', async () => {
+ placeAutocomplete.locationRestriction = innerMap.getBounds();
+ });
+ // Create the marker.
+ marker = new google.maps.marker.AdvancedMarkerElement({
+ map: innerMap,
+ });
+ // Handle selection of an autocomplete result.
+ // prettier-ignore
+ // @ts-ignore
+ placeAutocomplete.addEventListener('gmp-select', async ({ placePrediction }) => {
+ const place = placePrediction.toPlace();
+ // Fetch all summary fields.
+ // [START maps_ai_powered_summaries_fetchfields]
+ await place.fetchFields({
+ fields: [
+ 'displayName',
+ 'formattedAddress',
+ 'location',
+ 'generativeSummary',
+ 'neighborhoodSummary',
+ 'reviewSummary',
+ 'evChargeAmenitySummary',
+ ],
+ });
+ // [END maps_ai_powered_summaries_fetchfields]
+ // Update the map viewport and position the marker.
+ if (place.viewport) {
+ innerMap.fitBounds(place.viewport);
+ }
+ else {
+ innerMap.setCenter(place.location);
+ innerMap.setZoom(17);
+ }
+ marker.position = place.location;
+ // Update the panel UI.
+ updateSummaryPanel(place);
+ });
+}
+function updateSummaryPanel(place) {
+ // Reset UI
+ summaryPanel.classList.remove('hidden');
+ tabContainer.innerHTML = ''; // innerHTML is OK here since we're clearing known child elements.
+ summaryContent.textContent = '';
+ aiDisclosure.textContent = '';
+ placeName.textContent = place.displayName || '';
+ placeAddress.textContent = place.formattedAddress || '';
+ let firstTabActivated = false;
+ /**
+ * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment).
+ */
+ const createTab = (label, content, disclosure, flagUrl) => {
+ const btn = document.createElement('button');
+ btn.className = 'tab-button';
+ btn.textContent = label;
+ btn.onclick = () => {
+ // Do nothing if the tab is already active.
+ if (btn.classList.contains('active')) {
+ return;
+ }
+ // Manage the active class state.
+ document
+ .querySelectorAll('.tab-button')
+ .forEach((b) => b.classList.remove('active'));
+ btn.classList.add('active');
+ if (typeof content === 'string') {
+ summaryContent.textContent = content;
+ }
+ else {
+ summaryContent.replaceChildren(content.cloneNode(true));
+ }
+ // Set the disclosure text.
+ aiDisclosure.textContent = disclosure || 'AI-generated content.';
+ // Add the content flag URI.
+ if (flagUrl) {
+ flagContentLink.href = flagUrl;
+ flagContentLink.textContent = "Report an issue";
+ }
+ };
+ tabContainer.appendChild(btn);
+ // Auto-select the first available summary.
+ if (!firstTabActivated) {
+ btn.click();
+ firstTabActivated = true;
+ }
+ };
+ // --- 1. Generative Summary (Place) ---
+ //@ts-ignore
+ if (place.generativeSummary?.overview) {
+ createTab('Overview',
+ //@ts-ignore
+ place.generativeSummary.overview,
+ //@ts-ignore
+ place.generativeSummary.disclosureText,
+ //@ts-ignore
+ place.generativeSummary.flagContentURI);
+ }
+ // --- 2. Review Summary ---
+ //@ts-ignore
+ if (place.reviewSummary?.text) {
+ createTab('Reviews',
+ //@ts-ignore
+ place.reviewSummary.text,
+ //@ts-ignore
+ place.reviewSummary.disclosureText,
+ //@ts-ignore
+ place.reviewSummary.flagContentURI);
+ }
+ // --- 3. Neighborhood Summary ---
+ //@ts-ignore
+ if (place.neighborhoodSummary?.overview?.content) {
+ createTab('Neighborhood',
+ //@ts-ignore
+ place.neighborhoodSummary.overview.content,
+ //@ts-ignore
+ place.neighborhoodSummary.disclosureText,
+ //@ts-ignore
+ place.neighborhoodSummary.flagContentURI);
+ }
+ // --- 4. EV Amenity Summary (uses content blocks)) ---
+ //@ts-ignore
+ if (place.evChargeAmenitySummary) {
+ //@ts-ignore
+ const evSummary = place.evChargeAmenitySummary;
+ const evContainer = document.createDocumentFragment();
+ // Helper to build a safe DOM section for EV categories.
+ const createSection = (title, text) => {
+ const wrapper = document.createElement('div');
+ wrapper.style.marginBottom = '15px'; // Or use a CSS class
+ const titleEl = document.createElement('strong');
+ titleEl.textContent = title;
+ const textEl = document.createElement('div');
+ textEl.textContent = text;
+ wrapper.appendChild(titleEl);
+ wrapper.appendChild(textEl);
+ return wrapper;
+ };
+ // Check and append each potential section
+ if (evSummary.overview?.content) {
+ evContainer.appendChild(createSection('Overview', evSummary.overview.content));
+ }
+ if (evSummary.coffee?.content) {
+ evContainer.appendChild(createSection('Coffee', evSummary.coffee.content));
+ }
+ if (evSummary.restaurant?.content) {
+ evContainer.appendChild(createSection('Food', evSummary.restaurant.content));
+ }
+ if (evSummary.store?.content) {
+ evContainer.appendChild(createSection('Shopping', evSummary.store.content));
+ }
+ // Only add the tab if the container has children
+ if (evContainer.hasChildNodes()) {
+ createTab('EV Amenities', evContainer, // Passing a Node instead of string
+ evSummary.disclosureText, evSummary.flagContentURI);
+ }
+ }
+ // Safely handle the empty state.
+ if (!firstTabActivated) {
+ const msg = document.createElement('em');
+ msg.textContent =
+ 'No AI summaries are available for this specific location.';
+ summaryContent.replaceChildren(msg);
+ aiDisclosure.textContent = '';
+ }
+}
+initMap();
+// [END maps_ai_powered_summaries]
diff --git a/dist/samples/ai-powered-summaries/docs/index.ts b/dist/samples/ai-powered-summaries/docs/index.ts
new file mode 100644
index 00000000..4c44a671
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/docs/index.ts
@@ -0,0 +1,257 @@
+/*
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+// [START maps_ai_powered_summaries]
+// Define DOM elements.
+const mapElement = document.querySelector('gmp-map') as google.maps.MapElement;
+const placeAutocomplete = document.querySelector(
+ 'gmp-place-autocomplete'
+) as google.maps.places.PlaceAutocompleteElement;
+const summaryPanel = document.getElementById('summary-panel') as HTMLDivElement;
+const placeName = document.getElementById('place-name') as HTMLElement;
+const placeAddress = document.getElementById('place-address') as HTMLElement;
+const tabContainer = document.getElementById('tab-container') as HTMLDivElement;
+const summaryContent = document.getElementById(
+ 'summary-content'
+) as HTMLDivElement;
+const aiDisclosure = document.getElementById('ai-disclosure') as HTMLDivElement;
+const flagContentLink = document.getElementById('flag-content-link') as HTMLAnchorElement;
+
+let innerMap;
+let marker: google.maps.marker.AdvancedMarkerElement;
+
+async function initMap(): Promise {
+ // Request needed libraries.
+ const [] = await Promise.all([
+ google.maps.importLibrary('marker'),
+ google.maps.importLibrary('places'),
+ ]);
+
+ innerMap = mapElement.innerMap;
+ innerMap.setOptions({
+ mapTypeControl: false,
+ streetViewControl: false,
+ fullscreenControl: false,
+ });
+
+ // Bind autocomplete bounds to map bounds.
+ google.maps.event.addListener(innerMap, 'bounds_changed', async () => {
+ placeAutocomplete.locationRestriction = innerMap.getBounds();
+ });
+
+ // Create the marker.
+ marker = new google.maps.marker.AdvancedMarkerElement({
+ map: innerMap,
+ });
+
+ // Handle selection of an autocomplete result.
+ // prettier-ignore
+ // @ts-ignore
+ placeAutocomplete.addEventListener('gmp-select', async ({ placePrediction }) => {
+ const place = placePrediction.toPlace();
+
+ // Fetch all summary fields.
+ // [START maps_ai_powered_summaries_fetchfields]
+ await place.fetchFields({
+ fields: [
+ 'displayName',
+ 'formattedAddress',
+ 'location',
+ 'generativeSummary',
+ 'neighborhoodSummary',
+ 'reviewSummary',
+ 'evChargeAmenitySummary',
+ ],
+ });
+ // [END maps_ai_powered_summaries_fetchfields]
+
+ // Update the map viewport and position the marker.
+ if (place.viewport) {
+ innerMap.fitBounds(place.viewport);
+ } else {
+ innerMap.setCenter(place.location);
+ innerMap.setZoom(17);
+ }
+ marker.position = place.location;
+
+ // Update the panel UI.
+ updateSummaryPanel(place);
+ }
+ );
+}
+
+function updateSummaryPanel(place: google.maps.places.Place) {
+ // Reset UI
+ summaryPanel.classList.remove('hidden');
+ tabContainer.innerHTML = ''; // innerHTML is OK here since we're clearing known child elements.
+ summaryContent.textContent = '';
+ aiDisclosure.textContent = '';
+
+ placeName.textContent = place.displayName || '';
+ placeAddress.textContent = place.formattedAddress || '';
+
+ let firstTabActivated = false;
+
+ /**
+ * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment).
+ */
+ const createTab = (
+ label: string,
+ content: string | Node,
+ disclosure: string,
+ flagUrl: string
+ ) => {
+ const btn = document.createElement('button');
+ btn.className = 'tab-button';
+ btn.textContent = label;
+
+ btn.onclick = () => {
+ // Do nothing if the tab is already active.
+ if (btn.classList.contains('active')) {
+ return;
+ }
+
+ // Manage the active class state.
+ document
+ .querySelectorAll('.tab-button')
+ .forEach((b) => b.classList.remove('active'));
+ btn.classList.add('active');
+
+ if (typeof content === 'string') {
+ summaryContent.textContent = content;
+ } else {
+ summaryContent.replaceChildren(content.cloneNode(true));
+ }
+
+ // Set the disclosure text.
+ aiDisclosure.textContent = disclosure || 'AI-generated content.';
+
+ // Add the content flag URI.
+ if (flagUrl) {
+ flagContentLink.href = flagUrl;
+ flagContentLink.textContent = "Report an issue"
+ }
+ };
+
+ tabContainer.appendChild(btn);
+
+ // Auto-select the first available summary.
+ if (!firstTabActivated) {
+ btn.click();
+ firstTabActivated = true;
+ }
+ };
+
+ // --- 1. Generative Summary (Place) ---
+ //@ts-ignore
+ if (place.generativeSummary?.overview) {
+ createTab(
+ 'Overview',
+ //@ts-ignore
+ place.generativeSummary.overview,
+ //@ts-ignore
+ place.generativeSummary.disclosureText,
+ //@ts-ignore
+ place.generativeSummary.flagContentURI
+ );
+ }
+
+ // --- 2. Review Summary ---
+ //@ts-ignore
+ if (place.reviewSummary?.text) {
+ createTab(
+ 'Reviews',
+ //@ts-ignore
+ place.reviewSummary.text,
+ //@ts-ignore
+ place.reviewSummary.disclosureText,
+ //@ts-ignore
+ place.reviewSummary.flagContentURI
+ );
+ }
+
+ // --- 3. Neighborhood Summary ---
+ //@ts-ignore
+ if (place.neighborhoodSummary?.overview?.content) {
+ createTab(
+ 'Neighborhood',
+ //@ts-ignore
+ place.neighborhoodSummary.overview.content,
+ //@ts-ignore
+ place.neighborhoodSummary.disclosureText,
+ //@ts-ignore
+ place.neighborhoodSummary.flagContentURI
+ );
+ }
+
+ // --- 4. EV Amenity Summary (uses content blocks)) ---
+ //@ts-ignore
+ if (place.evChargeAmenitySummary) {
+ //@ts-ignore
+ const evSummary = place.evChargeAmenitySummary;
+ const evContainer = document.createDocumentFragment();
+
+ // Helper to build a safe DOM section for EV categories.
+ const createSection = (title: string, text: string) => {
+ const wrapper = document.createElement('div');
+ wrapper.style.marginBottom = '15px'; // Or use a CSS class
+
+ const titleEl = document.createElement('strong');
+ titleEl.textContent = title;
+
+ const textEl = document.createElement('div');
+ textEl.textContent = text;
+
+ wrapper.appendChild(titleEl);
+ wrapper.appendChild(textEl);
+ return wrapper;
+ };
+
+ // Check and append each potential section
+ if (evSummary.overview?.content) {
+ evContainer.appendChild(
+ createSection('Overview', evSummary.overview.content)
+ );
+ }
+ if (evSummary.coffee?.content) {
+ evContainer.appendChild(
+ createSection('Coffee', evSummary.coffee.content)
+ );
+ }
+ if (evSummary.restaurant?.content) {
+ evContainer.appendChild(
+ createSection('Food', evSummary.restaurant.content)
+ );
+ }
+ if (evSummary.store?.content) {
+ evContainer.appendChild(
+ createSection('Shopping', evSummary.store.content)
+ );
+ }
+
+ // Only add the tab if the container has children
+ if (evContainer.hasChildNodes()) {
+ createTab(
+ 'EV Amenities',
+ evContainer, // Passing a Node instead of string
+ evSummary.disclosureText,
+ evSummary.flagContentURI
+ );
+ }
+ }
+
+ // Safely handle the empty state.
+ if (!firstTabActivated) {
+ const msg = document.createElement('em');
+ msg.textContent =
+ 'No AI summaries are available for this specific location.';
+ summaryContent.replaceChildren(msg);
+ aiDisclosure.textContent = '';
+ }
+}
+
+initMap();
+// [END maps_ai_powered_summaries]
diff --git a/dist/samples/ai-powered-summaries/docs/style.css b/dist/samples/ai-powered-summaries/docs/style.css
new file mode 100644
index 00000000..31092e63
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/docs/style.css
@@ -0,0 +1,247 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/* [START maps_ai_powered_summaries] */
+/* Reuse existing map height */
+gmp-map {
+ height: 100%;
+}
+
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+/* Existing Autocomplete Card Style */
+.place-autocomplete-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 15px;
+ font-family: Roboto, sans-serif;
+ font-size: 1rem;
+}
+
+gmp-place-autocomplete {
+ width: 300px;
+}
+
+/* New: Summary Panel Styles */
+.summary-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 0; /* Padding handled by children */
+ font-family: Roboto, sans-serif;
+ width: 350px;
+ max-height: 80vh; /* Prevent overflow on small screens */
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+}
+
+.hidden {
+ display: none;
+}
+
+#place-header {
+ padding: 15px;
+ background-color: #f8f9fa;
+ border-bottom: 1px solid #ddd;
+}
+
+#place-header h2 {
+ margin: 0 0 5px 0;
+ font-size: 1.2rem;
+}
+
+#place-address {
+ margin: 0;
+ color: #555;
+ font-size: 0.9rem;
+}
+
+/* Tab Navigation */
+.tab-container {
+ display: flex;
+ border-bottom: 1px solid #ddd;
+ background-color: #fff;
+}
+
+.tab-button {
+ flex: 1;
+ background: none;
+ border: none;
+ padding: 10px;
+ cursor: pointer;
+ font-weight: 500;
+ color: #555;
+ border-bottom: 3px solid transparent;
+}
+
+.tab-button:hover {
+ background-color: #f1f1f1;
+}
+
+.tab-button.active {
+ font-weight: bold;
+ border-bottom: 3px solid #000000;
+}
+
+.tab-button.active:hover {
+ background-color: #ffffff;
+ cursor: default;
+}
+
+/* Content Area */
+.content-area {
+ padding: 15px;
+ line-height: 1.5;
+ font-size: 0.95rem;
+ color: #333;
+}
+
+.disclosure-footer {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+ border-top: 1px solid #eee;
+ font-style: italic;
+}
+
+.flag-content-link {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+ border-top: 1px solid #eee;
+}
+/* [END maps_ai_powered_summaries] */
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/* [START maps_ai_powered_summaries] */
+/* Reuse existing map height */
+gmp-map {
+ height: 100%;
+}
+
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+/* Existing Autocomplete Card Style */
+.place-autocomplete-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 15px;
+ font-family: Roboto, sans-serif;
+ font-size: 1rem;
+}
+
+gmp-place-autocomplete {
+ width: 300px;
+}
+
+/* New: Summary Panel Styles */
+.summary-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 0; /* Padding handled by children */
+ font-family: Roboto, sans-serif;
+ width: 350px;
+ max-height: 80vh; /* Prevent overflow on small screens */
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+}
+
+.hidden {
+ display: none;
+}
+
+#place-header {
+ padding: 15px;
+ background-color: #f8f9fa;
+ border-bottom: 1px solid #ddd;
+}
+
+#place-header h2 {
+ margin: 0 0 5px 0;
+ font-size: 1.2rem;
+}
+
+#place-address {
+ margin: 0;
+ color: #555;
+ font-size: 0.9rem;
+}
+
+/* Tab Navigation */
+.tab-container {
+ display: flex;
+ border-bottom: 1px solid #ddd;
+ background-color: #fff;
+}
+
+.tab-button {
+ flex: 1;
+ background: none;
+ border: none;
+ padding: 10px;
+ cursor: pointer;
+ font-weight: 500;
+ color: #555;
+ border-bottom: 3px solid transparent;
+}
+
+.tab-button:hover {
+ background-color: #f1f1f1;
+}
+
+.tab-button.active {
+ font-weight: bold;
+ border-bottom: 3px solid #000000;
+}
+
+.tab-button.active:hover {
+ background-color: #ffffff;
+ cursor: default;
+}
+
+/* Content Area */
+.content-area {
+ padding: 15px;
+ line-height: 1.5;
+ font-size: 0.95rem;
+ color: #333;
+}
+
+.disclosure-footer {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+ border-top: 1px solid #eee;
+ font-style: italic;
+}
+
+.flag-content-link {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+}
+/* [END maps_ai_powered_summaries] */
diff --git a/dist/samples/ai-powered-summaries/jsfiddle/demo.css b/dist/samples/ai-powered-summaries/jsfiddle/demo.css
new file mode 100644
index 00000000..1525097d
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/jsfiddle/demo.css
@@ -0,0 +1,247 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/* Reuse existing map height */
+gmp-map {
+ height: 100%;
+}
+
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+/* Existing Autocomplete Card Style */
+.place-autocomplete-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 15px;
+ font-family: Roboto, sans-serif;
+ font-size: 1rem;
+}
+
+gmp-place-autocomplete {
+ width: 300px;
+}
+
+/* New: Summary Panel Styles */
+.summary-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 0; /* Padding handled by children */
+ font-family: Roboto, sans-serif;
+ width: 350px;
+ max-height: 80vh; /* Prevent overflow on small screens */
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+}
+
+.hidden {
+ display: none;
+}
+
+#place-header {
+ padding: 15px;
+ background-color: #f8f9fa;
+ border-bottom: 1px solid #ddd;
+}
+
+#place-header h2 {
+ margin: 0 0 5px 0;
+ font-size: 1.2rem;
+}
+
+#place-address {
+ margin: 0;
+ color: #555;
+ font-size: 0.9rem;
+}
+
+/* Tab Navigation */
+.tab-container {
+ display: flex;
+ border-bottom: 1px solid #ddd;
+ background-color: #fff;
+}
+
+.tab-button {
+ flex: 1;
+ background: none;
+ border: none;
+ padding: 10px;
+ cursor: pointer;
+ font-weight: 500;
+ color: #555;
+ border-bottom: 3px solid transparent;
+}
+
+.tab-button:hover {
+ background-color: #f1f1f1;
+}
+
+.tab-button.active {
+ font-weight: bold;
+ border-bottom: 3px solid #000000;
+}
+
+.tab-button.active:hover {
+ background-color: #ffffff;
+ cursor: default;
+}
+
+/* Content Area */
+.content-area {
+ padding: 15px;
+ line-height: 1.5;
+ font-size: 0.95rem;
+ color: #333;
+}
+
+.disclosure-footer {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+ border-top: 1px solid #eee;
+ font-style: italic;
+}
+
+.flag-content-link {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+ border-top: 1px solid #eee;
+}
+
+/**
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/* Reuse existing map height */
+gmp-map {
+ height: 100%;
+}
+
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+/* Existing Autocomplete Card Style */
+.place-autocomplete-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 15px;
+ font-family: Roboto, sans-serif;
+ font-size: 1rem;
+}
+
+gmp-place-autocomplete {
+ width: 300px;
+}
+
+/* New: Summary Panel Styles */
+.summary-card {
+ background-color: #fff;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ margin: 10px;
+ padding: 0; /* Padding handled by children */
+ font-family: Roboto, sans-serif;
+ width: 350px;
+ max-height: 80vh; /* Prevent overflow on small screens */
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+}
+
+.hidden {
+ display: none;
+}
+
+#place-header {
+ padding: 15px;
+ background-color: #f8f9fa;
+ border-bottom: 1px solid #ddd;
+}
+
+#place-header h2 {
+ margin: 0 0 5px 0;
+ font-size: 1.2rem;
+}
+
+#place-address {
+ margin: 0;
+ color: #555;
+ font-size: 0.9rem;
+}
+
+/* Tab Navigation */
+.tab-container {
+ display: flex;
+ border-bottom: 1px solid #ddd;
+ background-color: #fff;
+}
+
+.tab-button {
+ flex: 1;
+ background: none;
+ border: none;
+ padding: 10px;
+ cursor: pointer;
+ font-weight: 500;
+ color: #555;
+ border-bottom: 3px solid transparent;
+}
+
+.tab-button:hover {
+ background-color: #f1f1f1;
+}
+
+.tab-button.active {
+ font-weight: bold;
+ border-bottom: 3px solid #000000;
+}
+
+.tab-button.active:hover {
+ background-color: #ffffff;
+ cursor: default;
+}
+
+/* Content Area */
+.content-area {
+ padding: 15px;
+ line-height: 1.5;
+ font-size: 0.95rem;
+ color: #333;
+}
+
+.disclosure-footer {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+ border-top: 1px solid #eee;
+ font-style: italic;
+}
+
+.flag-content-link {
+ font-size: 0.75rem;
+ color: #666;
+ padding: 10px 15px;
+}
+
diff --git a/dist/samples/ai-powered-summaries/jsfiddle/demo.details b/dist/samples/ai-powered-summaries/jsfiddle/demo.details
new file mode 100644
index 00000000..22b05589
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/jsfiddle/demo.details
@@ -0,0 +1,7 @@
+name: ai-powered-summaries
+authors:
+ - Geo Developer IX Documentation Team
+tags:
+ - google maps
+load_type: h
+description: Sample code supporting Google Maps Platform JavaScript API documentation.
diff --git a/dist/samples/ai-powered-summaries/jsfiddle/demo.html b/dist/samples/ai-powered-summaries/jsfiddle/demo.html
new file mode 100644
index 00000000..34258a6c
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/jsfiddle/demo.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+ AI Place Summaries
+
+
+
+
+
+
+
+
+
+
Search for a place with AI summaries:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/samples/ai-powered-summaries/jsfiddle/demo.js b/dist/samples/ai-powered-summaries/jsfiddle/demo.js
new file mode 100644
index 00000000..070184e4
--- /dev/null
+++ b/dist/samples/ai-powered-summaries/jsfiddle/demo.js
@@ -0,0 +1,199 @@
+"use strict";
+/*
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+// Define DOM elements.
+const mapElement = document.querySelector('gmp-map');
+const placeAutocomplete = document.querySelector('gmp-place-autocomplete');
+const summaryPanel = document.getElementById('summary-panel');
+const placeName = document.getElementById('place-name');
+const placeAddress = document.getElementById('place-address');
+const tabContainer = document.getElementById('tab-container');
+const summaryContent = document.getElementById('summary-content');
+const aiDisclosure = document.getElementById('ai-disclosure');
+const flagContentLink = document.getElementById('flag-content-link');
+let innerMap;
+let marker;
+async function initMap() {
+ // Request needed libraries.
+ const [] = await Promise.all([
+ google.maps.importLibrary('marker'),
+ google.maps.importLibrary('places'),
+ ]);
+ innerMap = mapElement.innerMap;
+ innerMap.setOptions({
+ mapTypeControl: false,
+ streetViewControl: false,
+ fullscreenControl: false,
+ });
+ // Bind autocomplete bounds to map bounds.
+ google.maps.event.addListener(innerMap, 'bounds_changed', async () => {
+ placeAutocomplete.locationRestriction = innerMap.getBounds();
+ });
+ // Create the marker.
+ marker = new google.maps.marker.AdvancedMarkerElement({
+ map: innerMap,
+ });
+ // Handle selection of an autocomplete result.
+ // prettier-ignore
+ // @ts-ignore
+ placeAutocomplete.addEventListener('gmp-select', async ({ placePrediction }) => {
+ const place = placePrediction.toPlace();
+ // Fetch all summary fields.
+
+ await place.fetchFields({
+ fields: [
+ 'displayName',
+ 'formattedAddress',
+ 'location',
+ 'generativeSummary',
+ 'neighborhoodSummary',
+ 'reviewSummary',
+ 'evChargeAmenitySummary',
+ ],
+ });
+
+ // Update the map viewport and position the marker.
+ if (place.viewport) {
+ innerMap.fitBounds(place.viewport);
+ }
+ else {
+ innerMap.setCenter(place.location);
+ innerMap.setZoom(17);
+ }
+ marker.position = place.location;
+ // Update the panel UI.
+ updateSummaryPanel(place);
+ });
+}
+function updateSummaryPanel(place) {
+ // Reset UI
+ summaryPanel.classList.remove('hidden');
+ tabContainer.innerHTML = ''; // innerHTML is OK here since we're clearing known child elements.
+ summaryContent.textContent = '';
+ aiDisclosure.textContent = '';
+ placeName.textContent = place.displayName || '';
+ placeAddress.textContent = place.formattedAddress || '';
+ let firstTabActivated = false;
+ /**
+ * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment).
+ */
+ const createTab = (label, content, disclosure, flagUrl) => {
+ const btn = document.createElement('button');
+ btn.className = 'tab-button';
+ btn.textContent = label;
+ btn.onclick = () => {
+ // Do nothing if the tab is already active.
+ if (btn.classList.contains('active')) {
+ return;
+ }
+ // Manage the active class state.
+ document
+ .querySelectorAll('.tab-button')
+ .forEach((b) => b.classList.remove('active'));
+ btn.classList.add('active');
+ if (typeof content === 'string') {
+ summaryContent.textContent = content;
+ }
+ else {
+ summaryContent.replaceChildren(content.cloneNode(true));
+ }
+ // Set the disclosure text.
+ aiDisclosure.textContent = disclosure || 'AI-generated content.';
+ // Add the content flag URI.
+ if (flagUrl) {
+ flagContentLink.href = flagUrl;
+ flagContentLink.textContent = "Report an issue";
+ }
+ };
+ tabContainer.appendChild(btn);
+ // Auto-select the first available summary.
+ if (!firstTabActivated) {
+ btn.click();
+ firstTabActivated = true;
+ }
+ };
+ // --- 1. Generative Summary (Place) ---
+ //@ts-ignore
+ if (place.generativeSummary?.overview) {
+ createTab('Overview',
+ //@ts-ignore
+ place.generativeSummary.overview,
+ //@ts-ignore
+ place.generativeSummary.disclosureText,
+ //@ts-ignore
+ place.generativeSummary.flagContentURI);
+ }
+ // --- 2. Review Summary ---
+ //@ts-ignore
+ if (place.reviewSummary?.text) {
+ createTab('Reviews',
+ //@ts-ignore
+ place.reviewSummary.text,
+ //@ts-ignore
+ place.reviewSummary.disclosureText,
+ //@ts-ignore
+ place.reviewSummary.flagContentURI);
+ }
+ // --- 3. Neighborhood Summary ---
+ //@ts-ignore
+ if (place.neighborhoodSummary?.overview?.content) {
+ createTab('Neighborhood',
+ //@ts-ignore
+ place.neighborhoodSummary.overview.content,
+ //@ts-ignore
+ place.neighborhoodSummary.disclosureText,
+ //@ts-ignore
+ place.neighborhoodSummary.flagContentURI);
+ }
+ // --- 4. EV Amenity Summary (uses content blocks)) ---
+ //@ts-ignore
+ if (place.evChargeAmenitySummary) {
+ //@ts-ignore
+ const evSummary = place.evChargeAmenitySummary;
+ const evContainer = document.createDocumentFragment();
+ // Helper to build a safe DOM section for EV categories.
+ const createSection = (title, text) => {
+ const wrapper = document.createElement('div');
+ wrapper.style.marginBottom = '15px'; // Or use a CSS class
+ const titleEl = document.createElement('strong');
+ titleEl.textContent = title;
+ const textEl = document.createElement('div');
+ textEl.textContent = text;
+ wrapper.appendChild(titleEl);
+ wrapper.appendChild(textEl);
+ return wrapper;
+ };
+ // Check and append each potential section
+ if (evSummary.overview?.content) {
+ evContainer.appendChild(createSection('Overview', evSummary.overview.content));
+ }
+ if (evSummary.coffee?.content) {
+ evContainer.appendChild(createSection('Coffee', evSummary.coffee.content));
+ }
+ if (evSummary.restaurant?.content) {
+ evContainer.appendChild(createSection('Food', evSummary.restaurant.content));
+ }
+ if (evSummary.store?.content) {
+ evContainer.appendChild(createSection('Shopping', evSummary.store.content));
+ }
+ // Only add the tab if the container has children
+ if (evContainer.hasChildNodes()) {
+ createTab('EV Amenities', evContainer, // Passing a Node instead of string
+ evSummary.disclosureText, evSummary.flagContentURI);
+ }
+ }
+ // Safely handle the empty state.
+ if (!firstTabActivated) {
+ const msg = document.createElement('em');
+ msg.textContent =
+ 'No AI summaries are available for this specific location.';
+ summaryContent.replaceChildren(msg);
+ aiDisclosure.textContent = '';
+ }
+}
+initMap();
+
diff --git a/index.html b/index.html
index 02f85042..820cd730 100644
--- a/index.html
+++ b/index.html
@@ -48,6 +48,8 @@ Maps JSAPI Samples
advanced-markers-html-simple
advanced-markers-simple
advanced-markers-zoom
+ ai-powered-summaries
+ ai-powered-summaries-basic
boundaries-choropleth
boundaries-click
boundaries-simple
diff --git a/package-lock.json b/package-lock.json
index 8b30b48f..d1de45c2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1360,6 +1360,14 @@
"resolved": "samples/advanced-markers-zoom",
"link": true
},
+ "node_modules/@js-api-samples/ai-powered-summaries": {
+ "resolved": "samples/ai-powered-summaries",
+ "link": true
+ },
+ "node_modules/@js-api-samples/ai-powered-summaries-basic": {
+ "resolved": "samples/ai-powered-summaries-basic",
+ "link": true
+ },
"node_modules/@js-api-samples/boundaries-choropleth": {
"resolved": "samples/boundaries-choropleth",
"link": true
@@ -8268,6 +8276,14 @@
"name": "@js-api-samples/advanced-markers-zoom",
"version": "1.0.0"
},
+ "samples/ai-powered-summaries": {
+ "name": "@js-api-samples/ai-powered-summaries",
+ "version": "1.0.0"
+ },
+ "samples/ai-powered-summaries-basic": {
+ "name": "@js-api-samples/ai-powered-summaries-basic",
+ "version": "1.0.0"
+ },
"samples/boundaries-choropleth": {
"name": "@js-api-samples/boundaries-choropleth",
"version": "1.0.0"