Skip to content

Add bidirectional realtime translation#132

Merged
tleyden merged 8 commits into
mainfrom
bidirectional_realtime_translation
May 11, 2026
Merged

Add bidirectional realtime translation#132
tleyden merged 8 commits into
mainfrom
bidirectional_realtime_translation

Conversation

@tleyden
Copy link
Copy Markdown
Owner

@tleyden tleyden commented May 10, 2026

Screenshot 2026-05-11 at 12 32 34 AM

Only works when using Near Field noise reduction, so I made that the default setting

Summary by CodeRabbit

  • New Features

    • Bidirectional translation mode with selectable reverse-language and toggle in Advanced Settings.
    • Configurable noise-reduction selection for translation sessions.
  • Documentation

    • README updated: explicit "Bring Your Own OpenAI API Key" requirement and clarified bidirectional translator/interpreter wording.
  • Behavior

    • Default translation noise reduction set to "near_field" (improves audio handling).

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e021bd3f-5932-4aa7-b92b-80c08e482ddc

📥 Commits

Reviewing files that changed from the base of the PR and between b89237e and ef39a0f.

📒 Files selected for processing (2)
  • app/RealtimeTranslation.tsx
  • components/realtime/AdvancedPanel.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • components/realtime/AdvancedPanel.tsx
  • app/RealtimeTranslation.tsx

📝 Walkthrough

Walkthrough

Adds bidirectional translation: persisted settings and UI, RealtimeTranslation and AdvancedPanel wiring, TypeScript contract updates, and iOS WebRTC dual-client orchestration with per-stream role and noise-reduction config.

Changes

Bidirectional Translation Feature

