Spaces:
Sleeping
Sleeping
Commit
·
5a4439e
1
Parent(s):
1c2899b
Updated Google maps API key
Browse files
backend/app/config/settings.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
# backend/app/config/settings.py
|
| 2 |
from __future__ import annotations
|
| 3 |
from pathlib import Path
|
| 4 |
-
from pydantic import Field
|
| 5 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 6 |
import os, tempfile
|
| 7 |
|
|
@@ -41,7 +41,6 @@ def _default_data_dir() -> Path:
|
|
| 41 |
w = _try_writable(p)
|
| 42 |
if w:
|
| 43 |
return w
|
| 44 |
-
# As a last resort, raise a clear error
|
| 45 |
raise RuntimeError(f"No writable DATA_DIR found. Tried: {candidates!r}")
|
| 46 |
|
| 47 |
def _default_uploads_dir() -> Path:
|
|
@@ -63,6 +62,16 @@ class Settings(BaseSettings):
|
|
| 63 |
OPENAI_MODEL_AGENT: str = "gpt-4o"
|
| 64 |
OPENAI_MODEL_CLASSIFIER: str = "gpt-4o-mini"
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
# Paths
|
| 67 |
DATA_DIR: Path = Field(default_factory=_default_data_dir)
|
| 68 |
REPORTS_DB: Path | None = None
|
|
@@ -75,7 +84,7 @@ class Settings(BaseSettings):
|
|
| 75 |
DEFAULT_LIMIT: int = 10
|
| 76 |
MAX_AGE_HOURS: int = 48
|
| 77 |
|
| 78 |
-
# Optional extras
|
| 79 |
firms_map_key: str | None = None
|
| 80 |
gdacs_rss_url: str | None = "https://www.gdacs.org/xml/rss.xml"
|
| 81 |
nvidia_api_key: str | None = None
|
|
|
|
| 1 |
# backend/app/config/settings.py
|
| 2 |
from __future__ import annotations
|
| 3 |
from pathlib import Path
|
| 4 |
+
from pydantic import Field, AliasChoices
|
| 5 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 6 |
import os, tempfile
|
| 7 |
|
|
|
|
| 41 |
w = _try_writable(p)
|
| 42 |
if w:
|
| 43 |
return w
|
|
|
|
| 44 |
raise RuntimeError(f"No writable DATA_DIR found. Tried: {candidates!r}")
|
| 45 |
|
| 46 |
def _default_uploads_dir() -> Path:
|
|
|
|
| 62 |
OPENAI_MODEL_AGENT: str = "gpt-4o"
|
| 63 |
OPENAI_MODEL_CLASSIFIER: str = "gpt-4o-mini"
|
| 64 |
|
| 65 |
+
# ✅ Google Maps (read from multiple env names for safety)
|
| 66 |
+
google_maps_api_key: str | None = Field(
|
| 67 |
+
default=None,
|
| 68 |
+
validation_alias=AliasChoices("VITE_GOOGLE_MAPS_API_KEY", "GOOGLE_MAPS_API_KEY"),
|
| 69 |
+
)
|
| 70 |
+
google_maps_map_id: str | None = Field(
|
| 71 |
+
default=None,
|
| 72 |
+
validation_alias=AliasChoices("VITE_GOOGLE_MAPS_MAP_ID", "VITE_GOOGLE_MAPS_MAP_IDY", "GOOGLE_MAPS_MAP_ID"),
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
# Paths
|
| 76 |
DATA_DIR: Path = Field(default_factory=_default_data_dir)
|
| 77 |
REPORTS_DB: Path | None = None
|
|
|
|
| 84 |
DEFAULT_LIMIT: int = 10
|
| 85 |
MAX_AGE_HOURS: int = 48
|
| 86 |
|
| 87 |
+
# Optional extras
|
| 88 |
firms_map_key: str | None = None
|
| 89 |
gdacs_rss_url: str | None = "https://www.gdacs.org/xml/rss.xml"
|
| 90 |
nvidia_api_key: str | None = None
|
backend/app/routers/config.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
from fastapi import APIRouter
|
| 2 |
from ..config.settings import settings
|
| 3 |
|
|
@@ -6,5 +7,7 @@ router = APIRouter(prefix="/config", tags=["config"])
|
|
| 6 |
@router.get("/runtime")
|
| 7 |
def runtime_config():
|
| 8 |
# Only the fields you need in the client
|
| 9 |
-
return {
|
| 10 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
# backend/app/routers/config.py
|
| 2 |
from fastapi import APIRouter
|
| 3 |
from ..config.settings import settings
|
| 4 |
|
|
|
|
| 7 |
@router.get("/runtime")
|
| 8 |
def runtime_config():
|
| 9 |
# Only the fields you need in the client
|
| 10 |
+
return {
|
| 11 |
+
"VITE_GOOGLE_MAPS_API_KEY": settings.google_maps_api_key,
|
| 12 |
+
"VITE_GOOGLE_MAPS_MAP_ID": settings.google_maps_map_id,
|
| 13 |
+
}
|
web/src/components/map/MapCanvas.tsx
CHANGED
|
@@ -1,10 +1,6 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
AdvancedMarker,
|
| 5 |
-
// useMap,
|
| 6 |
-
} from "@vis.gl/react-google-maps";
|
| 7 |
-
// import { useEffect } from "react";
|
| 8 |
import SearchControl from "./controls/SearchControl";
|
| 9 |
import MyLocationControl from "./controls/MyLocationControl";
|
| 10 |
import SingleSelect from "./controls/SingleSelect";
|
|
@@ -18,16 +14,10 @@ import FirmsLayer from "./overlays/FirmsLayer";
|
|
| 18 |
import TractsLayer from "./overlays/TractsLayer";
|
| 19 |
import LegendControl from "./controls/LegendControl";
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
// map.panTo({ lat: ll[0], lng: ll[1] });
|
| 26 |
-
// const z = map.getZoom?.() ?? 0;
|
| 27 |
-
// if (z < 14) map.setZoom(14); // tweak 14–16 as you like
|
| 28 |
-
// }, [map, ll]);
|
| 29 |
-
// return null;
|
| 30 |
-
// }
|
| 31 |
|
| 32 |
export default function MapCanvas({
|
| 33 |
selectedLL,
|
|
@@ -48,20 +38,32 @@ export default function MapCanvas({
|
|
| 48 |
firms: FC | null;
|
| 49 |
reports: FC;
|
| 50 |
}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
return (
|
| 52 |
-
<APIProvider
|
| 53 |
-
apiKey={GMAPS_KEY}
|
| 54 |
-
libraries={["places", "marker", "geometry"]}
|
| 55 |
-
>
|
| 56 |
<Map
|
| 57 |
className="map"
|
| 58 |
-
mapId={
|
| 59 |
defaultCenter={{ lat: 39, lng: -98 }}
|
| 60 |
defaultZoom={4}
|
| 61 |
gestureHandling="greedy"
|
| 62 |
disableDefaultUI={false}
|
| 63 |
>
|
| 64 |
-
{/* <PanOnSelect ll={selectedLL} /> */}
|
| 65 |
<SearchControl onPlace={setSelected} />
|
| 66 |
<MyLocationControl onLocated={setSelected} />
|
| 67 |
<SingleSelect onPick={setSelected} />
|
|
@@ -81,6 +83,7 @@ export default function MapCanvas({
|
|
| 81 |
firms={firms}
|
| 82 |
minZoom={9}
|
| 83 |
/>
|
|
|
|
| 84 |
{selectedLL && (
|
| 85 |
<EmojiMarker
|
| 86 |
position={{ lat: selectedLL[0], lng: selectedLL[1] }}
|
|
@@ -152,7 +155,7 @@ export default function MapCanvas({
|
|
| 152 |
);
|
| 153 |
})}
|
| 154 |
|
| 155 |
-
{/* User reports
|
| 156 |
{reports.features.map((f, i) => {
|
| 157 |
if (f.geometry?.type !== "Point") return null;
|
| 158 |
const [lng, lat] = f.geometry.coordinates as [number, number];
|
|
|
|
| 1 |
+
// web/src/components/map/MapCanvas.tsx
|
| 2 |
+
import { APIProvider, Map, AdvancedMarker } from "@vis.gl/react-google-maps";
|
| 3 |
+
import { useEffect, useState } from "react";
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import SearchControl from "./controls/SearchControl";
|
| 5 |
import MyLocationControl from "./controls/MyLocationControl";
|
| 6 |
import SingleSelect from "./controls/SingleSelect";
|
|
|
|
| 14 |
import TractsLayer from "./overlays/TractsLayer";
|
| 15 |
import LegendControl from "./controls/LegendControl";
|
| 16 |
|
| 17 |
+
type RuntimeCfg = {
|
| 18 |
+
VITE_GOOGLE_MAPS_API_KEY?: string;
|
| 19 |
+
VITE_GOOGLE_MAPS_MAP_ID?: string;
|
| 20 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
export default function MapCanvas({
|
| 23 |
selectedLL,
|
|
|
|
| 38 |
firms: FC | null;
|
| 39 |
reports: FC;
|
| 40 |
}) {
|
| 41 |
+
const [cfg, setCfg] = useState<RuntimeCfg | null>(null);
|
| 42 |
+
const [err, setErr] = useState<string | null>(null);
|
| 43 |
+
|
| 44 |
+
useEffect(() => {
|
| 45 |
+
fetch("/config/runtime", { cache: "no-store" })
|
| 46 |
+
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(r.statusText))))
|
| 47 |
+
.then(setCfg)
|
| 48 |
+
.catch((e) => setErr(e.message));
|
| 49 |
+
}, []);
|
| 50 |
+
|
| 51 |
+
const apiKey = cfg?.VITE_GOOGLE_MAPS_API_KEY || GMAPS_KEY;
|
| 52 |
+
const mapId = cfg?.VITE_GOOGLE_MAPS_MAP_ID || MAP_ID || undefined;
|
| 53 |
+
|
| 54 |
+
if (err) return <div>Map config error: {err}</div>;
|
| 55 |
+
if (!apiKey) return <div>Loading map…</div>;
|
| 56 |
+
|
| 57 |
return (
|
| 58 |
+
<APIProvider apiKey={apiKey} libraries={["places", "marker", "geometry"]}>
|
|
|
|
|
|
|
|
|
|
| 59 |
<Map
|
| 60 |
className="map"
|
| 61 |
+
mapId={mapId}
|
| 62 |
defaultCenter={{ lat: 39, lng: -98 }}
|
| 63 |
defaultZoom={4}
|
| 64 |
gestureHandling="greedy"
|
| 65 |
disableDefaultUI={false}
|
| 66 |
>
|
|
|
|
| 67 |
<SearchControl onPlace={setSelected} />
|
| 68 |
<MyLocationControl onLocated={setSelected} />
|
| 69 |
<SingleSelect onPick={setSelected} />
|
|
|
|
| 83 |
firms={firms}
|
| 84 |
minZoom={9}
|
| 85 |
/>
|
| 86 |
+
|
| 87 |
{selectedLL && (
|
| 88 |
<EmojiMarker
|
| 89 |
position={{ lat: selectedLL[0], lng: selectedLL[1] }}
|
|
|
|
| 155 |
);
|
| 156 |
})}
|
| 157 |
|
| 158 |
+
{/* User reports */}
|
| 159 |
{reports.features.map((f, i) => {
|
| 160 |
if (f.geometry?.type !== "Point") return null;
|
| 161 |
const [lng, lat] = f.geometry.coordinates as [number, number];
|