HipFil98 commited on
Commit
b2635fe
·
1 Parent(s): 5c7da15

feat: location dropdown with interactive Leaflet map (Nominatim reverse geocoding)

Browse files
Files changed (2) hide show
  1. agent/job_searcher.py +3 -0
  2. app.py +81 -3
agent/job_searcher.py CHANGED
@@ -314,6 +314,9 @@ class JobSearcher:
314
  Deduplicated list of JobListing dicts, sorted with richer entries first.
315
  """
316
  pt = position_type.lower() if position_type else "any"
 
 
 
317
  all_listings: list[dict] = []
318
 
319
  # jobs.ac.uk is UK-only: only query it when location is UK or worldwide
 
314
  Deduplicated list of JobListing dicts, sorted with richer entries first.
315
  """
316
  pt = position_type.lower() if position_type else "any"
317
+ # Normalize UI display labels to query-friendly strings
318
+ _normalize = {"Europe (all)": "Europe", "Worldwide": ""}
319
+ location = _normalize.get(location, location)
320
  all_listings: list[dict] = []
321
 
322
  # jobs.ac.uk is UK-only: only query it when location is UK or worldwide
app.py CHANGED
@@ -371,6 +371,80 @@ MODELS = [
371
  "google/gemma-2-9b-it",
372
  ]
373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  # If a shared token is configured via Space Secrets, users don't need to provide one.
375
  _SHARED_TOKEN = os.environ.get("HF_TOKEN", "")
376
 
@@ -410,11 +484,15 @@ with gr.Blocks(
410
  label="Research field",
411
  placeholder="e.g. machine learning, computational neuroscience, molecular biology",
412
  )
413
- location_input = gr.Textbox(
414
  label="Location preference",
415
- placeholder="e.g. Europe, Germany, UK",
416
- value="Europe",
 
 
 
417
  )
 
418
  with gr.Row():
419
  pos_type = gr.Dropdown(
420
  label="Position type",
 
371
  "google/gemma-2-9b-it",
372
  ]
373
 
374
+ LOCATIONS = [
375
+ "Worldwide",
376
+ "Europe (all)",
377
+ # Western Europe
378
+ "UK", "Germany", "France", "Italy", "Spain", "Netherlands",
379
+ "Switzerland", "Belgium", "Austria", "Portugal", "Ireland",
380
+ # Nordic
381
+ "Sweden", "Denmark", "Finland", "Norway",
382
+ # Central / Eastern Europe
383
+ "Poland", "Czech Republic", "Hungary", "Romania", "Greece",
384
+ "Croatia", "Slovakia", "Slovenia", "Bulgaria", "Estonia",
385
+ "Latvia", "Lithuania", "Luxembourg", "Serbia", "Turkey",
386
+ # Americas
387
+ "United States", "Canada", "Brazil",
388
+ # Asia-Pacific
389
+ "Australia", "Japan", "South Korea", "China", "Singapore",
390
+ "India", "New Zealand",
391
+ # Other
392
+ "South Africa", "Israel",
393
+ ]
394
+
395
+ _MAP_HTML = """
396
+ <div style="margin-top:6px;">
397
+ <p style="font-size:0.8em;color:#666;margin-bottom:4px;">
398
+ 🗺 Click on the map to select a country, or use the dropdown above.
399
+ </p>
400
+ <div id="job-map" style="height:280px;border-radius:8px;border:1px solid #ddd;"></div>
401
+ </div>
402
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
403
+ <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
404
+ <script>
405
+ (function initMap() {
406
+ if (window._jobMap) return;
407
+ const el = document.getElementById('job-map');
408
+ if (!el) { setTimeout(initMap, 300); return; }
409
+
410
+ var map = L.map('job-map', {zoomControl:true}).setView([30, 10], 2);
411
+ window._jobMap = map;
412
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
413
+ attribution: '© <a href="https://openstreetmap.org">OpenStreetMap</a>',
414
+ maxZoom: 18
415
+ }).addTo(map);
416
+
417
+ var marker = null;
418
+
419
+ map.on('click', function(e) {
420
+ var lat = e.latlng.lat, lon = e.latlng.lng;
421
+ if (marker) map.removeLayer(marker);
422
+ marker = L.marker([lat, lon]).addTo(map).bindPopup('Loading…').openPopup();
423
+
424
+ fetch('https://nominatim.openstreetmap.org/reverse?lat=' + lat + '&lon=' + lon + '&format=json')
425
+ .then(function(r){ return r.json(); })
426
+ .then(function(data) {
427
+ var country = (data.address && data.address.country) || '';
428
+ if (!country) return;
429
+ marker.setPopupContent(country);
430
+
431
+ // Update Gradio dropdown (elem_id="location-dd")
432
+ var container = document.getElementById('location-dd');
433
+ if (!container) return;
434
+ var input = container.querySelector('input');
435
+ if (!input) return;
436
+ var setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
437
+ setter.call(input, country);
438
+ input.dispatchEvent(new Event('input', {bubbles:true}));
439
+ input.dispatchEvent(new Event('change', {bubbles:true}));
440
+ input.dispatchEvent(new Event('blur', {bubbles:true}));
441
+ })
442
+ .catch(function(){});
443
+ });
444
+ })();
445
+ </script>
446
+ """
447
+
448
  # If a shared token is configured via Space Secrets, users don't need to provide one.
449
  _SHARED_TOKEN = os.environ.get("HF_TOKEN", "")
450
 
 
484
  label="Research field",
485
  placeholder="e.g. machine learning, computational neuroscience, molecular biology",
486
  )
487
+ location_input = gr.Dropdown(
488
  label="Location preference",
489
+ choices=LOCATIONS,
490
+ value="Europe (all)",
491
+ allow_custom_value=True,
492
+ elem_id="location-dd",
493
+ info="Select from the list, type a custom location, or click the map below",
494
  )
495
+ gr.HTML(_MAP_HTML)
496
  with gr.Row():
497
  pos_type = gr.Dropdown(
498
  label="Position type",