Skip to main content

Store Finder

Store finder with Geo location services, Python code sorts the JSON neareast to the users location.

Store Carousel

Integration Properties

Integration selecting the Data Source, description, Carousel, and Python

Note that we selected Python, using a Carousel

Geo Settings

Main settings of integration


LLM Location Parameter Location

A string representing location to look for property This can be a full address (e.g., "123 Main St, Boston, MA") or geographic coordinates in latitude and longitude format (e.g., "42.3601,-71.0589"). Used to determine proximity or directions.

LLM parameter location


Handlebars List Template

<div class="atlas-store-list grid grid-cols-1 gap-6">
{{#each items}}
<div
class="atlas-store-card bg-white rounded-2xl shadow-lg border border-gray-200 flex flex-col md:flex-row overflow-hidden mb-4 transition hover:shadow-2xl hover:-translate-y-1 duration-200"
data-lat="{{latitude}}"
data-lng="{{longitude}}"
>
<div class="w-full md:w-1/3 flex-shrink-0 bg-gray-100">
<img
src="{{image}}"
alt="{{title}}"
class="atlas-store-img w-full h-40 object-cover md:h-full"
loading="lazy"
/>
</div>
<div class="flex-1 p-4 flex flex-col justify-between">
<div>
<h2 class="text-2xl font-bold text-sky-800 mb-1 flex items-center">
{{title}}
<span
class="inline-block ml-2 rounded-full bg-gradient-to-br from-sky-300 to-blue-200 px-3 py-1 text-xs font-semibold text-sky-900 shadow"
>Open</span
>
</h2>
<p class="text-gray-700 text-sm mb-2 font-medium">
{{short_description}}
</p>
<div class="flex flex-col gap-1 mb-3">
<span class="font-semibold text-gray-600 flex items-center">
<svg
class="inline mr-2 w-4 h-4 text-sky-700"
fill="none"
stroke="currentColor"
stroke-width="2"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.657 16.657L13.414 12.414a2 2 0 010-2.828l4.243-4.243m-5.657 5.657a4 4 0 110-5.657 4 4 0 010 5.657z"
/>
</svg>
{{address}}
</span>
<span class="text-xs text-sky-700 flex items-center">
<svg
class="inline mr-1 w-4 h-4 text-sky-600"
fill="none"
stroke="currentColor"
stroke-width="2"
viewBox="0 0 24 24"
>
<circle cx="12" cy="12" r="10" />
<path d="M12 6v6l4 2" />
</svg>
{{hours}} - Distance {{distance_km}} km
</span>
</div>
<div class="flex flex-wrap gap-2 mb-2">
{{#each services}}
<span
class="atlas-badge bg-gradient-to-br from-blue-100 to-sky-100 text-sky-900 text-xs font-semibold px-3 py-1 rounded-xl border border-sky-200 shadow-sm"
>{{this}}</span
>
{{/each}}
</div>
</div>
<div
class="flex flex-row flex-wrap items-center mt-3 gap-2 overflow-visible"
>
<button
class="atlas-copy-btn text-sky-600 border border-sky-200 rounded px-3 py-2 text-xs hover:bg-sky-50 transition flex-shrink-0 min-w-[100px]"
onclick="ai12zBot.sendMessage('Google Map from your location to destination {{address}}','Google Map from your location to destination {{address}}')"
type="button"
>
Google Map
</button>
</div>
</div>
</div>
{{/each}}
</div>

Python

Python code for GEO ordering

def find_nearest_stores(address, source, max_distance_km=10000):
try:
stores = source["items"]
geolocator = Nominatim(user_agent="ai12z")
try:
location = geolocator.geocode(address, timeout=10)
except Exception as geo_err:
raise Exception(f"Network error during geocode: {geo_err}")
if not location:
raise Exception(f"Could not geocode address: {address}")
user_coords = (location.latitude, location.longitude)
log_message(f"User address geocoded to: {user_coords}")

result = []
for store in stores:
store_coords = (store['latitude'], store['longitude'])
distance_km = geodesic(user_coords, store_coords).kilometers
if distance_km <= max_distance_km:
store_with_distance = {}
for k in store:
store_with_distance[k] = store[k]
store_with_distance['distance_km'] = round(distance_km, 2)
result.append(store_with_distance)
# log_message(f"Store {store['title']} is {store_with_distance['distance_km']} km away.")

result.sort(key=lambda x: x['distance_km'])
items = result
source["items"] = items
return {"success": True, "source": source}
except Exception as e:
log_message(f"ERROR in find_nearest_stores: {str(e)}")
return {"success": False, "error": str(e)}

# Main entry point: expects llm["location"] and source["items"]
address = llm.get("location", "Boston, MA")
result = find_nearest_stores(address, source)

Javascript for a slider in the AI Handlebar

ai12zBot.addEventListener("messageSent", (event) => {
const userMessage = String(event.detail).toLowerCase()
if (userMessage.includes("current location")) {
if (!navigator.geolocation) {
alert("Geolocation is not supported by your browser.")
return
}
navigator.geolocation.getCurrentPosition(
handleLocationSuccess,
handleLocationError
)
}
})

if (typeof ai12zBot.lastLocationSendTime === "undefined") {
ai12zBot.lastLocationSendTime = 0
}

function handleLocationSuccess(position) {
const now = Date.now()
// 20 seconds = 20000 ms
if (now - ai12zBot.lastLocationSendTime < 20000) return

// Set time immediately to block re-entry
ai12zBot.lastLocationSendTime = now

const { latitude, longitude } = position.coords
setTimeout(() => {
ai12zBot.sendJSON(
{ latitude: latitude, longitude: longitude },
"Using location data"
)
// Optionally: update the time here if you want the block to begin AFTER send (uncommon)
// ai12zBot.lastLocationSendTime = Date.now();
}, 5000)
}

function handleLocationError(err) {
const messages = {
1: "Location access was denied. Unable to retrieve your location.",
2: "Location information is unavailable. Please try again later.",
3: "Location request timed out. Please try again.",
}
alert(
messages[err.code] ||
"Unable to retrieve location. Please check your settings."
)
}

Style for a slider in the AI Handlebar

.atlas-store-list {
padding: 1rem 0;
}
.atlas-store-card {
box-shadow:
0 2px 10px 0 rgba(34, 71, 107, 0.05),
0 1.5px 6px 0 rgba(36, 63, 106, 0.07);
transition:
box-shadow 0.2s,
transform 0.2s;
}
.atlas-store-card:hover {
box-shadow:
0 8px 32px rgba(36, 71, 107, 0.2),
0 2.5px 12px rgba(36, 63, 106, 0.11);
transform: translateY(-3px) scale(1.01);
}
.atlas-store-img {
border-radius: 0 0 0 1rem;
}
.atlas-badge {
background: linear-gradient(90deg, #f0f6ff 0%, #d2ebff 100%);
}
.atlas-directions-btn {
cursor: pointer;
}
.atlas-copy-btn {
cursor: pointer;
}

JSON

{
"items": [
{
"address": "1200 18th St NW, Washington, DC 20036",
"hours": "Mon-Sat 10am-7pm, Sun 12pm-5pm",
"image": "https://cdn.ai12z.net/assets/web/atlas-dc.jpg",
"latitude": 38.9065,
"longitude": -77.0427,
"services": ["Fitting", "Tuning", "Rentals"],
"short_description": "DC’s best source for ski, snowboard, and winter sports gear.",
"title": "Atlas Washington DC"
},
{
"address": "1201 S Hayes St, Arlington, VA 22202",
"hours": "Mon-Fri 10am-8pm, Sat 10am-7pm, Sun 12pm-5pm",
"image": "https://cdn.ai12z.net/assets/web/atlas-arlington.jpg",
"latitude": 38.8621,
"longitude": -77.0596,
"services": ["Fitting", "Tuning", "Boot Customization"],
"short_description": "Northern Virginia’s premier ski and snowboard outfitter.",
"title": "Atlas Arlington VA"
}
]
}