@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.
193 lines (192 loc) • 7.63 kB
JavaScript
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
import React, { useState, useEffect } from "react";
import { useSpeakerDetection } from "../hooks/useSpeakerDetection";
import useListeningMode from "../hooks/useListeningMode";
import { API_BASE, CLIENT_VERSION } from "../lib/constants";
var SpeakerStatus = _ref => {
var {
minConfidence = 0.5,
interval = 3000
} = _ref;
var {
speaker,
confidence,
isSpeaking,
error,
status,
backendOnline // ✅ from updated hook
} = useSpeakerDetection({
interval,
minConfidence
});
var [showCorrection, setShowCorrection] = useState(false);
var [correctSpeaker, setCorrectSpeaker] = useState("");
var [correctionStatus, setCorrectionStatus] = useState(null);
var {
mode: listeningMode,
setMode: setListeningMode,
intervalMs,
setIntervalMs,
threshold,
setThreshold,
error: settingsError,
syncing
} = useListeningMode("off");
var [speakers, setSpeakers] = useState(null);
var [backendVersion, setBackendVersion] = useState(null);
var fetchSpeakers = () => {
fetch("".concat(API_BASE, "/api/speakers")).then(res => {
if (!res.ok) throw new Error("Backend unreachable");
return res.json();
}).then(data => setSpeakers(data)).catch(() => setSpeakers([]));
};
var fetchBackendVersion = () => {
fetch("".concat(API_BASE, "/api/version")).then(res => res.json()).then(data => setBackendVersion(data.version)).catch(() => setBackendVersion(null));
};
var handleSubmit = /*#__PURE__*/function () {
var _ref2 = _asyncToGenerator(function* () {
if (!correctSpeaker || !speaker) return;
try {
var res = yield fetch("".concat(API_BASE, "/api/correct-segment"), {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
old_speaker: speaker,
correct_speaker: correctSpeaker,
filename: "chunk_001.wav",
delete_original: true
})
});
var data = yield res.json();
setCorrectionStatus(data.success ? "✅ Correction submitted" : "\u274C Failed: ".concat(data.error));
} catch (err) {
console.error("❌ Correction failed:", err);
setCorrectionStatus("❌ Network error");
}
});
return function handleSubmit() {
return _ref2.apply(this, arguments);
};
}();
useEffect(() => {
fetchSpeakers();
fetchBackendVersion();
}, []);
useEffect(() => {
if (!isSpeaking) {
setShowCorrection(false);
setCorrectSpeaker("");
setCorrectionStatus(null);
}
}, [isSpeaking]);
// Determine readable status
var getReadableStatus = () => {
if (backendOnline === false) {
return "🔌 Backend unreachable — detection unavailable";
}
switch ((status || "").trim()) {
case "disabled":
return "🔇 Detection disabled";
case "mic unavailable":
return "❌ No microphone detected (unplugged or blocked)";
case "mic no signal":
return "⚠️ Mic detected but silent (muted or virtual)";
case "pending":
return listeningMode === "off" ? "🔇 Detection disabled" : "⏳ Awaiting detection data...";
case "listening":
return isSpeaking ? "🎙 Voice detected" : "🕵️ Listening for input...";
default:
return "⚠️ Unknown system state";
}
};
if (error && backendOnline === false) {
return /*#__PURE__*/React.createElement("p", {
className: "speaker-error"
}, "\u274C ", error);
}
return /*#__PURE__*/React.createElement("div", {
className: "speaker-status-panel"
}, /*#__PURE__*/React.createElement("h4", {
className: "speaker-heading"
}, "\uD83D\uDD0A Speaker Status"), /*#__PURE__*/React.createElement("div", {
style: {
marginBottom: "10px"
}
}, /*#__PURE__*/React.createElement("label", null, "\uD83C\uDF9A Listening Mode:", /*#__PURE__*/React.createElement("select", {
value: listeningMode,
onChange: e => setListeningMode(e.target.value),
style: {
marginLeft: "8px",
background: "#222",
color: "#fff",
borderRadius: "4px",
padding: "4px",
border: "1px solid #555"
}
}, /*#__PURE__*/React.createElement("option", {
value: "off"
}, "\uD83D\uDD07 Off"), /*#__PURE__*/React.createElement("option", {
value: "single"
}, "\uD83E\uDDCD Single"), /*#__PURE__*/React.createElement("option", {
value: "multi"
}, "\uD83D\uDC65 Multi")))), /*#__PURE__*/React.createElement("div", {
style: {
marginBottom: "10px"
}
}, /*#__PURE__*/React.createElement("label", null, "\u23F1 Interval (ms):", /*#__PURE__*/React.createElement("input", {
type: "range",
min: "500",
max: "5000",
step: "100",
value: intervalMs,
onChange: e => setIntervalMs(Number(e.target.value))
}), /*#__PURE__*/React.createElement("span", null, intervalMs, " ms"))), /*#__PURE__*/React.createElement("div", {
style: {
marginBottom: "10px"
}
}, /*#__PURE__*/React.createElement("label", null, "\uD83E\uDDE0 Threshold:", /*#__PURE__*/React.createElement("input", {
type: "range",
min: "0",
max: "1",
step: "0.01",
value: threshold,
onChange: e => setThreshold(parseFloat(e.target.value))
}), /*#__PURE__*/React.createElement("span", null, threshold))), syncing && /*#__PURE__*/React.createElement("p", {
style: {
fontStyle: "italic",
color: "#999"
}
}, "\uD83D\uDD04 Syncing settings..."), /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("strong", null, "Detected Speaker:"), " ", speaker || "None", /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement("strong", null, "Confidence:"), " ", confidence !== null ? "".concat(Math.round(confidence * 100), "%") : "N/A", /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement("strong", null, "Status:"), " ", getReadableStatus()), isSpeaking && (!showCorrection ? /*#__PURE__*/React.createElement("button", {
onClick: () => setShowCorrection(true)
}, "\uD83E\uDD14 Voice not recognized?") : /*#__PURE__*/React.createElement("div", {
className: "correction-form"
}, /*#__PURE__*/React.createElement("input", {
type: "text",
placeholder: "Correct speaker name",
value: correctSpeaker,
onChange: e => setCorrectSpeaker(e.target.value)
}), /*#__PURE__*/React.createElement("button", {
onClick: handleSubmit
}, "Submit Correction"), correctionStatus && /*#__PURE__*/React.createElement("p", {
className: "correction-status"
}, correctionStatus))), /*#__PURE__*/React.createElement("hr", null), /*#__PURE__*/React.createElement("h5", null, "\uD83E\uDDE0 Enrolled Speakers"), speakers === null ? /*#__PURE__*/React.createElement("p", {
style: {
fontStyle: "italic",
color: "#aaa"
}
}, "Loading...") : speakers.length === 0 ? /*#__PURE__*/React.createElement("p", {
style: {
color: "#f55"
}
}, "\uD83D\uDD0C Backend unreachable or no speakers enrolled.") : /*#__PURE__*/React.createElement("ul", null, speakers.map(s => /*#__PURE__*/React.createElement("li", {
key: s.name
}, s.name, " (", s.recordings, ")"))), /*#__PURE__*/React.createElement("hr", null), /*#__PURE__*/React.createElement("p", {
style: {
fontSize: "0.8rem",
color: "#888"
}
}, "\uD83E\uDDEA Client v", CLIENT_VERSION, backendVersion && " | Backend v".concat(backendVersion)));
};
export default SpeakerStatus;