feat: implement mobile scan terminal dashboard and city picker with backend forum automation scripts

This commit is contained in:
2569718930@qq.com
2026-05-17 17:05:47 +08:00
parent 2d9aa576dd
commit 58e6557c66
12 changed files with 942 additions and 242 deletions
@@ -3,9 +3,7 @@
import clsx from "clsx";
import type { MouseEvent } from "react";
import { useEffect, useState } from "react";
import { AiCityTemperatureChart } from "@/components/dashboard/scan-terminal/AiCityTemperatureChart";
import { AiEvidencePanel } from "@/components/dashboard/scan-terminal/AiEvidencePanel";
import { AirportEvidencePanel } from "@/components/dashboard/scan-terminal/AirportEvidencePanel";
import { CityCardHeader } from "@/components/dashboard/scan-terminal/CityCardHeader";
import { MobileDecisionCard } from "@/components/dashboard/scan-terminal/MobileDecisionCard";
import { ModelEvidencePanel } from "@/components/dashboard/scan-terminal/ModelEvidencePanel";
@@ -267,27 +265,8 @@ export function AiPinnedCityCard({
const report = isHkoObservation
? ""
: detail?.current?.raw_metar || detail?.airport_current?.raw_metar || "";
const metarReportTimeDisplay = formatMetarReportTime(detail, report, isEn);
const observationStation = isHkoObservation
? detail?.current?.station_name ||
detail?.current?.station_code ||
detail?.settlement_station?.settlement_station_label ||
detail?.settlement_station?.settlement_station_code ||
"香港天文台"
: detail?.risk?.icao ||
detail?.current?.station_code ||
detail?.airport_current?.station_code ||
displayAirportPrimary?.station_code ||
"";
const observationSourceZh = isHkoObservation ? "香港天文台观测" : "METAR 实测";
const observationSourceEn = isHkoObservation ? "HKO observations" : "METAR observations";
const rawObservationText = isHkoObservation
? `${isEn ? "Observation source" : "观测来源"}${observationStation || (isEn ? "Hong Kong Observatory" : "香港天文台")}${metarReportTimeDisplay ? `${metarReportTimeDisplay}` : ""}`
: report
? `${isEn ? "Raw METAR" : "原始 METAR"}${`${observationStation} ${report}`.trim()}`
: isEn
? "Raw METAR: unavailable."
: "原始 METAR:暂无。";
const detailCityName = detail?.name || item.cityName;
const [refreshingDetail, setRefreshingDetail] = useState(false);
const { aiForecast, refreshAiForecast } = useAiCityForecast({
@@ -316,31 +295,16 @@ export function AiPinnedCityCard({
}, []);
const aiCityForecast = aiForecast.payload?.city_forecast || null;
const localizedFinalJudgmentRaw =
const localizedFinalJudgment =
(isEn ? aiCityForecast?.final_judgment_en : aiCityForecast?.final_judgment_zh) ||
(isEn ? aiCityForecast?.reasoning_en : aiCityForecast?.reasoning_zh) ||
"";
const localizedMetarReadRaw =
const localizedMetarRead =
(isEn ? aiCityForecast?.metar_read_en : aiCityForecast?.metar_read_zh) ||
"";
const localizedReasoningRaw =
const localizedReasoning =
(isEn ? aiCityForecast?.reasoning_en : aiCityForecast?.reasoning_zh) ||
"";
const localizedFinalJudgment = normalizeMetarReadTime(
localizedFinalJudgmentRaw,
metarReportTimeDisplay,
isEn,
);
const localizedMetarRead = normalizeMetarReadTime(
localizedMetarReadRaw,
metarReportTimeDisplay,
isEn,
);
const localizedReasoning = normalizeMetarReadTime(
localizedReasoningRaw,
metarReportTimeDisplay,
isEn,
);
const localizedModelNote =
(isEn
? aiCityForecast?.model_cluster_note_en
@@ -390,11 +354,6 @@ export function AiPinnedCityCard({
decisionExpectedHighNumber != null
? formatTemperatureValue(decisionExpectedHighNumber, tempSymbol, { digits: 1 })
: "--";
const observedLabel = undefined;
const currentTempText =
currentTempNumber != null
? formatTemperatureValue(currentTempNumber, tempSymbol, { digits: 1 })
: "--";
const debText =
debNumber != null
? formatTemperatureValue(debNumber, tempSymbol, { digits: 1 })
@@ -463,33 +422,20 @@ export function AiPinnedCityCard({
peakHasPassed,
});
const dataFreshnessRows = [
{
label: isHkoObservation ? (isEn ? "HKO" : "天文台") : "METAR",
labelTitle: isHkoObservation
? (isEn ? "Hong Kong Observatory official readings" : "香港天文台官方实测")
: (isEn ? "Meteorological Aerodrome Report — airport weather observation" : "机场气象观测报文"),
value: buildObservationFreshnessValue({
detail,
displayTime: metarReportTimeDisplay,
isEn,
isHkoObservation,
}),
tone: observationStale ? "stale" : "fresh",
},
{
label: isEn ? "Models" : "模型",
value: buildModelFreshnessValue(detail, locale, isEn),
tone: "fresh",
tone: "fresh" as const,
},
{
label: isEn ? "Market" : "市场价格",
value: buildMarketFreshnessValue({ isEn, marketScan, marketStatus }),
tone:
marketDecisionView.status === "ready"
? "fresh"
? ("fresh" as const)
: marketDecisionView.status === "loading"
? "loading"
: "stale",
? ("loading" as const)
: ("stale" as const),
},
];
const freshnessSeparator = isEn ? ": " : "";
@@ -569,15 +515,12 @@ export function AiPinnedCityCard({
aiReadInProgressText={aiReadInProgressText}
aiRuleEvidenceMode={aiRuleEvidenceMode}
aiRuleEvidenceText={aiRuleEvidenceText}
currentTempText={currentTempText}
dataFreshnessRows={dataFreshnessRows}
debPrediction={debNumber}
decisionState={decisionState}
detail={detail}
displayName={displayName}
expectedHighText={expectedHighText}
fallbackAiReason={fallbackAiReason}
freshnessSeparator={freshnessSeparator}
isEn={isEn}
isHkoObservation={isHkoObservation}
isRefreshing={isRefreshing}
@@ -588,7 +531,6 @@ export function AiPinnedCityCard({
onRefresh={handleRefresh}
onRemove={handleRemove}
peakWindow={peakWindow}
rawObservationText={rawObservationText}
removing={removing}
tempSymbol={tempSymbol}
/>
@@ -599,14 +541,10 @@ export function AiPinnedCityCard({
aiStatusTone={decisionState.aiStatusTone}
collapseId={collapseId}
collapsed={collapsed}
currentTempText={currentTempText}
observedLabel={observedLabel}
dataFreshnessRows={dataFreshnessRows}
debText={debText}
detailLocalTime={detail?.local_time}
displayName={displayName}
expectedHighText={expectedHighText}
freshnessSeparator={freshnessSeparator}
isEn={isEn}
isRefreshing={isRefreshing}
modelRange={modelRange}
@@ -631,7 +569,6 @@ export function AiPinnedCityCard({
/>
<div className="scan-ai-city-analysis-grid">
<AiCityTemperatureChart detail={detail} />
<AiEvidencePanel
aiBullets={aiBullets}
aiCityForecast={aiCityForecast}
@@ -650,12 +587,10 @@ export function AiPinnedCityCard({
isHkoObservation={isHkoObservation}
localModelSupportNote={localModelSupportNote}
localizedFinalJudgment={localizedFinalJudgment}
rawObservationText={rawObservationText}
tempSymbol={tempSymbol}
/>
</div>
<AirportEvidencePanel detail={detail} isEn={isEn} />
<ModelEvidencePanel detail={detail} isEn={isEn} />
</div>
) : !detail ? (