AI-powered summaries

This sample lets you search for AI-powered summaries. Some suggested searches:

  • "Hotel" for neighborhood summaries.
  • "EV charging station" for EVCS amenity summaries.
  • Any restaurant or business for place and review summaries.

Read the documentation .

TypeScript

 // 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<void> 
  
 { 
  
 // 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 
 : 
  
 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 
 (); 
  

JavaScript

 // 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 
 (); 
  

CSS

 /* 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 
 : 
  
 5 
 px 
 ; 
  
 box-shadow 
 : 
  
 rgba 
 ( 
 0 
 , 
  
 0 
 , 
  
 0 
 , 
  
 0.35 
 ) 
  
 0 
 px 
  
 5 
 px 
  
 15 
 px 
 ; 
  
 margin 
 : 
  
 10 
 px 
 ; 
  
 padding 
 : 
  
 15 
 px 
 ; 
  
 font-family 
 : 
  
 Roboto 
 , 
  
 sans-serif 
 ; 
  
 font-size 
 : 
  
 1 
 rem 
 ; 
 } 
 gmp-place-autocomplete 
  
 { 
  
 width 
 : 
  
 300 
 px 
 ; 
 } 
 /* New: Summary Panel Styles */ 
 . 
 summary-card 
  
 { 
  
 background-color 
 : 
  
 #fff 
 ; 
  
 border-radius 
 : 
  
 5 
 px 
 ; 
  
 box-shadow 
 : 
  
 rgba 
 ( 
 0 
 , 
  
 0 
 , 
  
 0 
 , 
  
 0.35 
 ) 
  
 0 
 px 
  
 5 
 px 
  
 15 
 px 
 ; 
  
 margin 
 : 
  
 10 
 px 
 ; 
  
 padding 
 : 
  
 0 
 ; 
  
 /* Padding handled by children */ 
  
 font-family 
 : 
  
 Roboto 
 , 
  
 sans-serif 
 ; 
  
 width 
 : 
  
 350 
 px 
 ; 
  
 max-height 
 : 
  
 80 
 vh 
 ; 
  
 /* Prevent overflow on small screens */ 
  
 overflow-y 
 : 
  
 auto 
 ; 
  
 display 
 : 
  
 flex 
 ; 
  
 flex-direction 
 : 
  
 column 
 ; 
 } 
 . 
 hidden 
  
 { 
  
 display 
 : 
  
 none 
 ; 
 } 
 # 
 place-header 
  
 { 
  
 padding 
 : 
  
 15 
 px 
 ; 
  
 background-color 
 : 
  
 #f8f9fa 
 ; 
  
 border-bottom 
 : 
  
 1 
 px 
  
 solid 
  
 #ddd 
 ; 
 } 
 # 
 place-header 
  
 h2 
  
 { 
  
 margin 
 : 
  
 0 
  
 0 
  
 5 
 px 
  
 0 
 ; 
  
 font-size 
 : 
  
 1.2 
 rem 
 ; 
 } 
 # 
 place-address 
  
 { 
  
 margin 
 : 
  
 0 
 ; 
  
 color 
 : 
  
 #555 
 ; 
  
 font-size 
 : 
  
 0.9 
 rem 
 ; 
 } 
 /* Tab Navigation */ 
 . 
 tab-container 
  
 { 
  
 display 
 : 
  
 flex 
 ; 
  
 border-bottom 
 : 
  
 1 
 px 
  
 solid 
  
 #ddd 
 ; 
  
 background-color 
 : 
  
 #fff 
 ; 
 } 
 . 
 tab-button 
  
 { 
  
 flex 
 : 
  
 1 
 ; 
  
 background 
 : 
  
 none 
 ; 
  
 border 
 : 
  
 none 
 ; 
  
 padding 
 : 
  
 10 
 px 
 ; 
  
 cursor 
 : 
  
 pointer 
 ; 
  
 font-weight 
 : 
  
 500 
 ; 
  
 color 
 : 
  
 #555 
 ; 
  
 border-bottom 
 : 
  
 3 
 px 
  
 solid 
  
 transparent 
 ; 
 } 
 . 
 tab-button 
 : 
 hover 
  
 { 
  
 background-color 
 : 
  
 #f1f1f1 
 ; 
 } 
 . 
 tab-button 
 . 
 active 
  
 { 
  
 font-weight 
 : 
  
 bold 
 ; 
  
 border-bottom 
 : 
  
 3 
 px 
  
 solid 
  
 #000000 
 ; 
 } 
 . 
 tab-button 
 . 
 active 
 : 
 hover 
  
 { 
  
 background-color 
 : 
  
 #ffffff 
 ; 
  
 cursor 
 : 
  
 default 
 ; 
 } 
 /* Content Area */ 
 . 
 content-area 
  
 { 
  
 padding 
 : 
  
 15 
 px 
 ; 
  
 line-height 
 : 
  
 1.5 
 ; 
  
 font-size 
 : 
  
 0.95 
 rem 
 ; 
  
 color 
 : 
  
 #333 
 ; 
 } 
 . 
 disclosure-footer 
  
 { 
  
 font-size 
 : 
  
 0.75 
 rem 
 ; 
  
 color 
 : 
  
 #666 
 ; 
  
 padding 
 : 
  
 10 
 px 
  
 15 
 px 
 ; 
  
 border-top 
 : 
  
 1 
 px 
  
 solid 
  
 #eee 
 ; 
  
 font-style 
 : 
  
 italic 
 ; 
 } 
 . 
 flag-content-link 
  
 { 
  
 font-size 
 : 
  
 0.75 
 rem 
 ; 
  
 color 
 : 
  
 #666 
 ; 
  
 padding 
 : 
  
 10 
 px 
  
 15 
 px 
 ; 
  
 border-top 
 : 
  
 1 
 px 
  
 solid 
  
 #eee 
 ; 
 } 
 /* 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 
 : 
  
 5 
 px 
 ; 
  
 box-shadow 
 : 
  
 rgba 
 ( 
 0 
 , 
  
 0 
 , 
  
 0 
 , 
  
 0.35 
 ) 
  
 0 
 px 
  
 5 
 px 
  
 15 
 px 
 ; 
  
 margin 
 : 
  
 10 
 px 
 ; 
  
 padding 
 : 
  
 15 
 px 
 ; 
  
 font-family 
 : 
  
 Roboto 
 , 
  
 sans-serif 
 ; 
  
 font-size 
 : 
  
 1 
 rem 
 ; 
 } 
 gmp-place-autocomplete 
  
 { 
  
 width 
 : 
  
 300 
 px 
 ; 
 } 
 /* New: Summary Panel Styles */ 
 . 
 summary-card 
  
 { 
  
 background-color 
 : 
  
 #fff 
 ; 
  
 border-radius 
 : 
  
 5 
 px 
 ; 
  
 box-shadow 
 : 
  
 rgba 
 ( 
 0 
 , 
  
 0 
 , 
  
 0 
 , 
  
 0.35 
 ) 
  
 0 
 px 
  
 5 
 px 
  
 15 
 px 
 ; 
  
 margin 
 : 
  
 10 
 px 
 ; 
  
 padding 
 : 
  
 0 
 ; 
  
 /* Padding handled by children */ 
  
 font-family 
 : 
  
 Roboto 
 , 
  
 sans-serif 
 ; 
  
 width 
 : 
  
 350 
 px 
 ; 
  
 max-height 
 : 
  
 80 
 vh 
 ; 
  
 /* Prevent overflow on small screens */ 
  
 overflow-y 
 : 
  
 auto 
 ; 
  
 display 
 : 
  
 flex 
 ; 
  
 flex-direction 
 : 
  
 column 
 ; 
 } 
 . 
 hidden 
  
 { 
  
 display 
 : 
  
 none 
 ; 
 } 
 # 
 place-header 
  
 { 
  
 padding 
 : 
  
 15 
 px 
 ; 
  
 background-color 
 : 
  
 #f8f9fa 
 ; 
  
 border-bottom 
 : 
  
 1 
 px 
  
 solid 
  
 #ddd 
 ; 
 } 
 # 
 place-header 
  
 h2 
  
 { 
  
 margin 
 : 
  
 0 
  
 0 
  
 5 
 px 
  
 0 
 ; 
  
 font-size 
 : 
  
 1.2 
 rem 
 ; 
 } 
 # 
 place-address 
  
 { 
  
 margin 
 : 
  
 0 
 ; 
  
 color 
 : 
  
 #555 
 ; 
  
 font-size 
 : 
  
 0.9 
 rem 
 ; 
 } 
 /* Tab Navigation */ 
 . 
 tab-container 
  
 { 
  
 display 
 : 
  
 flex 
 ; 
  
 border-bottom 
 : 
  
 1 
 px 
  
 solid 
  
 #ddd 
 ; 
  
 background-color 
 : 
  
 #fff 
 ; 
 } 
 . 
 tab-button 
  
 { 
  
 flex 
 : 
  
 1 
 ; 
  
 background 
 : 
  
 none 
 ; 
  
 border 
 : 
  
 none 
 ; 
  
 padding 
 : 
  
 10 
 px 
 ; 
  
 cursor 
 : 
  
 pointer 
 ; 
  
 font-weight 
 : 
  
 500 
 ; 
  
 color 
 : 
  
 #555 
 ; 
  
 border-bottom 
 : 
  
 3 
 px 
  
 solid 
  
 transparent 
 ; 
 } 
 . 
 tab-button 
 : 
 hover 
  
 { 
  
 background-color 
 : 
  
 #f1f1f1 
 ; 
 } 
 . 
 tab-button 
 . 
 active 
  
 { 
  
 font-weight 
 : 
  
 bold 
 ; 
  
 border-bottom 
 : 
  
 3 
 px 
  
 solid 
  
 #000000 
 ; 
 } 
 . 
 tab-button 
 . 
 active 
 : 
 hover 
  
 { 
  
 background-color 
 : 
  
 #ffffff 
 ; 
  
 cursor 
 : 
  
 default 
 ; 
 } 
 /* Content Area */ 
 . 
 content-area 
  
 { 
  
 padding 
 : 
  
 15 
 px 
 ; 
  
 line-height 
 : 
  
 1.5 
 ; 
  
 font-size 
 : 
  
 0.95 
 rem 
 ; 
  
 color 
 : 
  
 #333 
 ; 
 } 
 . 
 disclosure-footer 
  
 { 
  
 font-size 
 : 
  
 0.75 
 rem 
 ; 
  
 color 
 : 
  
 #666 
 ; 
  
 padding 
 : 
  
 10 
 px 
  
 15 
 px 
 ; 
  
 border-top 
 : 
  
 1 
 px 
  
 solid 
  
 #eee 
 ; 
  
 font-style 
 : 
  
 italic 
 ; 
 } 
 . 
 flag-content-link 
  
 { 
  
 font-size 
 : 
  
 0.75 
 rem 
 ; 
  
 color 
 : 
  
 #666 
 ; 
  
 padding 
 : 
  
 10 
 px 
  
 15 
 px 
 ; 
 } 
  

HTML

<html>
    <head>
        <title>AI Place Summaries</title>
        <link rel="stylesheet" type="text/css" href="./style.css" />
        <script type="module" src="./index.js"></script>
        <!-- prettier-ignore -->
        <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
        ({key: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "weekly"});</script>
    </head>
    <body>
        <gmp-map center="37.805, -122.425" zoom="14" map-id="DEMO_MAP_ID">
            <!-- Search Input Card -->
            <div
                class="place-autocomplete-card"
                slot="control-inline-start-block-start">
                <p>Search for a place with AI summaries:</p>
                <gmp-place-autocomplete></gmp-place-autocomplete>
            </div>

            <!-- Summary text panel (initially hidden) -->
            <div
                id="summary-panel"
                class="summary-card hidden"
                slot="control-inline-end-block-start">
                <div id="place-header">
                    <h2 id="place-name"></h2>
                    <p id="place-address"></p>
                </div>

                <!-- Tabs for toggling summary types -->
                <div class="tab-container" id="tab-container"></div>

                <!-- Content display area -->
                <div id="summary-content" class="content-area"></div>

                <!-- Legal/AI Disclosure -->
                <div id="ai-disclosure" class="disclosure-footer"></div>

                <!-- Flag content link -->
                <a id="flag-content-link" class="flag-content-link"></a>
            </div>
        </gmp-map>
    </body>
</html>  

Try Sample

Clone Sample

Git and Node.js are required to run this sample locally. Follow these instructions to install Node.js and NPM. The following commands clone, install dependencies and start the sample application.

  
  git 
  
 clone 
  
 https 
 : 
 //github.com/googlemaps-samples/js-api-samples.git 
 
  
  cd 
  
 samples 
 / 
 ai 
 - 
 powered 
 - 
 summaries 
 
  
  npm 
  
 i 
 
  
  npm 
  
 start 
 
Create a Mobile Website
View Site in Mobile | Classic
Share by: