Conversation
Owner
Author
|
@greptileai , comprehensively update your review based on the changes above : |
Owner
Author
|
@greptileai , comprehensively update your assessment , review out of diff review , and comments based on the changes above: |
Owner
Author
|
@greptileai , comprehensively update your assessment , review out of diff review , and comments based on the changes above: |
Owner
Author
|
@greptileai , comprehensively update your assessment , review out of diff review , and comments based on the changes above: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Greptile Summary
This PR adds two major capabilities: Google Maps and OpenStreetMap (Leaflet) dual-provider map support in the React frontend, and Gemini as a new LLM provider throughout the Python backend. It also introduces the
/gis/emergency-eventsendpoint, a unifiedOperatorMapcomponent with provider-switching, and supporting utilities (escapeHtml,googleMapsLoader,mapSourceConfig).A significant number of issues raised in previous review rounds have been addressed in this PR:
googleMapsLoader.tsnow clears the cached promise on failure (allowing retries)OperatorMapLeafletusesuseEffectinChangeViewinstead ofuseMemofor side effectsDOMPurifyis applied to alldangerouslySetInnerHTMLin Leaflet popupsvite-env.d.tsnow declares all VITE_ env vars consumed by the map config modulesincequery param on/gis/emergency-eventsis typeddatetime | Noneso FastAPI validates format before the DB is touchedescapeHtmlutilityKey issues found in this round:
OperatorMap.tsx(logic): Thecolorfield onOperatorMapMarkeris set byemergencyToMarker(red/green per status) but is never applied in thegoogle.maps.Markerconstructor, making emergency pins visually identical to operator pins on the Google Maps provider.factory.py(logic): The Gemini model-prefix guard uses"gemini/" in model(substring) rather thanmodel.startswith("gemini/"), which is the pattern used consistently for every other provider in the same function and which avoids false positives on model strings that happen to contain"gemini/"mid-string.OperatorMap.tsx(style):clearMarkersis referenced in the inituseEffectcleanup but missing from its empty dependency array; stable reference so no runtime impact, but will triggerreact-hooks/exhaustive-depslint warnings.Confidence Score: 3/5
colorfield omission and substring prefix check are both small, targeted corrections before merge.useMemoside effect, CDN icons, missingvite-envdeclarations). Two new logic issues were found: emergency marker colour is not applied in the Google Maps path (functionality regression for the emergency overlay), and the Gemini model-prefix guard uses a substring check instead ofstartsWith. Neither is a security issue, but the colour omission means the emergency-event layer is not usable as intended on Google Maps until fixed.radioshaq/web-interface/src/components/maps/OperatorMap.tsx(emergency marker colour) andradioshaq/radioshaq/orchestrator/factory.py(Gemini model prefix guard).Important Files Changed
colorfield declared onOperatorMapMarkerand set on emergency markers but silently ignored when constructinggoogle.maps.Marker— emergency events are visually indistinguishable from operator markers on the Google provider. InituseEffect([], [])referencesclearMarkersin cleanup but omits it from the dependency array (stable ref, not a runtime bug but triggers exhaustive-deps lint)."gemini/" in model(substring match) instead ofmodel.startswith("gemini/"), so a model name like"pre-gemini/v1"would incorrectly bypass the prefix addition._resolve_api_key()with provider-matched env lookup before the generic fallback chain; addresses prior cross-provider contamination at the provider-key level. Gemini is properly wired in the provider map.get_emergency_events_with_locationsusing a parameterisedtext()query with PostGIS geometry extraction; no SQL injection risk. Also addsidandlast_seen_atto the operators-nearby response for stable marker keys./gis/emergency-eventsendpoint;sinceis now typed as `datetimeloadPromiseon failure (addresses prior permanent-cache-on-rejection bug) and exposesisGoogleMapsConfigured()helper.ChangeViewusesuseEffect(notuseMemo), popups sanitized withDOMPurify.&,<,>,",'; used consistently across all infoHtml template literals in new map components.Sequence Diagram
sequenceDiagram participant UI as React UI (MapPage) participant OM as OperatorMap participant GL as googleMapsLoader participant OML as OperatorMapLeaflet participant API as FastAPI Backend participant DB as PostGIS DB UI->>API: GET /gis/operators-nearby?lat&lng&radius API->>DB: get_operators_nearby() DB-->>API: [{ id, callsign, lat, lon, last_seen_at }] API-->>UI: { operators, count } UI->>API: GET /gis/emergency-events?since&status&limit API->>DB: get_emergency_events_with_locations() DB-->>API: [{ id, latitude, longitude, status, ... }] API-->>UI: { events, count } UI->>OM: <OperatorMap markers=[ops+emergency] /> alt provider = 'google' OM->>GL: loadGoogleMaps() GL-->>OM: google namespace OM->>OM: new google.maps.Marker (color ignored ⚠️) else provider = 'osm' OM->>OML: <OperatorMapLeaflet markers /> OML->>OML: DOMPurify.sanitize(infoHtml) OML-->>UI: Leaflet map with markers end note over UI,API: LLM config flow UI->>API: PATCH /api/v1/config/llm { provider: "gemini", gemini_api_key } API->>API: factory._llm_model_string → "gemini/gemini-2.5-flash" API->>API: _llm_api_key_from_llm_config → gemini_api_key API->>API: LLMClient._resolve_api_key() → GEMINI_API_KEY envComments Outside Diff (4)
radioshaq/radioshaq/llm/client.py, line 78-86 (link)API key fallback chain may send wrong key to provider
The API key resolution chain (
MISTRAL_API_KEY or OPENAI_API_KEY or ANTHROPIC_API_KEY or HF_TOKEN or ... or GEMINI_API_KEY) means if a user has bothMISTRAL_API_KEYandGEMINI_API_KEYset, and the model isgemini/..., the Mistral key will be used for the Gemini API call because it appears first. The factory (_llm_api_key_from_llm_config) correctly matches by provider, but theLLMClientfallback does not. This could cause confusing authentication errors. Consider making theLLMClientenv-var chain aware of the model prefix, or documenting thatapi_keyshould always be passed explicitly (which the factory does).radioshaq/web-interface/src/features/map/MapPage.tsx, line 682-685 (link)centermissing fromuseEffectdependency arrayfetchNearbyis called withcenter.latandcenter.lngfrom state, but neither is listed as a dependency.react-hooks/exhaustive-depswill warn on this, and it means the effect won't re-run ifcenterchanges independently ofradiusKm. While the current control flow (center updates only come from insidefetchNearby) happens to work, the stale-closure risk makes this fragile.radioshaq/web-interface/src/maps/googleMapsLoader.ts, line 39-51 (link)Cached rejected promise prevents map recovery
loadPromiseis assigned once and never reset on failure. IfimportLibrary('maps')rejects — due to a transient network error, quota limit, or misconfiguration — the cached rejected promise is returned to every subsequent caller. The map becomes permanently broken for the session until the user reloads the page.The fix is to clear
loadPromiseinside the.catch()handler (set it back tonull) before re-throwing the error, so the nextloadGoogleMaps()call can make a fresh attempt rather than immediately returning the same rejected promise.radioshaq/radioshaq/llm/client.py, line 75-91 (link)Gemini env key shadowed by earlier fallback entries
GEMINI_API_KEYis the last entry in theor-chain. In any environment whereMISTRAL_API_KEY,OPENAI_API_KEY, orANTHROPIC_API_KEYis also present — which is common when running multiple providers — a Gemini call will silently receive the wrong key, causing an authentication error that is hard to diagnose.The existing code comment already flags this: "Consider making this model-aware". A minimal fix would be to select the env var based on the configured provider (matching the existing pattern in
factory.py—_llm_api_key_from_llm_config) rather than trying all keys in a fixed order. The fallback chain would only be used as a last resort for unknown providers.The same issue exists in the
chat_with_toolsmethod further down in this file.Last reviewed commit: cdddb2b