Layer / File(s) Summary
Settings Persistence
lib/translationSettings.ts
New bidirectional defaults and AsyncStorage load/save helpers; default noise reduction changed to near_field.
Type Contracts
modules/vm-webrtc/src/VmWebrtcTranslatorModule.ts
TranslationConnectionOptions adds bidirectionalLanguage; TranslationTranscriptEventPayload adds optional role (`"primary"
iOS WebRTC Client
modules/vm-webrtc/ios/OpenAIWebRTCCore.swift, OpenAIWebRTCTranslatorClient.swift, WebRtcClientHelpers.swift
Added role and noiseReductionType handling; transcript deltas now include role; skipAudioSession guard prevents audio-session ops for secondary clients.
iOS Dual-Stream Module
modules/vm-webrtc/ios/WebRTCTranslatorModule.swift
Refactored to primaryClient + lazy reverseClient; both emit events; open primary then optionally reverse (reverse uses skipAudioSession); close reverse before primary; mute/unmute applies to both.
Configuration UI
components/settings/ConfigureTranslation.tsx
Added Bidirectional selection card and modal, persisted load/save, selected-language state, and onBidirectionalLanguageChange callback.
Advanced Panel Controls
components/realtime/AdvancedPanel.tsx
Accepts isBidirectional, bidirectionalLanguage, and onToggleBidirectional; renders gated Switch and helper text with new styles.
Realtime Translation Component
app/RealtimeTranslation.tsx
Adds bidirectionalLanguage prop and local bidirectional state; loads persisted settings; computes/logs intendedStreams; conditionally includes bidirectionalLanguage in connection options; transcript logging includes role; toggle persists enabled state.
App Integration
app/index.tsx
Adds bidirectionalLanguage state initialized/hydrated from storage; passes into RealtimeTranslation and wires ConfigureTranslation callback.
Documentation
README.md
Features list updated to mention bi-directional translations; requirement text clarified to "Bring Your Own OpenAI API Key".

Sequence Diagram

sequenceDiagram
  participant User
  participant App as App (index.tsx)
  participant ConfigUI as ConfigureTranslation
  participant RTComponent as RealtimeTranslation
  participant AdvPanel as AdvancedPanel
  participant Settings as translationSettings
  participant iOS as iOS Module (primaryClient, reverseClient)

  User->>ConfigUI: Opens settings
  ConfigUI->>Settings: loadBidirectionalLanguage
  Settings-->>ConfigUI: persisted language
  User->>ConfigUI: Selects language
  ConfigUI->>Settings: saveBidirectionalLanguage
  ConfigUI->>App: onBidirectionalLanguageChange
  App->>App: update bidirectionalLanguage state

  User->>RTComponent: Start translation session
  RTComponent->>Settings: loadBidirectionalEnabled
  Settings-->>RTComponent: persisted enabled flag
  RTComponent->>RTComponent: compute intendedStreams
  RTComponent->>iOS: openTranslationConnectionAsync(..., bidirectionalLanguage?)
  iOS->>iOS: open primaryClient(outputLanguage)
  iOS->>iOS: open reverseClient(bidirectionalLanguage, skipAudioSession=true)
  iOS-->>RTComponent: connection established

  AdvPanel->>RTComponent: onToggleBidirectional
  RTComponent->>Settings: saveBidirectionalEnabled
  iOS->>RTComponent: emit transcript delta (role=primary/reverse)
  RTComponent->>User: display transcript
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • tleyden/arty#128: Extends the same realtime-translation codepaths with bidirectional-language, role/noise-reduction configuration, and dual-stream orchestration.
  • tleyden/arty#77: Modifies vm-webrtc transcript event pipeline and related event payload handling.

Poem

🐰 I hop between two tongues today,
Primary speaks, reverse finds its way,
One session shared, two voices sing,
Bidirectional cheer—let translations spring!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add bidirectional realtime translation' directly and clearly describes the primary feature addition across the entire changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bidirectional_realtime_translation

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

OSSF Scorecard (PR vs base)

  • Base score: 4.4
  • PR score: 4.4
  • Change: 0.00 (unchanged)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/RealtimeTranslation.tsx (2)

243-302: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce near_field before opening a bidirectional session.

Per the PR objective, bidirectional translation only works with Near Field noise reduction. This path still opens bidirectional mode with whatever loadTranslationNoiseReductionType() returns, so a user can switch to far_field or disabled and land in a broken/echo-prone session. Guard this before openTranslationConnectionAsync() or force the supported mode.

Possible guard
       const [noiseReductionType, transcriptionEnabled, transcriptionModel] =
         await Promise.all([
           loadTranslationNoiseReductionType(),
           loadTranslationInputTranscriptionEnabled(),
           loadTranslationInputTranscriptionModel(),
         ]);
+
+      if (isBidirectional && noiseReductionType !== "near_field") {
+        Alert.alert(
+          "Bidirectional translation",
+          "Bidirectional mode requires Near Field noise reduction. Update this in Configure before starting the session.",
+        );
+        return;
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/RealtimeTranslation.tsx` around lines 243 - 302, The bidirectional flow
must enforce Near Field noise reduction before opening the connection: after
loading noiseReductionType (from loadTranslationNoiseReductionType()) and before
calling openTranslationConnectionAsync(), check if isBidirectional is true and
noiseReductionType !== "near_field" and then either (a) override
noiseReductionType to "near_field" and log an info/warn noting the enforced
change, or (b) reject/start-fail with a clear error log and avoid calling
openTranslationConnectionAsync(); apply this change around the existing symbols
noiseReductionType, isBidirectional, and openTranslationConnectionAsync so the
connection is only opened with near_field when bidirectional is requested.

141-177: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Route transcript deltas by payload.role.

In bidirectional mode, these listeners still append every delta into the same inputTranscript / outputTranscript buffers even though the payload now carries primary vs secondary. That will interleave the forward and reverse conversations into one transcript stream. Please keep separate state per role, or explicitly ignore the secondary stream until the UI can render it cleanly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/RealtimeTranslation.tsx` around lines 141 - 177, The
onTranslationInputTranscript and onTranslationOutputTranscript listeners
currently append every payload.delta into the shared
setInputTranscript/setOutputTranscript buffers regardless of payload.role;
update those listeners to route by payload.role (e.g., check payload.role ===
"primary" vs "secondary") and either (a) only append to the existing primary
buffers when role === "primary" (ignore secondary until UI supports it) or (b)
maintain separate state vars (e.g., inputTranscriptPrimary,
inputTranscriptSecondary, outputTranscriptPrimary, outputTranscriptSecondary)
and update the appropriate setter based on payload.role; keep the same separator
logic and call resetIdleTimer as before, and refer to
VmWebrtcTranslatorModule.addListener, onTranslationInputTranscript,
onTranslationOutputTranscript, setInputTranscript, setOutputTranscript, and
TranslationTranscriptEventPayload when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/RealtimeTranslation.tsx`:
- Around line 373-376: The bidirectional toggle currently only updates state via
handleToggleBidirectional (setIsBidirectional + saveBidirectionalEnabled) but
does not reconfigure the live native connection, so it must not imply immediate
effect during an active session; update the UI logic to disable the switch when
isSessionActive is true (or show a persistent inline message like "applies next
session") and ensure handleToggleBidirectional remains a no-op for live
reconfigure; locate the toggle rendering and references around
handleToggleBidirectional and the related UI code (also change the same behavior
in the code block around lines where the other toggle logic lives ~401-409) to
prevent changing the control while isSessionActive.

---

Outside diff comments:
In `@app/RealtimeTranslation.tsx`:
- Around line 243-302: The bidirectional flow must enforce Near Field noise
reduction before opening the connection: after loading noiseReductionType (from
loadTranslationNoiseReductionType()) and before calling
openTranslationConnectionAsync(), check if isBidirectional is true and
noiseReductionType !== "near_field" and then either (a) override
noiseReductionType to "near_field" and log an info/warn noting the enforced
change, or (b) reject/start-fail with a clear error log and avoid calling
openTranslationConnectionAsync(); apply this change around the existing symbols
noiseReductionType, isBidirectional, and openTranslationConnectionAsync so the
connection is only opened with near_field when bidirectional is requested.
- Around line 141-177: The onTranslationInputTranscript and
onTranslationOutputTranscript listeners currently append every payload.delta
into the shared setInputTranscript/setOutputTranscript buffers regardless of
payload.role; update those listeners to route by payload.role (e.g., check
payload.role === "primary" vs "secondary") and either (a) only append to the
existing primary buffers when role === "primary" (ignore secondary until UI
supports it) or (b) maintain separate state vars (e.g., inputTranscriptPrimary,
inputTranscriptSecondary, outputTranscriptPrimary, outputTranscriptSecondary)
and update the appropriate setter based on payload.role; keep the same separator
logic and call resetIdleTimer as before, and refer to
VmWebrtcTranslatorModule.addListener, onTranslationInputTranscript,
onTranslationOutputTranscript, setInputTranscript, setOutputTranscript, and
TranslationTranscriptEventPayload when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bcfb229f-e010-4cf6-a753-117fca774865

📥 Commits

Reviewing files that changed from the base of the PR and between 6d7f910 and b89237e.

📒 Files selected for processing (11)
  • README.md
  • app/RealtimeTranslation.tsx
  • app/index.tsx
  • components/realtime/AdvancedPanel.tsx
  • components/settings/ConfigureTranslation.tsx
  • lib/translationSettings.ts
  • modules/vm-webrtc/ios/OpenAIWebRTCCore.swift
  • modules/vm-webrtc/ios/OpenAIWebRTCTranslatorClient.swift
  • modules/vm-webrtc/ios/WebRTCTranslatorModule.swift
  • modules/vm-webrtc/ios/WebRtcClientHelpers.swift
  • modules/vm-webrtc/src/VmWebrtcTranslatorModule.ts

Comment on lines +373 to +376
const handleToggleBidirectional = useCallback((nextValue: boolean) => {
setIsBidirectional(nextValue);
void saveBidirectionalEnabled(nextValue);
}, []);
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't let the bidirectional switch imply a live reconfigure.

handleToggleBidirectional() only updates React state and persisted prefs; it never reopens or reconfigures the current native connection. During an active session, the switch can move to ON/OFF while the live stream topology stays unchanged until the next start. Disable this control while isSessionActive, or show explicit "applies next session" feedback.

Also applies to: 401-409

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/RealtimeTranslation.tsx` around lines 373 - 376, The bidirectional toggle
currently only updates state via handleToggleBidirectional (setIsBidirectional +
saveBidirectionalEnabled) but does not reconfigure the live native connection,
so it must not imply immediate effect during an active session; update the UI
logic to disable the switch when isSessionActive is true (or show a persistent
inline message like "applies next session") and ensure handleToggleBidirectional
remains a no-op for live reconfigure; locate the toggle rendering and references
around handleToggleBidirectional and the related UI code (also change the same
behavior in the code block around lines where the other toggle logic lives
~401-409) to prevent changing the control while isSessionActive.

@tleyden tleyden merged commit 3b1a0b7 into main May 11, 2026
4 checks passed
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