aac-chatbot / frontend /src /App.tsx
akashkolte's picture
fixed affect
948a968
import { useState, useCallback, useEffect, useRef } from "react";
import type { Persona, Affect, ChatMessage, LatencyLog } from "./types";
import { resetSession, checkHealth } from "./lib/api";
import { useWebcam } from "./hooks/useWebcam";
import { useSensing } from "./hooks/useSensing";
import { PersonaSelector } from "./components/PersonaSelector";
import { ChatPanel } from "./components/ChatPanel";
import { WebcamSensing } from "./components/WebcamSensing";
import { SensingStatus } from "./components/SensingStatus";
import { LatencyMetrics } from "./components/LatencyMetrics";
import "./App.css";
function App() {
const [persona, setPersona] = useState<Persona | null>(null);
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [latency, setLatency] = useState<LatencyLog | null>(null);
const [webcamEnabled, setWebcamEnabled] = useState(false);
const [affectOverride, setAffectOverride] = useState<Affect | null>(null);
const [backendReady, setBackendReady] = useState(false);
const healthPoll = useRef<ReturnType<typeof setInterval>>(undefined);
useEffect(() => {
async function poll() {
const ready = await checkHealth();
if (ready) {
setBackendReady(true);
clearInterval(healthPoll.current);
}
}
poll();
healthPoll.current = setInterval(poll, 2000);
return () => clearInterval(healthPoll.current);
}, []);
const {
sensing,
ready,
initError,
init,
processFrame,
clearAirWrittenText,
clearHeadSignal,
resetCalibration,
} = useSensing();
const onFrame = useCallback(
(video: HTMLVideoElement, timestamp: number) => {
processFrame(video, timestamp);
},
[processFrame]
);
const { videoRef, active, error } = useWebcam({
enabled: webcamEnabled && ready,
onFrame,
});
async function handleWebcamToggle() {
if (!webcamEnabled) {
const ok = await init();
if (ok) setWebcamEnabled(true);
} else {
setWebcamEnabled(false);
resetCalibration();
}
}
async function handlePersonaSelect(p: Persona) {
setPersona(p);
setMessages([]);
setLatency(null);
try {
await resetSession(p.id);
} catch {
// Session reset failed — non-critical, continue with fresh UI state
}
}
return (
<div className="app-layout">
<aside className="sidebar">
<h1 className="app-title">
<img src="/favicon.svg" alt="" className="app-logo" />
AAC Chatbot
</h1>
<PersonaSelector
selected={persona?.id ?? null}
onSelect={handlePersonaSelect}
/>
<div className="sidebar-section">
<label className="toggle-label">
<input
type="checkbox"
checked={webcamEnabled}
onChange={handleWebcamToggle}
/>
Enable webcam
</label>
<WebcamSensing videoRef={videoRef} active={active} error={error || initError} />
<SensingStatus sensing={sensing} webcamActive={active} />
</div>
<div className="sidebar-section">
<label htmlFor="affect-override">Affect override</label>
<select
id="affect-override"
value={affectOverride ?? "auto"}
onChange={(e) =>
setAffectOverride(
e.target.value === "auto" ? null : (e.target.value as Affect)
)
}
>
<option value="auto">Auto (webcam)</option>
<option value="HAPPY">HAPPY</option>
<option value="FRUSTRATED">FRUSTRATED</option>
<option value="NEUTRAL">NEUTRAL</option>
<option value="SURPRISED">SURPRISED</option>
</select>
</div>
<LatencyMetrics latency={latency} />
</aside>
<main className="main-content">
<ChatPanel
userId={persona?.id ?? null}
personaName={persona?.name ?? ""}
sensing={sensing}
affectOverride={affectOverride}
onAirTextConsumed={clearAirWrittenText}
onHeadSignalConsumed={clearHeadSignal}
messages={messages}
setMessages={setMessages}
onLatency={setLatency}
backendReady={backendReady}
/>
</main>
</div>
);
}
export default App;