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.

193 lines (192 loc) 7.63 kB
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;