Skip to content

Preserve Gemini speaker turns in mixed-audio transcripts - #8127

Open
ilyakarakotov wants to merge 1 commit into
fastrepl:mainfrom
ilyakarakotov:fix/gemini-mixed-audio-diarization
Open

ilyakarakotov wants to merge 1 commit into
fastrepl:mainfrom
ilyakarakotov:fix/gemini-mixed-audio-diarization

Conversation

@ilyakarakotov

@ilyakarakotov ilyakarakotov commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Intent: Gemini batch words were treated as personal-microphone audio, collapsing distinct speakers into the owner. Preserve speaker turns for new recordings and recover saved Gemini transcripts without retranscription.

Preserve manual speaker assignments and verified source-channel restoration. Normalize legacy source transcripts before batch refinement so retranscription cannot restore the same incorrect personal-microphone attribution.

Demo

Synthetic regression fixture: before and after

Synthetic reproduction: Speaker 0: Hello. followed by Speaker 1: Hi. previously rendered as a single owner block on channel 0. The corrected render input uses mixed capture and retains both speaker labels. Regression tests also check that live microphone channels remain unchanged and manual speaker assignments survive.

Verification

  • Gemini adapter unit tests: 14 passed.
  • Transcript crate tests: 163 passed.
  • Focused desktop query/render tests: 53 passed.
  • Desktop typecheck, frontend production build, formatting, ESLint, license-boundary checks, and repository script tests passed.
  • Desktop suite: 4,194 passed; three failures caused by the local timezone/production API environment all passed in a targeted rerun with UTC and the default development API URL.
  • Native macOS 1.4.31 app bundle built and signature verified; persistence regression tests: 9 passed; database crate tests: 278 passed.
  • Latest GitHub CI, desktop JS/i18n, CLI, mobile, lint, formatting, PR description, and workflow-security checks passed.
  • End-to-end local UI verification of the patch was not completed: macOS blocks the ad hoc-signed database extension. The user chose to restore the official notarized app rather than relax local library validation. No signing exception or entitlement change is included in this PR; the upstream release uses the project's Developer ID.
  • The offline workflow scan reports 335 pre-existing findings in untouched workflows; no workflow files changed.

Summary by cubic

Fixes Gemini batch transcripts being treated as personal-microphone audio, which collapsed all speaker turns into a single owner block and lost speaker labels.

  • New Gemini batch output is marked as mixed capture (channel 2) instead of personal mic (channel 0).
  • Existing affected transcripts are repaired in memory at render time, without rewriting saved words or requiring retranscription.
  • Legacy transcripts are normalized before batch refinement so retranscription cannot restore the incorrect personal-mic attribution.
  • Manual speaker assignments and verified source-channel restorations are preserved.

Written for commit 3629f22. Summary will update on new commits.

View guided diff

@ilyakarakotov
ilyakarakotov marked this pull request as ready for review October 21, 2026 04:58
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T05:07:44.620501Z 3629f22 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Devin Review

Comment on lines +282 to +298
const legacyGeminiMix =
transcript.source === "batch_transcription" &&
transcript.provider === "google_generative_ai" &&
words.length > 0 &&
words.every((word) => word.channel === 0) &&
!(transcript.speaker_hints ?? []).some((hint) => {
const value = parseHintValue(hint.value);
return (
hint.type === "provider_speaker_index" &&
value != null &&
typeof value === "object" &&
(value as { source_channel_restored?: unknown })
.source_channel_restored === true
);
});
if (legacyGeminiMix) {
for (const word of words) word.channel = 2;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Legacy speaker turns remain collapsed in summaries

For saved Gemini batches, renderSessionTranscript still treats channel-zero words as personal-mic audio. Its native renderer bypasses this repair, so summaries and chat still lose distinct speaker turns.

Learn more

The desktop renderer now repairs channel-zero words from older Gemini batch transcripts before invoking the shared segment renderer. render_session_transcript instead reads saved words and hints directly and calls render_input_from_stored without checking the transcript's source or provider. buildTranscript consumes that native result for chat; summary generation calls the same command. render_transcripts_markdown also renders stored words without this repair. The UI and these consumers therefore disagree on the same transcript.

Example: A saved Gemini batch contains Hello on channel 0 with speaker 0 and Hi on channel 0 with speaker 1. The desktop view repairs both to mixed capture, but chat and summary requests still identify the channel as DirectMic and can attribute both turns to the owner.

Recommended fix: Share the legacy normalization across native rendering and desktop rendering, preserving verified source-channel restorations and translating channel-scoped assignments. Pass source/provider through the native session-render query and cover its rendered segments with a saved legacy Gemini fixture; apply the same normalization to agent-access exports.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +213 to +220
if let Some(mut value) = hint
.value
.as_str()
.and_then(|value| serde_json::from_str::<serde_json::Value>(value).ok())
&& value.get("channel").and_then(serde_json::Value::as_f64) == Some(0.0)
{
value["channel"] = serde_json::json!(2);
hint.value = serde_json::Value::String(value.to_string());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Object-valued hints undo mixed-channel repair

When legacy Gemini hints contain JSON objects, materialize_source_row changes words to channel 2 but leaves hint channels at 0. Refinement reapplies those hints, so speaker assignments can be lost during retranscription.

Learn more

Stored speaker hints can hold either a JSON string or a JSON object; parse_stored_speaker_hints accepts both. This branch rewrites the channel only when hint.value.as_str() succeeds. An object-valued provider hint stays on channel 0 while its associated word changes to 2. Later, normalize_speaker_hint overwrites the normalized word channel with the unchanged provider hint; explicit user scopes also remain on channel 0.

Example: An imported legacy Gemini word on channel 0 has an object-valued provider hint { "channel": 0, "speaker_index": 1 }. Before refinement the word becomes channel 2, but the unchanged hint puts it back on channel 0 when speaker labels are reconciled.

Recommended fix: Parse both object and string hint values when detecting restoration markers and remapping channels. Preserve the existing value representation if needed, and test a source row with object-valued provider and user hints.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3629f2244d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +290 to +294
hint.type === "provider_speaker_index" &&
value != null &&
typeof value === "object" &&
(value as { source_channel_restored?: unknown })
.source_channel_restored === true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record channel restoration without requiring a speaker hint

When a current_capture Gemini refinement returns timestamped words but no speaker labels, restore_refined_source_channels can legitimately restore every word to channel 0, but restore_refined_source_hints has no provider_speaker_index hint on which to write source_channel_restored. This predicate therefore reclassifies the saved words as MixedCapture on the next render (and materialize_source_row repeats it before a later refinement), undoing the verified DirectMic restoration and owner attribution. Persist this provenance independently of optional diarization hints and add a regression for the no-label current-capture path.

AGENTS.md reference: AGENTS.md:L95-L95

Useful? React with 👍 / 👎.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant