Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions tools/audio-lab/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

121 changes: 121 additions & 0 deletions tools/audio-lab/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import { STATE_COLORS as TURN_STATE_COLORS } from "@/lib/turnColors";
import { TurnConfigPanel } from "@/components/TurnConfigPanel";
import { PipelineConfigPanel } from "@/components/PipelineConfigPanel";
import { PipelineTimeline } from "@/components/PipelineTimeline";
import { AsrConfigPanel } from "@/components/AsrConfigPanel";
import {
AsrTranscript,
type AsrTranscriptState,
} from "@/components/AsrTranscript";
import {
type Viewport,
createDefaultViewport,
Expand All @@ -32,6 +37,7 @@ import {
type TurnConfig,
type PipelineConfig,
type PipelineResultPoint,
type AsrConfig,
type ParamInfo,
type ServerMessage,
type ConnectionState,
Expand Down Expand Up @@ -192,6 +198,17 @@ function App() {
});
const [pipelineResults, setPipelineResults] = useState<Record<string, PipelineResultPoint[]>>({});

const [asrBackends, setAsrBackends] = useState<Record<string, ParamInfo[]>>({});
const [asrConfigs, setAsrConfigs] = useState<AsrConfig[]>(() => {
try {
const saved = localStorage.getItem("lab-asr-configs");
if (saved !== null) return JSON.parse(saved) as AsrConfig[];
} catch { /* ignore */ }
return [];
});
const [asrTranscripts, setAsrTranscripts] = useState<Record<string, AsrTranscriptState>>({});
const [asrOpen, setAsrOpen] = useState(true);

// Preprocessed data per config
const [preprocessedSamples, setPreprocessedSamples] = useState<Record<string, number[]>>({});
const [preprocessedSpectrumData, setPreprocessedSpectrumData] = useState<
Expand All @@ -217,6 +234,18 @@ function App() {
localStorage.setItem("lab-pipeline-configs", JSON.stringify(pipelineConfigs));
}, [pipelineConfigs]);

// Persist asr configs to localStorage
useEffect(() => {
localStorage.setItem("lab-asr-configs", JSON.stringify(asrConfigs));
}, [asrConfigs]);

// Push asr config changes to the backend so the next start picks them up
useEffect(() => {
const socket = socketRef.current;
if (!socket || !connected) return;
socket.send({ type: "set_asr_configs", configs: asrConfigs });
}, [asrConfigs, connected]);

// Resolve playback samples based on selected source
const playbackSamples =
playbackSource === "original" ? samples : (preprocessedSamples[playbackSource] ?? []);
Expand Down Expand Up @@ -261,6 +290,7 @@ function App() {
socket.send({ type: "list_devices" });
socket.send({ type: "list_backends" });
socket.send({ type: "list_turn_backends" });
socket.send({ type: "list_asr_backends" });
}, []);

const handleMessage = useCallback((msg: ServerMessage) => {
Expand Down Expand Up @@ -433,6 +463,59 @@ function App() {
}));
break;

case "asr_backends":
setAsrBackends(msg.backends);
break;

case "asr":
setAsrTranscripts((prev) => {
const existing: AsrTranscriptState = prev[msg.config_id] ?? {
ready: false,
finals: [],
partial: null,
warning: null,
};
switch (msg.kind) {
case "ready":
return {
...prev,
[msg.config_id]: { ...existing, ready: true, warning: null },
};
case "partial":
return {
...prev,
[msg.config_id]: { ...existing, partial: msg.text ?? null },
};
case "final":
return {
...prev,
[msg.config_id]: {
...existing,
partial: null,
finals: [
...existing.finals,
{
ts_ms: msg.ts_ms ?? 0,
end_ms: msg.end_ms ?? msg.ts_ms ?? 0,
text: msg.text ?? "",
confidence: msg.confidence ?? 1,
},
],
},
};
case "warning":
return {
...prev,
[msg.config_id]: { ...existing, warning: msg.message ?? null },
};
case "speech_started":
case "speech_ended":
default:
return prev;
}
});
break;

case "done":
recordingRef.current = false;
setRecording(false);
Expand Down Expand Up @@ -488,6 +571,7 @@ function App() {
setTurnResults({});
setTurnTiming({});
setPipelineResults({});
setAsrTranscripts({});
setPlaybackSource("original");
setTotalDurationMs(0);
setSampleRate(null);
Expand All @@ -504,6 +588,7 @@ function App() {
socket.send({ type: "set_configs", configs });
socket.send({ type: "set_turn_configs", configs: turnConfigs });
socket.send({ type: "set_pipeline_configs", configs: pipelineConfigs });
socket.send({ type: "set_asr_configs", configs: asrConfigs });
socket.send({
type: "start_recording",
device_index: parseInt(selectedDevice),
Expand Down Expand Up @@ -537,6 +622,7 @@ function App() {
setTurnResults({});
setTurnTiming({});
setPipelineResults({});
setAsrTranscripts({});
setPlaybackSource("original");
setTotalDurationMs(0);
setSampleRate(null);
Expand All @@ -545,6 +631,7 @@ function App() {
socket.send({ type: "set_configs", configs });
socket.send({ type: "set_turn_configs", configs: turnConfigs });
socket.send({ type: "set_pipeline_configs", configs: pipelineConfigs });
socket.send({ type: "set_asr_configs", configs: asrConfigs });
socket.send({ type: "load_file", path, channel });
setLoadingFile(true);
};
Expand Down Expand Up @@ -979,6 +1066,10 @@ function App() {
/>
))}

{asrConfigs.length > 0 && (
<AsrTranscript configs={asrConfigs} states={asrTranscripts} />
)}

{/* Preprocessed Waveforms/Spectrograms/VAD - only for configs with showPreprocessed enabled */}
{vadOpen && configs.filter((c) => showPreprocessed[c.id]).map((config) => {
const configIndex = configs.findIndex((c) => c.id === config.id);
Expand Down Expand Up @@ -1149,6 +1240,36 @@ function App() {
)}
</div>

{/* ASR Config Panel */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<button
className="flex items-center gap-1 text-sm font-medium text-left"
onClick={() => setAsrOpen((v) => !v)}
>
<span className="text-muted-foreground text-xs">{asrOpen ? "\u25BC" : "\u25B6"}</span>
ASR
</button>
</div>
{asrOpen && (
<AsrConfigPanel
configs={asrConfigs}
backends={asrBackends}
onConfigsChange={setAsrConfigs}
onResetDefaults={() => {
const backendNames = Object.keys(asrBackends);
if (backendNames.length === 0) return;
const backend = backendNames[0];
const params: Record<string, unknown> = {};
for (const p of asrBackends[backend]) {
params[p.name] = p.default;
}
setAsrConfigs([{ id: "asr-1", label: "asr-1", backend, params }]);
}}
/>
)}
</div>

<Separator />

{/* Logs */}
Expand Down
Loading
Loading