Stream transcript segments to frontend as they are transcribed
Send each segment to the frontend immediately after transcription via a new pipeline.segment IPC message, then send speaker assignments as a batch pipeline.speaker_update message after diarization completes. This lets the UI display segments progressively instead of waiting for the entire pipeline to finish. Changes: - Add partial_segment_message and speaker_update_message IPC factories - Add on_segment callback parameter to TranscribeService.transcribe() - Emit partial segments and speaker updates from PipelineService.run() - Add send_and_receive_with_progress to SidecarManager (Rust) - Route pipeline.segment/speaker_update events in run_pipeline command - Listen for streaming events in Svelte frontend (+page.svelte) - Add tests for new message types, callback signature, and update logic Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -165,6 +165,70 @@ impl SidecarManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a message and receive the response, calling a callback for intermediate messages.
|
||||
/// Intermediate messages include progress, pipeline.segment, and pipeline.speaker_update.
|
||||
pub fn send_and_receive_with_progress<F>(
|
||||
&self,
|
||||
msg: &IPCMessage,
|
||||
on_intermediate: F,
|
||||
) -> Result<IPCMessage, String>
|
||||
where
|
||||
F: Fn(&IPCMessage),
|
||||
{
|
||||
// Write to stdin
|
||||
{
|
||||
let mut stdin_guard = self.stdin.lock().map_err(|e| e.to_string())?;
|
||||
if let Some(ref mut stdin) = *stdin_guard {
|
||||
let json = serde_json::to_string(msg).map_err(|e| e.to_string())?;
|
||||
stdin
|
||||
.write_all(json.as_bytes())
|
||||
.map_err(|e| format!("Write error: {e}"))?;
|
||||
stdin
|
||||
.write_all(b"\n")
|
||||
.map_err(|e| format!("Write error: {e}"))?;
|
||||
stdin.flush().map_err(|e| format!("Flush error: {e}"))?;
|
||||
} else {
|
||||
return Err("Sidecar stdin not available".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Read from stdout
|
||||
{
|
||||
let mut reader_guard = self.reader.lock().map_err(|e| e.to_string())?;
|
||||
if let Some(ref mut reader) = *reader_guard {
|
||||
let mut line = String::new();
|
||||
loop {
|
||||
line.clear();
|
||||
let bytes_read = reader
|
||||
.read_line(&mut line)
|
||||
.map_err(|e| format!("Read error: {e}"))?;
|
||||
if bytes_read == 0 {
|
||||
return Err("Sidecar closed stdout".to_string());
|
||||
}
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let response: IPCMessage = serde_json::from_str(trimmed)
|
||||
.map_err(|e| format!("Parse error: {e}"))?;
|
||||
|
||||
// Forward intermediate messages via callback, return the final result/error
|
||||
let is_intermediate = matches!(
|
||||
response.msg_type.as_str(),
|
||||
"progress" | "pipeline.segment" | "pipeline.speaker_update"
|
||||
);
|
||||
if is_intermediate {
|
||||
on_intermediate(&response);
|
||||
} else {
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err("Sidecar stdout not available".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop the sidecar process.
|
||||
pub fn stop(&self) -> Result<(), String> {
|
||||
// Drop stdin to signal EOF
|
||||
|
||||
Reference in New Issue
Block a user