Add post-diarization speaker review workflow
This commit is contained in:
@@ -92,6 +92,8 @@ class ProcessingOutcome:
|
||||
failed_stage: str | None = None
|
||||
error_message: str | None = None
|
||||
speaker_attribution_available: bool | None = None
|
||||
awaiting_speaker_review: bool = False
|
||||
detected_speaker_count: int | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -249,6 +251,7 @@ class MeetingProcessingService:
|
||||
"diarization_runtime": self.settings.diarization_runtime,
|
||||
"diarization_container_image": (self.settings.diarization_container_image),
|
||||
"diarization_container_args": self.settings.diarization_container_args,
|
||||
"stop_after_diarization": options.diarization_enabled,
|
||||
}
|
||||
)
|
||||
current_stage: str | None = None
|
||||
@@ -299,6 +302,15 @@ class MeetingProcessingService:
|
||||
failed_stage=failed_stage or current_stage or "preparing",
|
||||
error_message=failure_message,
|
||||
)
|
||||
if result.protocol_path is None:
|
||||
return ProcessingOutcome(
|
||||
succeeded=True,
|
||||
run_dir=result.run_dir,
|
||||
original_protocol=None,
|
||||
protocol_path=None,
|
||||
awaiting_speaker_review=True,
|
||||
detected_speaker_count=self._detected_speaker_count(result.run_dir),
|
||||
)
|
||||
protocol_path = Path(result.protocol_path)
|
||||
return ProcessingOutcome(
|
||||
succeeded=True,
|
||||
@@ -401,6 +413,13 @@ class MeetingProcessingService:
|
||||
context_data["speaker_mappings"] = dict(sorted(speaker_mappings.items()))
|
||||
self._apply_glossary(context_data)
|
||||
context = self.meeting_lab.create_context(context_data)
|
||||
# A checkpoint mapping is user input, not a derived protocol artifact. Save it
|
||||
# before inference so a failed generation remains retryable with the same review.
|
||||
if context_path.is_symlink():
|
||||
context_path.unlink()
|
||||
context_path.write_text(
|
||||
yaml.safe_dump(context.data, allow_unicode=True, sort_keys=False), encoding="utf-8"
|
||||
)
|
||||
|
||||
def relay(event: Any) -> None:
|
||||
self._update_timer(event)
|
||||
@@ -432,6 +451,7 @@ class MeetingProcessingService:
|
||||
self.timer.finish_run()
|
||||
raise
|
||||
self.timer.finish_run()
|
||||
self._persist_timing(Path(result.run_dir))
|
||||
protocol_path = Path(result.protocol_path)
|
||||
return ProcessingOutcome(
|
||||
succeeded=True,
|
||||
@@ -473,6 +493,18 @@ class MeetingProcessingService:
|
||||
value = metadata.get("speaker_attribution_available")
|
||||
return value if isinstance(value, bool) else None
|
||||
|
||||
@staticmethod
|
||||
def _detected_speaker_count(run_dir: Path | None) -> int | None:
|
||||
if run_dir is None:
|
||||
return None
|
||||
metadata_path = Path(run_dir) / "run_metadata.json"
|
||||
try:
|
||||
metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return None
|
||||
value = metadata.get("diarization", {}).get("speaker_count")
|
||||
return value if isinstance(value, int) else None
|
||||
|
||||
@staticmethod
|
||||
def _read_failure(run_dir: Path | None) -> dict[str, str]:
|
||||
if run_dir is None:
|
||||
|
||||
+25
-16
@@ -554,7 +554,15 @@ def _render_result() -> None:
|
||||
st.caption("Intermediate artifacts and run metadata were preserved here.")
|
||||
return
|
||||
|
||||
st.success("Processing completed. Review the generated protocol before use.")
|
||||
awaiting_review = getattr(outcome, "awaiting_speaker_review", False)
|
||||
if awaiting_review:
|
||||
count = getattr(outcome, "detected_speaker_count", None)
|
||||
detected = f"{count} speakers detected" if count is not None else "speakers detected"
|
||||
st.info(
|
||||
f"Diarization complete — {detected}. Assign speakers if desired, then generate the protocol."
|
||||
)
|
||||
else:
|
||||
st.success("Processing completed. Review the generated protocol before use.")
|
||||
st.caption(f"Run artifacts: {outcome.run_dir}")
|
||||
if outcome.speaker_attribution_available is False:
|
||||
st.warning(
|
||||
@@ -563,17 +571,18 @@ def _render_result() -> None:
|
||||
)
|
||||
if message := st.session_state.pop("speaker_mapping_message", None):
|
||||
st.success(message)
|
||||
with st.expander("Original generated protocol", expanded=False):
|
||||
st.markdown(outcome.original_protocol or "")
|
||||
edited = st.text_area(
|
||||
"Editable protocol",
|
||||
key="edited_protocol",
|
||||
height=500,
|
||||
help="The original protocol.md remains unchanged.",
|
||||
)
|
||||
if st.button("Save edited protocol", type="primary"):
|
||||
path = MeetingProcessingService.save_edited_protocol(outcome.run_dir, edited)
|
||||
st.success(f"Saved edited protocol to {path}")
|
||||
if not awaiting_review:
|
||||
with st.expander("Original generated protocol", expanded=False):
|
||||
st.markdown(outcome.original_protocol or "")
|
||||
edited = st.text_area(
|
||||
"Editable protocol",
|
||||
key="edited_protocol",
|
||||
height=500,
|
||||
help="The original protocol.md remains unchanged.",
|
||||
)
|
||||
if st.button("Save edited protocol", type="primary"):
|
||||
path = MeetingProcessingService.save_edited_protocol(outcome.run_dir, edited)
|
||||
st.success(f"Saved edited protocol to {path}")
|
||||
|
||||
try:
|
||||
service = MeetingProcessingService(AppSettings.from_environment(), MeetingLabGateway())
|
||||
@@ -590,7 +599,7 @@ def _render_result() -> None:
|
||||
if duplicate_assignments:
|
||||
st.error("Each participant can be assigned to only one detected speaker.")
|
||||
if st.button(
|
||||
"Regenerate protocol with confirmed speakers",
|
||||
"Generate protocol" if awaiting_review else "Regenerate protocol with confirmed speakers",
|
||||
disabled=duplicate_assignments,
|
||||
type="primary",
|
||||
):
|
||||
@@ -610,7 +619,7 @@ def _render_result() -> None:
|
||||
performance_profile=selected_profile,
|
||||
),
|
||||
states,
|
||||
"Starting protocol regeneration",
|
||||
"Starting protocol generation" if awaiting_review else "Starting protocol regeneration",
|
||||
)
|
||||
except (OSError, RuntimeError, ValueError) as exc:
|
||||
_remember_regeneration_timing(
|
||||
@@ -618,13 +627,13 @@ def _render_result() -> None:
|
||||
"Protocol regeneration failed",
|
||||
service.timer.snapshot(),
|
||||
)
|
||||
st.error(f"Protocol regeneration failed: {exc}")
|
||||
st.error(f"Protocol generation failed: {exc}")
|
||||
else:
|
||||
_remember_regeneration_timing(states, message, timing)
|
||||
st.session_state.outcome = regenerated
|
||||
_queue_edited_protocol(regenerated.original_protocol or "")
|
||||
st.session_state.speaker_mapping_message = (
|
||||
"Speaker mappings saved and protocol regenerated."
|
||||
"Speaker mappings saved and protocol generated."
|
||||
)
|
||||
st.rerun()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user