返回 DeepSeek-Reasonix
SessionLoadingIndicator.tsx
根目录 / desktop / frontend / src / components / SessionLoadingIndicator.tsx
1 import { useEffect, useState } from "react";
2 import { Loader2 } from "lucide-react";
3 import { useT } from "../lib/i18n";
4 import type { SessionAvailability } from "../lib/sessionAvailability";
5
6 /** Presentation deadlines only; neither data fetching nor ready content waits. */
7 export const SESSION_LOADING_DELAY_MS = 250;
8 export const SESSION_LOADING_DETAIL_MS = 2000;
9
10 export function SessionLoadingIndicator({ active, identity, source = "history" }: {
11 active: boolean;
12 identity: string;
13 source?: SessionAvailability["source"];
14 }) {
15 // Each pending episode owns its timers. Switching identity or completing a
16 // load unmounts that episode, including when the next load uses the same tab.
17 return active ? <PendingIndicator key={identity} source={source} /> : null;
18 }
19
20 function PendingIndicator({ source }: { source: SessionAvailability["source"] }) {
21 const t = useT();
22 const [stage, setStage] = useState<"quiet" | "loading" | "slow">("quiet");
23 useEffect(() => {
24 let current = true;
25 const loading = setTimeout(() => { if (current) setStage("loading"); }, SESSION_LOADING_DELAY_MS);
26 const slow = setTimeout(() => { if (current) setStage("slow"); }, SESSION_LOADING_DETAIL_MS);
27 return () => { current = false; clearTimeout(loading); clearTimeout(slow); };
28 }, []);
29 if (stage === "quiet") return null;
30 const label = t(stage !== "slow" || source === "runtime" ? "common.loading"
31 : source === "connection" ? "remoteSurface.connecting" : "sessionRecovery.loadingHistory");
32 return <div className="session-loading-indicator" role="status" aria-live="polite">
33 <Loader2 size={16} className="session-recovery__spinner" aria-hidden="true" />
34 <span>{label}</span>
35 </div>;
36 }
37
37 lines Plain Text