UNPKG

@p0llen/speaker-detector-client

Version:

React client for interfacing with the [speaker-detector](https://pypi.org/project/speaker-detector) Python backend. Supports real-time speaker identification, meeting transcription, and speaker enrollment.

106 lines (103 loc) 3.89 kB
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator"; // hooks/useSpeakerDetection.js import { useState, useEffect } from "react"; import useListeningMode from "./useListeningMode"; var DEFAULT_API_URL = import.meta.env.VITE_SPEAKER_API_URL || "http://localhost:9000"; export function useSpeakerDetection() { var { interval = 3000, minConfidence = 0.5, endpoint = "/api/active-speaker", log = true } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var [speaker, setSpeaker] = useState(null); var [confidence, setConfidence] = useState(null); var [isSpeaking, setIsSpeaking] = useState(false); var [error, setError] = useState(null); var [status, setStatus] = useState("pending"); var [backendOnline, setBackendOnline] = useState(null); var { mode: listeningMode } = useListeningMode("off"); // Heartbeat: backend reachability useEffect(() => { var id; var check = /*#__PURE__*/function () { var _ref = _asyncToGenerator(function* () { try { var res = yield fetch("".concat(DEFAULT_API_URL, "/api/version"), { cache: "no-store" }); if (!res.ok) throw new Error("Version API failed"); yield res.json(); setBackendOnline(true); } catch (_unused) { setBackendOnline(false); } }); return function check() { return _ref.apply(this, arguments); }; }(); check(); id = setInterval(check, 5000); // snappier return () => clearInterval(id); }, []); // Active speaker polling — ALWAYS poll so UI can show disabled status too useEffect(() => { var id; var cancelled = false; var poll = /*#__PURE__*/function () { var _ref2 = _asyncToGenerator(function* () { try { var _data$status, _data$speaker; var res = yield fetch("".concat(DEFAULT_API_URL).concat(endpoint), { cache: "no-store" }); if (!res.ok) throw new Error("Active speaker API failed"); var data = yield res.json(); if (cancelled) return; // data from backend is already { speaker, confidence, status, is_speaking } var s = (_data$status = data.status) !== null && _data$status !== void 0 ? _data$status : "pending"; var spk = (_data$speaker = data.speaker) !== null && _data$speaker !== void 0 ? _data$speaker : null; var conf = typeof data.confidence === "number" ? data.confidence : null; var speaking = typeof data.is_speaking === "boolean" ? data.is_speaking : (conf !== null && conf !== void 0 ? conf : 0) >= minConfidence && spk && spk !== "unknown"; setStatus(s); setSpeaker(spk); setConfidence(conf); setIsSpeaking(speaking); setError(null); if (log) { console.log("\uD83C\uDF99\uFE0F Detected: ".concat(spk !== null && spk !== void 0 ? spk : "—", " (").concat(Math.round((conf !== null && conf !== void 0 ? conf : 0) * 100), "%) | ").concat(s)); } } catch (err) { console.error("❌ Failed to poll active speaker:", err); if (backendOnline === false) setError("Backend unreachable"); setStatus("error"); setSpeaker(null); setConfidence(null); setIsSpeaking(false); } }); return function poll() { return _ref2.apply(this, arguments); }; }(); poll(); // immediate id = setInterval(poll, interval); return () => { cancelled = true; clearInterval(id); }; }, [interval, minConfidence, endpoint, log, backendOnline /* note: not gated by listeningMode */]); return { speaker, confidence, isSpeaking, error, status, backendOnline, listeningMode }; } export default useSpeakerDetection;