feat: implement scan terminal service with AI-powered city analysis and caching support

This commit is contained in:
2569718930@qq.com
2026-04-26 13:50:46 +08:00
parent d42cdf6b51
commit f9ad34d7c0
3 changed files with 127 additions and 19 deletions
@@ -155,7 +155,7 @@ function AiPinnedCityCard({
const { aiForecast, refreshAiForecast } = useAiCityForecast({
detail,
detailCityName,
enabled: Boolean(detail && !collapsed),
enabled: Boolean(detail),
isEn,
locale,
report,
@@ -163,7 +163,7 @@ function AiPinnedCityCard({
const { marketScan, marketStatus } = useCityMarketScan({
detail,
detailCityName,
enabled: Boolean(detail && !collapsed),
enabled: Boolean(detail),
});
const aiCityForecast = aiForecast.payload?.city_forecast || null;
@@ -572,7 +572,6 @@ export function AiPinnedForecastView({
items.forEach((item) => {
const stableKey = normalizeCityKey(item.cityName) || item.cityName;
if (!knownCityKeysRef.current.has(stableKey)) {
next.add(stableKey);
changed = true;
}
});
@@ -82,6 +82,16 @@ function writeCachedPayload<T>(key: string, payload: T) {
}
}
function removeCachedPayload(key: string) {
const storage = getStorage();
if (!storage) return;
try {
storage.removeItem(key);
} catch {
// Ignore privacy-mode failures; the next network request can still proceed.
}
}
function parseAiCityStreamBlock(block: string): AiCityStreamEvent | null {
const eventLines = block
.split(/\r?\n/)
@@ -369,10 +379,17 @@ export function useAiCityForecast({
)
: null;
if (cachedPayload) {
setAiForecast({ payload: cachedPayload, status: "ready" });
return () => {
cancelled = true;
};
if (
cachedPayload.status === "ready" &&
!cachedPayload.degraded &&
cachedPayload.city_forecast
) {
setAiForecast({ payload: cachedPayload, status: "ready" });
return () => {
cancelled = true;
};
}
removeCachedPayload(cacheKey);
}
const initialFallback = buildAiCityFallbackPayload({ detail, isEn, report });
setAiForecast({