feat: initialize TJWater WebGIS frontend
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { Bot } from "lucide-react";
|
||||
import { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { PersonaState } from "@/shared/ai-elements/persona";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const LazyPersona = lazy(async () => {
|
||||
const module = await import("@/shared/ai-elements/persona");
|
||||
return { default: module.Persona };
|
||||
});
|
||||
|
||||
type AgentPersonaProps = {
|
||||
className?: string;
|
||||
state?: PersonaState;
|
||||
};
|
||||
|
||||
function useElementVisibility() {
|
||||
const containerRef = useRef<HTMLSpanElement>(null);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
let intersectsViewport = false;
|
||||
const updateVisibility = () => {
|
||||
setVisible(intersectsViewport && document.visibilityState === "visible");
|
||||
};
|
||||
const observer = new IntersectionObserver(([entry]) => {
|
||||
intersectsViewport = Boolean(entry?.isIntersecting);
|
||||
updateVisibility();
|
||||
});
|
||||
|
||||
observer.observe(container);
|
||||
document.addEventListener("visibilitychange", updateVisibility);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
document.removeEventListener("visibilitychange", updateVisibility);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { containerRef, visible };
|
||||
}
|
||||
|
||||
function PersonaFallback() {
|
||||
return (
|
||||
<span className="grid h-full w-full place-items-center rounded-full border border-slate-300/70 bg-slate-100 text-slate-500">
|
||||
<Bot className="h-2/5 w-2/5" aria-hidden="true" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function AgentPersona({ className, state = "idle" }: AgentPersonaProps) {
|
||||
const { containerRef, visible } = useElementVisibility();
|
||||
const [failed, setFailed] = useState(false);
|
||||
const handleLoadError = useCallback(() => setFailed(true), []);
|
||||
|
||||
const shouldRenderAnimation = !failed && visible;
|
||||
|
||||
return (
|
||||
<span ref={containerRef} className={cn("relative block shrink-0", className)}>
|
||||
{shouldRenderAnimation ? (
|
||||
<Suspense fallback={<PersonaFallback />}>
|
||||
<LazyPersona
|
||||
className="h-full w-full scale-110"
|
||||
onLoadError={handleLoadError}
|
||||
state={state}
|
||||
/>
|
||||
</Suspense>
|
||||
) : (
|
||||
<PersonaFallback />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user