Files
PolyWeather/frontend/components/dashboard/scan-terminal/use-scan-terminal-query.ts
T
2569718930@qq.com b122e7cbae Stabilize the decision workspace data boundaries
The scan terminal had grown into overlapping CSS, request-state, AI-provider, and city-card data responsibilities. This refactor separates those boundaries without changing product behavior: CSS modules are split by surface, city AI prompt/provider/fallback logic is isolated, and scan terminal request state now has reusable RemoteData adapters plus business-state tests.

Constraint: Preserve existing global scan-terminal class names and API responses during the refactor

Constraint: No new dependencies; keep this as a file-boundary cleanup

Rejected: Introduce React Query now | higher migration risk than the requested lightweight query-client path

Rejected: Rewrite AI stream behavior | progressive/fallback states are product-sensitive and were only adapter-split

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep AI stream state changes covered by business snapshots before changing fallback/cache wording

Tested: npm run test:business; npx tsc --noEmit; npm run build; python pytest -q; ruff check; py_compile targeted city AI modules

Not-tested: Live DeepSeek provider network replay and browser visual QA
2026-04-28 14:45:34 +08:00

100 lines
2.5 KiB
TypeScript

"use client";
import { useCallback, useEffect, useRef } from "react";
import {
scanTerminalQueryPolicy,
scanTerminalClient,
shouldRunAutoTerminalRefresh,
shouldSkipManualTerminalRefresh,
} from "@/components/dashboard/scan-terminal/scan-terminal-client";
import { useRemoteDataQuery } from "@/components/dashboard/scan-terminal/use-remote-data-query";
import type { ScanTerminalResponse } from "@/lib/dashboard-types";
export function useScanTerminalQuery({
isPro,
proAccessLoading,
}: {
isPro: boolean;
proAccessLoading: boolean;
}) {
const {
data: terminalData,
error: scanError,
isLoading,
loading: scanLoading,
remote: scanRemote,
reset,
run,
} = useRemoteDataQuery<ScanTerminalResponse>();
const lastForcedScanRefreshAtRef = useRef(0);
const fetchScanTerminal = useCallback(
async ({
forceRefresh = false,
showLoading = false,
}: {
forceRefresh?: boolean;
showLoading?: boolean;
} = {}) => {
if (proAccessLoading || !isPro) return;
if (forceRefresh) {
lastForcedScanRefreshAtRef.current = Date.now();
}
await run({
request: (signal) =>
scanTerminalClient.getTerminal({
forceRefresh,
signal,
}),
showLoading,
});
},
[isPro, proAccessLoading, run],
);
useEffect(() => {
if (proAccessLoading) return;
if (!isPro) {
reset();
return;
}
void fetchScanTerminal({ forceRefresh: false, showLoading: true });
}, [fetchScanTerminal, isPro, proAccessLoading, reset]);
const refreshScanTerminalManually = useCallback(() => {
if (
shouldSkipManualTerminalRefresh({
hasCurrentData: Boolean(terminalData),
lastForcedRefreshAt: lastForcedScanRefreshAtRef.current,
})
) {
return;
}
void fetchScanTerminal({ forceRefresh: true, showLoading: true });
}, [fetchScanTerminal, terminalData]);
useEffect(() => {
if (proAccessLoading || !isPro) return;
const intervalId = window.setInterval(() => {
if (
!shouldRunAutoTerminalRefresh({
documentHidden: document.hidden,
isLoading: isLoading(),
})
) {
return;
}
void fetchScanTerminal({ forceRefresh: true, showLoading: false });
}, scanTerminalQueryPolicy.autoRefreshMs);
return () => window.clearInterval(intervalId);
}, [fetchScanTerminal, isLoading, isPro, proAccessLoading]);
return {
refreshScanTerminalManually,
scanError,
scanLoading,
scanRemote,
terminalData,
};
}