Spaces:
Running
Running
File size: 16,890 Bytes
6fda968 233b170 d0dda9e bf35ece 233b170 c7ab3f4 233b170 c7ab3f4 aeb6ce6 c7ab3f4 fc23f51 aeb6ce6 6c8b7ac bf35ece d0dda9e c7ab3f4 d0dda9e c7ab3f4 d0dda9e bf35ece 78ec24e bf35ece 78ec24e bf35ece 78ec24e c7ab3f4 2f13dbb c4ba210 233b170 c4ba210 78ec24e 6fda968 1c04950 6c8b7ac d0dda9e 233b170 d0dda9e 6be577f d0dda9e c7ab3f4 d0dda9e 6be577f d0dda9e c7ab3f4 d0dda9e c7ab3f4 d0dda9e c7ab3f4 d0dda9e e0deddf 6be577f d0dda9e 6be577f d0dda9e d79cd74 6c8b7ac d79cd74 c7ab3f4 d79cd74 c7ab3f4 d79cd74 c7ab3f4 d79cd74 c7ab3f4 d79cd74 c7ab3f4 d79cd74 c7ab3f4 d79cd74 c7ab3f4 d79cd74 d0dda9e d79cd74 c7ab3f4 d79cd74 c7ab3f4 d79cd74 c7ab3f4 d79cd74 c7ab3f4 d0dda9e d79cd74 c7ab3f4 d0dda9e 6be577f 233b170 78ec24e 233b170 78ec24e 6fda968 78ec24e c7ab3f4 6fda968 233b170 78ec24e 233b170 78ec24e 6be577f 78ec24e bace565 6fda968 d0dda9e 6fda968 d0dda9e 78ec24e 6be577f 1c04950 6be577f 1c04950 6be577f 1c04950 6be577f 1c04950 6be577f 7da3fc3 6be577f 1c04950 6be577f 1c04950 6be577f f22dc3b 1c04950 6be577f 1c04950 6be577f 1c04950 6be577f 1c04950 6be577f 1c04950 6be577f a2d3b74 6be577f 1c04950 6be577f 1c04950 6be577f 5e47334 1c04950 6be577f e0deddf 6be577f 1c04950 6be577f 1c04950 6be577f 1c04950 6c8b7ac 78ec24e 6fda968 78ec24e 1c04950 78ec24e 1c04950 6be577f 8f48959 c7ab3f4 8f48959 c7ab3f4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 |
import streamlit as st
import json
import os
import time
import re
from pathlib import Path
from geo_bot import GeoBot
from benchmark import MapGuesserBenchmark
from config import (
MODELS_CONFIG,
get_data_paths,
SUCCESS_THRESHOLD_KM,
get_model_class,
DEFAULT_MODEL,
DEFAULT_TEMPERATURE,
setup_environment_variables,
)
setup_environment_variables(st.secrets)
def convert_google_to_mapcrunch_url(google_url):
"""Convert Google Maps URL to MapCrunch URL format."""
try:
# Extract coordinates using regex
match = re.search(r"@(-?\d+\.\d+),(-?\d+\.\d+)", google_url)
if not match:
return None
lat, lon = match.groups()
# MapCrunch format: lat_lon_heading_pitch_zoom
# Using default values for heading (317.72), pitch (0.86), and zoom (0)
mapcrunch_url = f"http://www.mapcrunch.com/p/{lat}_{lon}_317.72_0.86_0"
return mapcrunch_url
except Exception as e:
st.error(f"Error converting URL: {str(e)}")
return None
def get_available_datasets():
datasets_dir = Path("datasets")
if not datasets_dir.exists():
return ["default"]
datasets = []
for dataset_dir in datasets_dir.iterdir():
if dataset_dir.is_dir():
data_paths = get_data_paths(dataset_dir.name)
if os.path.exists(data_paths["golden_labels"]):
datasets.append(dataset_dir.name)
return datasets if datasets else ["default"]
# UI Setup
st.set_page_config(
page_title="π§ Omniscient - Multiturn Geographic Intelligence", layout="wide"
)
st.title("π§ Omniscient")
st.markdown("""
### *An all-seeing AI agent for geographic analysis and deduction*
Omniscient engages in a multi-turn reasoning process β collecting visual clues, asking intelligent questions, and narrowing down locations step by step.
Whether it's identifying terrain, interpreting signs, or tracing road patterns, this AI agent learns, adapts, and solves like a true geo-detective.
""")
# Sidebar
with st.sidebar:
st.header("Configuration")
# Mode selection
mode = st.radio("Mode", ["Dataset Mode", "Online Mode"], index=0)
if mode == "Dataset Mode":
# Get available datasets and ensure we have a valid default
available_datasets = get_available_datasets()
default_dataset = available_datasets[0] if available_datasets else "default"
dataset_choice = st.selectbox("Dataset", available_datasets, index=0)
model_choice = st.selectbox(
"Model",
list(MODELS_CONFIG.keys()),
index=list(MODELS_CONFIG.keys()).index(DEFAULT_MODEL),
)
steps_per_sample = st.slider("Max Steps", 1, 20, 10)
temperature = st.slider(
"Temperature",
0.0,
2.0,
DEFAULT_TEMPERATURE,
0.1,
help="Controls randomness in AI responses. 0.0 = deterministic, higher = more creative",
)
# Load dataset with error handling
data_paths = get_data_paths(dataset_choice)
try:
with open(data_paths["golden_labels"], "r") as f:
golden_labels = json.load(f).get("samples", [])
st.info(f"Dataset '{dataset_choice}' has {len(golden_labels)} samples")
if len(golden_labels) == 0:
st.error(f"Dataset '{dataset_choice}' contains no samples!")
st.stop()
except FileNotFoundError:
st.error(
f"β Dataset '{dataset_choice}' not found at {data_paths['golden_labels']}"
)
st.info("π‘ Available datasets: " + ", ".join(available_datasets))
st.stop()
except Exception as e:
st.error(f"β Error loading dataset '{dataset_choice}': {str(e)}")
st.stop()
num_samples = st.slider(
"Samples to Test", 1, len(golden_labels), min(3, len(golden_labels))
)
else: # Online Mode
st.info("Enter a URL to analyze a specific location")
# Add example URLs
example_google_url = "https://www.google.com/maps/@37.8728123,-122.2445339,3a,75y,3.36h,90t/data=!3m7!1e1!3m5!1s4DTABKOpCL6hdNRgnAHTgw!2e0!6shttps:%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fcb_client%3Dmaps_sv.tactile%26w%3D900%26h%3D600%26pitch%3D0%26panoid%3D4DTABKOpCL6hdNRgnAHTgw%26yaw%3D3.3576431!7i13312!8i6656?entry=ttu"
example_mapcrunch_url = (
"http://www.mapcrunch.com/p/37.882284_-122.269626_293.91_-6.63_0"
)
# Create tabs for different URL types
input_tab1, input_tab2 = st.tabs(["Google Maps URL", "MapCrunch URL"])
google_url = ""
mapcrunch_url = ""
golden_labels = None
num_samples = None
with input_tab1:
url_col1, url_col2 = st.columns([3, 1])
with url_col1:
google_url = st.text_input(
"Google Maps URL",
placeholder="https://www.google.com/maps/@37.5851338,-122.1519467,9z?entry=ttu",
key="google_maps_url",
)
st.markdown(
f"π‘ **Example Location:** [View in Google Maps]({example_google_url})"
)
if google_url:
mapcrunch_url_converted = convert_google_to_mapcrunch_url(google_url)
if mapcrunch_url_converted:
st.success(f"Converted to MapCrunch URL: {mapcrunch_url_converted}")
try:
match = re.search(r"@(-?\d+\.\d+),(-?\d+\.\d+)", google_url)
if not match:
st.error("Invalid Google Maps URL format")
st.stop()
lat, lon = match.groups()
golden_labels = [
{
"id": "online",
"lat": float(lat),
"lng": float(lon),
"url": mapcrunch_url_converted,
}
]
num_samples = 1
except Exception as e:
st.error(f"Invalid Google Maps URL format: {str(e)}")
else:
st.error("Invalid Google Maps URL format")
with input_tab2:
st.markdown("π‘ **Example Location:**")
st.markdown(f"[View in MapCrunch]({example_mapcrunch_url})")
st.code(example_mapcrunch_url, language="text")
mapcrunch_url = st.text_input(
"MapCrunch URL", placeholder=example_mapcrunch_url, key="mapcrunch_url"
)
if mapcrunch_url:
try:
coords = mapcrunch_url.split("/")[-1].split("_")
lat, lon = float(coords[0]), float(coords[1])
golden_labels = [
{"id": "online", "lat": lat, "lng": lon, "url": mapcrunch_url}
]
num_samples = 1
except Exception as e:
st.error(f"Invalid MapCrunch URL format: {str(e)}")
# Only stop if neither input is provided
if not google_url and not mapcrunch_url:
st.warning(
"Please enter a Google Maps URL or MapCrunch URL, or use the example above."
)
st.stop()
if golden_labels is None or num_samples is None:
st.warning("Please enter a valid URL.")
st.stop()
model_choice = st.selectbox(
"Model",
list(MODELS_CONFIG.keys()),
index=list(MODELS_CONFIG.keys()).index(DEFAULT_MODEL),
)
steps_per_sample = st.slider("Max Steps", 1, 20, 10)
temperature = st.slider(
"Temperature",
0.0,
2.0,
DEFAULT_TEMPERATURE,
0.1,
help="Controls randomness in AI responses. 0.0 = deterministic, higher = more creative",
)
start_button = st.button("π Start", type="primary")
# Main Logic
if start_button:
test_samples = golden_labels[:num_samples]
config = MODELS_CONFIG[model_choice]
model_class = get_model_class(config["class"])
benchmark_helper = MapGuesserBenchmark(
dataset_name=dataset_choice if mode == "Dataset Mode" else "online"
)
all_results = []
progress_bar = st.progress(0)
with GeoBot(
model=model_class,
model_name=config["model_name"],
headless=True,
temperature=temperature,
) as bot:
for i, sample in enumerate(test_samples):
st.divider()
st.header(f"Sample {i + 1}/{num_samples}")
if mode == "Online Mode":
# Load the MapCrunch URL directly
bot.controller.load_url(sample["url"])
else:
# Load from dataset as before
bot.controller.load_location_from_data(sample)
bot.controller.setup_clean_environment()
# Create containers for UI updates
sample_container = st.container()
# Initialize UI state for this sample
step_containers = {}
sample_steps_data = []
def ui_step_callback(step_info):
"""Callback function to update UI after each step"""
step_num = step_info["step_num"]
# Store step data
sample_steps_data.append(step_info)
with sample_container:
# Create step container if it doesn't exist
if step_num not in step_containers:
step_containers[step_num] = st.container()
with step_containers[step_num]:
st.subheader(f"Step {step_num}/{step_info['max_steps']}")
col1, col2 = st.columns([1, 2])
with col1:
# Display screenshot
st.image(
step_info["screenshot_bytes"],
caption=f"What AI sees - Step {step_num}",
use_column_width=True,
)
with col2:
# Show available actions
st.write("**Available Actions:**")
st.code(
json.dumps(step_info["available_actions"], indent=2)
)
# Show history context - use the history from step_info
current_history = step_info.get("history", [])
history_text = bot.generate_history_text(current_history)
st.write("**AI Context:**")
st.text_area(
"History",
history_text,
height=100,
disabled=True,
key=f"history_{i}_{step_num}",
)
# Show AI reasoning and action
action = step_info.get("action_details", {}).get(
"action", "N/A"
)
if step_info.get("is_final_step") and action != "GUESS":
st.warning("Max steps reached. Forcing GUESS.")
st.write("**AI Reasoning:**")
st.info(step_info.get("reasoning", "N/A"))
st.write("**AI Action:**")
if action == "GUESS":
lat = step_info.get("action_details", {}).get("lat")
lon = step_info.get("action_details", {}).get("lon")
st.success(f"`{action}` - {lat:.4f}, {lon:.4f}")
else:
st.success(f"`{action}`")
# Show decision details for debugging
with st.expander("Decision Details"):
decision_data = {
"reasoning": step_info.get("reasoning"),
"action_details": step_info.get("action_details"),
"remaining_steps": step_info.get("remaining_steps"),
}
st.json(decision_data)
# Force UI refresh
time.sleep(0.5) # Small delay to ensure UI updates are visible
# Run the agent loop with UI callback
try:
final_guess = bot.run_agent_loop(
max_steps=steps_per_sample, step_callback=ui_step_callback
)
except Exception as e:
st.error(f"Error during agent execution: {e}")
final_guess = None
# Sample Results
with sample_container:
st.subheader("Sample Result")
true_coords = {"lat": sample.get("lat"), "lng": sample.get("lng")}
distance_km = None
is_success = False
if final_guess:
distance_km = benchmark_helper.calculate_distance(
true_coords, final_guess
)
if distance_km is not None:
is_success = distance_km <= SUCCESS_THRESHOLD_KM
col1, col2, col3 = st.columns(3)
col1.metric(
"Final Guess", f"{final_guess[0]:.3f}, {final_guess[1]:.3f}"
)
col2.metric(
"Ground Truth",
f"{true_coords['lat']:.3f}, {true_coords['lng']:.3f}",
)
col3.metric(
"Distance",
f"{distance_km:.1f} km",
delta="Success" if is_success else "Failed",
)
else:
st.error("No final guess made")
all_results.append(
{
"sample_id": sample.get("id"),
"model": model_choice,
"steps_taken": len(sample_steps_data),
"max_steps": steps_per_sample,
"temperature": temperature,
"true_coordinates": true_coords,
"predicted_coordinates": final_guess,
"distance_km": distance_km,
"success": is_success,
}
)
progress_bar.progress((i + 1) / num_samples)
# Final Summary
st.divider()
st.header("π Final Results")
# Calculate summary stats
successes = [r for r in all_results if r["success"]]
success_rate = len(successes) / len(all_results) if all_results else 0
valid_distances = [
r["distance_km"] for r in all_results if r["distance_km"] is not None
]
avg_distance = sum(valid_distances) / len(valid_distances) if valid_distances else 0
# Overall metrics
col1, col2, col3 = st.columns(3)
col1.metric("Success Rate", f"{success_rate * 100:.1f}%")
col2.metric("Average Distance", f"{avg_distance:.1f} km")
col3.metric("Total Samples", len(all_results))
# Detailed results table
st.subheader("Detailed Results")
st.dataframe(all_results, use_container_width=True)
# Success/failure breakdown
if successes:
st.subheader("β
Successful Samples")
st.dataframe(successes, use_container_width=True)
failures = [r for r in all_results if not r["success"]]
if failures:
st.subheader("β Failed Samples")
st.dataframe(failures, use_container_width=True)
# Export functionality
if st.button("πΎ Export Results"):
results_json = json.dumps(all_results, indent=2)
st.download_button(
label="Download results.json",
data=results_json,
file_name=f"geo_results_{dataset_choice}_{model_choice}_{num_samples}samples.json",
mime="application/json",
)
def handle_tab_completion():
"""Handle tab completion for the Google Maps URL input."""
if st.session_state.google_maps_url == "":
st.session_state.google_maps_url = (
"https://www.google.com/maps/@37.5851338,-122.1519467,9z?entry=ttu"
)
|