I already tried removing onAgentState from the dependency list. I’ve actually tried many different workarounds for this. No matter what I do, I get a cascading of “max depth exceeded” errors. This is what I need:
import { useState, useEffect } from "react";
import { fetch } from "@tauri-apps/plugin-http";
import { LiveKitRoom, VideoConference } from "@livekit/components-react";
import "@livekit/components-styles";
import { Canvas } from "@react-three/fiber";
import { useAgent } from "@livekit/components-react";
import { Center, OrbitControls } from "@react-three/drei";
import {
EffectComposer,
Outline,
Selection,
Select,
} from "@react-three/postprocessing";
import { BlendFunction } from "postprocessing";
import type { User } from "@supabase/supabase-js";
import { Yuki } from "../yuki";
import { useYukiStateMachine } from "../hooks/useYukiStateMachine";
interface MainAppProps {
user: User;
accessToken: string;
onSignOut: () => void;
}
interface LiveKitTokenResponse {
server_url: string;
participant_token: string;
room_name: string;
}
async function getLiveKitToken(
accessToken: string,
timezone: string,
): Promise<LiveKitTokenResponse> {
const response = await fetch(`<url>`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ timezone }),
});
if (!response.ok) throw new Error(`Token server error: ${response.status}`);
return response.json() as Promise<LiveKitTokenResponse>;
}
// My approach:
// reads agent.state from the LiveKit context and calls the setter so the
// state machine (which lives outside the LiveKit context) can react.
function AgentStateWatcher({
onAgentState,
}: {
onAgentState: (s: string) => void;
}) {
const agent = useAgent();
useEffect(() => {
onAgentState(agent.state ?? "idle");
}, [agent.state, onAgentState]);
return null;
}
function YukiWithStateMachine({ agentState }: { agentState: string }) {
const { currentAnimation, onAnimationComplete } =
useYukiStateMachine(agentState);
return (
<Select enabled>
<Center>
<Yuki
currentAnimation={currentAnimation}
onAnimationComplete={onAnimationComplete}
/>
</Center>
</Select>
);
}
export function MainApp({ user, accessToken, onSignOut }: MainAppProps) {
const [token, setToken] = useState("");
const [url, setUrl] = useState("");
const [connected, setConnected] = useState(false);
const [agentState, setAgentState] = useState("idle");
const connect = async () => {
try {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const { server_url, participant_token } = await getLiveKitToken(
accessToken,
timezone,
);
setUrl(server_url);
setToken(participant_token);
setConnected(true);
} catch (e) {
console.error("[ROOM ERROR] Failed to connect:", e);
alert("Failed to get LiveKit token. See console for details.");
}
};
const onDisconnected = () => {
setConnected(false);
setToken("");
setUrl("");
};
return (
<div className="container" data-lk-theme="default">
<div className="flex items-center justify-between px-4 py-2">
<h1 className="text-xl font-semibold">Yuki</h1>
<div className="flex items-center gap-3">
<span className="text-sm text-muted-foreground">{user.email}</span>
<button
onClick={onSignOut}
className="text-sm underline-offset-4 hover:underline"
>
Sign out
</button>
</div>
</div>
{/* 3D Model Display */}
<div style={{ width: "100%", height: "400px", marginBottom: "20px" }}>
<Canvas camera={{ position: [0, 1, 3], fov: 30 }}>
<ambientLight intensity={0.08} color="#c8d0ff" />
{/* Warm key light — upper-front-right, drives the main toon band split */}
<directionalLight
position={[2, 5, 3]}
intensity={4.5}
color="#fff8e8"
/>
{/* Cool rim / backlight — upper-back-left, classic anime halo */}
<directionalLight
position={[-3, 3, -4]}
intensity={1.8}
color="#7090ff"
/>
{/* Soft ground-bounce fill — lifts the chin/underside slightly */}
<directionalLight
position={[0, -2, 2]}
intensity={0.25}
color="#a0c8ff"
/>
<Selection>
<EffectComposer autoClear={false}>
<Outline
blendFunction={BlendFunction.ALPHA}
edgeStrength={4}
visibleEdgeColor={0x000000}
hiddenEdgeColor={0x000000}
blur={false}
xRay={false}
/>
</EffectComposer>
<YukiWithStateMachine agentState={agentState} />
</Selection>
<OrbitControls
target={[0, 0.5, 0]}
minDistance={0.5}
maxDistance={6}
minPolarAngle={Math.PI / 6}
maxPolarAngle={Math.PI / 1.8}
/>
</Canvas>
</div>
{!connected ? (
<div className="card">
<h2>Join Room</h2>
<button
onClick={connect}
style={{
padding: "10px 20px",
backgroundColor: "#28a745",
color: "white",
border: "none",
borderRadius: 4,
cursor: "pointer",
fontWeight: "bold",
fontSize: 16,
}}
>
Connect to Agent
</button>
</div>
) : (
<LiveKitRoom
serverUrl={url}
token={token}
connect={true}
onConnected={() => console.log("[ROOM] Connected to LiveKit room")}
onDisconnected={onDisconnected}
onError={(error) => console.error("[ROOM ERROR]", error)}
audio={true}
>
<AgentStateWatcher onAgentState={setAgentState} />
<VideoConference />
</LiveKitRoom>
)}
</div>
);
}