diff --git a/frontend/components/dashboard/DetailPanel.tsx b/frontend/components/dashboard/DetailPanel.tsx index 8ba0690b..4cb74a55 100644 --- a/frontend/components/dashboard/DetailPanel.tsx +++ b/frontend/components/dashboard/DetailPanel.tsx @@ -6,7 +6,6 @@ import { useRouter } from "next/navigation"; import { useEffect, useMemo, useRef, useState } from "react"; import { ForecastTable } from "@/components/dashboard/PanelSections"; import { - useDashboardHistory, useDashboardModal, useDashboardStore, useProAccess, @@ -38,7 +37,6 @@ export function DetailPanel({ } = {}) { const store = useDashboardStore(); const modal = useDashboardModal(); - const history = useDashboardHistory(); const { proAccess } = useProAccess(); const { locale, t } = useI18n(); const router = useRouter(); @@ -76,7 +74,7 @@ export function DetailPanel({ ); const [heavyContentReady, setHeavyContentReady] = useState(false); const isOverlayOpen = - Boolean(modal.futureModalDate) || history.historyState.isOpen; + Boolean(modal.futureModalDate); const isVisible = isRail ? Boolean(store.selectedCity) && !isOverlayOpen : store.isPanelOpen && Boolean(store.selectedCity) && !isOverlayOpen; @@ -139,24 +137,20 @@ export function DetailPanel({ } }; - const handleFeatureAccess = (feature: "today" | "history") => { + const handleTodayAccess = () => { blurActiveElement(); if (!isPro) { trackAppEvent("paywall_feature_clicked", { entry: "detail_panel", - feature, + feature: "today", city: store.selectedCity, user_state: isAuthenticated ? "logged_in" : "guest", }); } if (isPro) { - if (feature === "today") { - void modal.openTodayModal(); - return; - } - void history.openHistory(); + void modal.openTodayModal(); return; } @@ -165,11 +159,7 @@ export function DetailPanel({ return; } - if (feature === "today") { - void modal.openTodayModal(); - return; - } - void history.openHistory(); + void modal.openTodayModal(); }; useEffect(() => { @@ -348,21 +338,6 @@ export function DetailPanel({ {locale === "en-US" ? "Market" : "市场页"} ) : null} - diff --git a/frontend/components/dashboard/scan-terminal/city-detail-utils.ts b/frontend/components/dashboard/scan-terminal/city-detail-utils.ts index 89a389dd..a9cee629 100644 --- a/frontend/components/dashboard/scan-terminal/city-detail-utils.ts +++ b/frontend/components/dashboard/scan-terminal/city-detail-utils.ts @@ -47,8 +47,8 @@ export function isFullEnoughForDeepAnalysis(detail?: CityDetail | null) { return false; } return ( - countDetailModels(detail, detail.local_date) > 1 && - countDetailForecastDays(detail) > 1 + countDetailModels(detail, detail.local_date) >= 1 && + countDetailForecastDays(detail) >= 1 ); } diff --git a/frontend/components/dashboard/scan-terminal/use-ai-pinned-city-workspace.ts b/frontend/components/dashboard/scan-terminal/use-ai-pinned-city-workspace.ts index 57c8b98e..d13e7a05 100644 --- a/frontend/components/dashboard/scan-terminal/use-ai-pinned-city-workspace.ts +++ b/frontend/components/dashboard/scan-terminal/use-ai-pinned-city-workspace.ts @@ -30,6 +30,7 @@ export function useAiPinnedCityWorkspace({ const aiFullHydrationRef = useRef>(new Set()); const aiHydrationQueueRef = useRef([]); const aiHydrationRunningRef = useRef(false); + const aiHydrationRetriesRef = useRef>(new Map()); const runAiHydrationQueue = useCallback(async () => { if (aiHydrationRunningRef.current) return; @@ -47,10 +48,22 @@ export function useAiPinnedCityWorkspace({ "full", ); if (!isFullEnoughForDeepAnalysis(detail)) { - aiFullHydrationRef.current.delete(key); + const retries = aiHydrationRetriesRef.current.get(key) || 0; + if (retries >= 3) { + aiFullHydrationRef.current.delete(key); + aiHydrationRetriesRef.current.delete(key); + } else { + aiHydrationRetriesRef.current.set(key, retries + 1); + } } } catch { - aiFullHydrationRef.current.delete(key); + const retries = aiHydrationRetriesRef.current.get(key) || 0; + if (retries >= 3) { + aiFullHydrationRef.current.delete(key); + aiHydrationRetriesRef.current.delete(key); + } else { + aiHydrationRetriesRef.current.set(key, retries + 1); + } } } } finally { @@ -132,6 +145,8 @@ export function useAiPinnedCityWorkspace({ aiPinnedCities.forEach((item) => { const key = normalizeCityKey(item.cityName); if (!key || aiFullHydrationRef.current.has(key)) return; + const retries = aiHydrationRetriesRef.current.get(key) || 0; + if (retries >= 3) return; const detail = findDetailForCity(store.cityDetailsByName, item.cityName); const needsFullHydration = !isFullEnoughForDeepAnalysis(detail); if (!needsFullHydration) return; diff --git a/frontend/lib/chart-utils.ts b/frontend/lib/chart-utils.ts index 3bdebe6f..a5732476 100644 --- a/frontend/lib/chart-utils.ts +++ b/frontend/lib/chart-utils.ts @@ -376,10 +376,18 @@ export function getTemperatureChartData( value: Number(rawTemps[index]), })) .filter((entry) => entry.tail !== ""); - const times = validEntries.map((entry) => entry.tail); - const temps = validEntries.map((entry) => - Number.isFinite(entry.value) ? entry.value : null, - ); + const dataByHour = new Map(); + validEntries.forEach((entry) => { + dataByHour.set(entry.tail, Number.isFinite(entry.value) ? entry.value : null); + }); + const times: string[] = []; + const temps: Array = []; + for (let h = 0; h < 24; h++) { + const hh = String(h).padStart(2, "0"); + const key = `${hh}:00`; + times.push(key); + temps.push(dataByHour.has(key) ? dataByHour.get(key)! : null); + } const suppressAnkaraMgmObservation = isTurkishMgmCity(detail); if (!times.length) return null; diff --git a/src/data_collection/nws_open_meteo_sources.py b/src/data_collection/nws_open_meteo_sources.py index f32ef617..f2f5e4d9 100644 --- a/src/data_collection/nws_open_meteo_sources.py +++ b/src/data_collection/nws_open_meteo_sources.py @@ -386,6 +386,7 @@ class NwsOpenMeteoSourceMixin: "daily": "temperature_2m_max,apparent_temperature_max,sunrise,sunset,sunshine_duration", "timezone": "auto", "forecast_days": forecast_days, + "past_days": 1, } # 显式指定单位,防止 API 默认行为漂移