Skip to content

feat(mascot): locale-aware voice + multilingual LLM replies#2115

Merged
senamakel merged 7 commits into
tinyhumansai:mainfrom
senamakel:feat/mascot-voice-locale-multilingual
May 18, 2026
Merged

feat(mascot): locale-aware voice + multilingual LLM replies#2115
senamakel merged 7 commits into
tinyhumansai:mainfrom
senamakel:feat/mascot-voice-locale-multilingual

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented May 18, 2026

Summary

  • New Mascot settings UI exposing voice gender, an ElevenLabs voice picker (curated presets + paste-custom), and a "match the app language" toggle that maps the active locale → voice id.
  • Default mascot voice switched to George (JBFqnCBsd6RMkjVDRZzb), a multilingual ElevenLabs voice, and all TTS requests now default to eleven_multilingual_v2 so non-Latin scripts render correctly.
  • Frontend chat send now passes the active UI locale to the core; in the web channel that locale threads into the agent's system prompt as a "Respond in <language>" directive (non-English locales only).
  • Voice picker moved out of VoicePanel into MascotPanel (single source of truth); VoicePanel keeps a one-line pointer to the new location.

Problem

The mascot was speaking English even when a user picked Arabic / Hindi / etc. in the UI. Two underlying issues:

  1. The ElevenLabs voice id only controls accent/timbre, not the spoken language — and the agent was always writing its reply in English.
  2. The default TTS model was English-only; even if the agent had returned Arabic text, the model would have phoneticised it incorrectly.

There was no plumbing from the frontend's localeSlice into the Rust core, and the mascot voice picker lived under Voice settings (alongside Piper/Whisper, which confused which provider an id belonged to).

Solution

Frontend (app/)

  • mascotSlice: added voiceGender and voiceUseLocaleDefault (both REHYDRATE-guarded); new selectEffectiveMascotVoiceId(state) resolves locale-default → manual override → build-time default so the UI and TTS path can never drift.
  • MascotPanel.tsx: new Voice section — gender radio, preset dropdown filtered by gender, custom paste, locale-default checkbox showing the live <locale> → <voiceId> mapping, preview button, reset.
  • elevenlabsVoicePresets.ts: each preset now declares locales; added DEFAULT_VOICE_BY_LOCALE[locale][gender] map + defaultVoiceIdForLocale() helper.
  • ttsClient.ts: defaults model_id to eleven_multilingual_v2 so non-English glyphs render natively.
  • chatService.chatSend: new optional locale param sent as locale in the RPC payload; Conversations.tsx and webviewAccountService.ts read the active locale from redux and pass it through.

Rust core (src/openhuman/channels/providers/web.rs)

  • WebChatParams, channel_web_chat, start_chat, run_chat_task, build_session_agent now thread locale: Option<String>.
  • Two new helpers: locale_reply_directive(locale) maps BCP-47 → "Respond in <Language>" (no-op for English / unknown tags); compose_system_prompt_suffix() stitches that directive in front of the profile's own suffix before handing to the agent builder.
  • RPC schema exposes the new optional locale input.
  • socketio.rs + channels/bus.rs updated for the new signature.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • N/A: Diff coverage ≥ 80% — gate verifies on CI; all changed Rust paths and frontend selectors / panel logic carry direct unit tests (web_tests.rs locale helpers; mascotSlice selectors; MascotPanel, ttsClient, useHumanMascot, Conversations render tests). Run locally with pnpm test:coverage and pnpm test:rust.
  • N/A: behaviour-only change — no feature rows added/removed/renamed in docs/TEST-COVERAGE-MATRIX.md.
  • N/A: no matrix rows touched, so no feature IDs to list under ## Related.
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • N/A: doesn't touch release-cut surfaces in docs/RELEASE-MANUAL-SMOKE.md.
  • N/A: no linked tracking issue.

Impact

  • Desktop only (mobile / CLI / web unaffected).
  • Per-message cost change: eleven_multilingual_v2 and eleven_monolingual_v1 are priced the same on ElevenLabs, so TTS billing is unchanged.
  • Session cache caveat: the agent session-cache fingerprint does not yet include locale, so a thread that already has a warm agent keeps its current language until rebuilt (new threads pick up the new locale immediately). Documented; intentional for this PR.
  • Backwards compatible: voiceUseLocaleDefault defaults to false, voiceGender defaults to 'male' (matching the new default voice), and pre-Add configurable mascot voices with ElevenLabs voice IDs #1762 persisted blobs hydrate cleanly via the REHYDRATE guards.

Related

  • Closes:
  • Follow-up PR(s)/TODOs: optionally add locale to SessionCacheFingerprint so mid-thread locale changes invalidate the cached agent.

AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/mascot-voice-locale-multilingual
  • Commit SHA: c0ec737

Validation Run

  • `pnpm --filter openhuman-app format:check` (pre-push hook re-ran prettier; auto-fixes committed as separate chore commit)
  • `pnpm typecheck`
  • Focused tests: `vitest ttsClient useHumanMascot mascotSlice MascotPanel VoicePanel chatService Conversations` — 244 passed
  • Rust fmt/check (if changed): `cargo check --bin openhuman-core` clean; `cargo test channels::providers::web` — 32 passed
  • N/A: Tauri shell unchanged in this PR.

Validation Blocked

  • `command:` N/A
  • `error:` N/A
  • `impact:` N/A

Behavior Changes

  • Intended behavior change: mascot reply audio + text now follow the active UI locale; new Mascot settings UI for gender / voice / locale-default toggle.
  • User-visible effect: switching the UI to Arabic and starting a new thread → mascot replies in Arabic and speaks it correctly.

Parity Contract

  • Legacy behavior preserved: locale param is optional everywhere; `None` / `undefined` reproduces the prior English-only behaviour exactly. Default-voice change is silent for users that already picked a custom voice.
  • Guard/fallback/dispatch parity checks: `selectEffectiveMascotVoiceId` is defensive against missing locale slice (test harnesses); REHYDRATE scrubs unknown values back to safe defaults.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: N/A
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • Full Mascot voice settings UI: gender, "match app language" toggle, curated presets, paste/save custom voice IDs, preview/reset controls, and current effective voice display.
    • Mascot voice controls consolidated into the dedicated Mascot settings panel (voice panel now links to Mascot settings).
  • Improvements

    • Locale-aware voice defaults and expanded curated presets; updated default mascot voice/model for improved multilingual replies.
    • Chat messages include UI locale for locale-aware responses.
    • Added mascot voice translations across multiple languages.

Review Change Stack

senamakel added 2 commits May 18, 2026 06:33
Mascot voice settings now expose gender, ElevenLabs voice picker, and
a "match the app language" toggle backed by a per-locale default voice
map. Default voice is George (`JBFqnCBsd6RMkjVDRZzb`, multilingual) and
all TTS requests now default to `eleven_multilingual_v2` so non-Latin
scripts render correctly.

Frontend chat send now carries the active UI locale to the core. In
the web channel, a new `locale` RPC param threads through `start_chat`
and `run_chat_task` into `build_session_agent`, which composes a
"Respond in <language>" directive on top of the profile's existing
system-prompt suffix for non-English locales.
@senamakel senamakel requested a review from a team May 18, 2026 13:36
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

📝 Walkthrough

Walkthrough

Moves mascot voice controls from VoicePanel to MascotPanel, adds gender and locale-aware defaults and selectors, ensures frontend passes UI locale to chat RPCs, and composes locale-driven reply directives in the backend agent builder.

Changes

Mascot Voice and Chat Locale Integration

Layer / File(s) Summary
Voice preset schema and mascot voice state
app/src/components/settings/panels/elevenlabsVoicePresets.ts, app/src/store/mascotSlice.ts
ElevenLabsVoicePreset gains locales; DEFAULT_VOICE_BY_LOCALE and defaultVoiceIdForLocale() added. MascotState adds voiceGender and voiceUseLocaleDefault; selectEffectiveMascotVoiceId resolves effective voice id by locale-default → manual → build-time fallback.
MascotPanel voice configuration UI
app/src/components/settings/panels/MascotPanel.tsx
Adds voice UI: gender radios, locale-default toggle and mapping display, curated preset dropdown (gender/locale-filtered), custom-paste input + save, preview via synthesizeSpeech, reset, and error handling.
VoicePanel refactor to remove mascot voice picker
app/src/components/settings/panels/VoicePanel.tsx, app/src/components/settings/panels/__tests__/VoicePanel.test.tsx
Removes Mascot voice redux usage, ElevenLabs preset imports, and in-panel preview/picker; replaces with navigation link to MascotPanel and deletes the nested Mascot voice test block.
Mascot voice synthesis with effective voice ID and model
app/src/features/human/useHumanMascot.ts, app/src/features/human/voice/ttsClient.ts, app/src/utils/config.ts, app/src/test/setup.ts
useHumanMascot reads selectEffectiveMascotVoiceId and always supplies a voiceId to synthesizeSpeech. ttsClient defaults modelId to MASCOT_VOICE_MODEL_ID and includes model_id in RPC params. Config updates default mascot voice id and adds MASCOT_VOICE_MODEL_ID; tests updated accordingly.
Chat message locale propagation (frontend)
app/src/pages/Conversations.tsx, app/src/services/chatService.ts, app/src/services/webviewAccountService.ts
Conversations reads UI locale and passes locale to chatSend. ChatSendParams gains `locale?: string
Chat message locale propagation (backend)
src/core/socketio.rs, src/openhuman/channels/bus.rs, src/openhuman/channels/providers/web.rs
Adds optional locale through Socket.IO payloads, start_chat, run_chat_task, and build_session_agent, and wires channel_web_chat/handle_chat to accept and forward locale.
Locale-aware system prompt composition
src/openhuman/channels/providers/web.rs
Adds locale_reply_directive and compose_system_prompt_suffix; build_session_agent prepends a locale "reply in this language" directive for non-English locales and passes the composed suffix into agent construction.
Tests and i18n strings for voice and locale features
app/src/lib/i18n/*, app/src/pages/__tests__/Conversations.render.test.tsx, app/src/features/human/*, src/openhuman/channels/providers/web_tests.rs
Adds settings.mascot.voice.* translations across locales, updates tests to expect resolved voice id and model_id in TTS RPCs, adapts Conversations tests to include locale, and adds unit tests for locale directive/suffix composition.

Sequence Diagram(s)

sequenceDiagram
  participant User as User (UI)
  participant Conversations as Conversations.tsx
  participant chatService as chatService
  participant SocketIO as Socket.IO
  participant WebProvider as Web Provider
  participant SessionAgent as SessionAgent
  User->>Conversations: send chat message
  Conversations->>Conversations: uiLocale = locale.current ?? 'en'
  Conversations->>chatService: chatSend({message, locale: uiLocale})
  chatService->>SocketIO: channel_web_chat RPC (message, locale)
  SocketIO->>WebProvider: start_chat(message, locale)
  WebProvider->>SessionAgent: build_session_agent(locale)
  SessionAgent->>SessionAgent: directive = locale_reply_directive(locale)
  SessionAgent->>SessionAgent: suffix = compose_system_prompt_suffix(directive, profile_suffix)
  SessionAgent->>SessionAgent: Agent configured with locale prompt
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • graycyrus

"A rabbit hums as voices grow,
George speaks soft where locales flow,
Presets, paste, and gender choice,
Prompts now guide the agent's voice,
Hooray — the mascot finds its glow!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely summarizes the main objectives: adding locale-aware mascot voice selection and enabling multilingual LLM replies.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label May 18, 2026
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

🧹 Nitpick comments (4)
src/openhuman/channels/providers/web.rs (2)

47-72: ⚖️ Poor tradeoff

Locale omitted from session cache fingerprint — documented but worth tracking.

The SessionCacheFingerprint doesn't include locale, so mid-conversation locale changes won't take effect until the session rebuilds for another reason. The PR documents this as intentional. Consider opening a follow-up issue to add locale to the fingerprint if user testing shows this causes confusion when switching app language.

🤖 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 `@src/openhuman/channels/providers/web.rs` around lines 47 - 72,
SessionCacheFingerprint currently omits locale, so add a new field (e.g.,
locale: Option<String> or String) to the SessionCacheFingerprint struct and
include it wherever fingerprints are constructed/compared so locale changes
invalidate the cache; update the struct declaration (SessionCacheFingerprint)
and every place that builds instances of it (call sites that set model_override,
temperature, target_agent_id, provider_binding) to populate the new locale field
and ensure equality/clone behavior still works.

1672-1701: 💤 Low value

Consider adding a locale coverage test to prevent future drift.

The frontend Locale type (app/src/lib/i18n/types.ts) and backend locale_reply_directive mapping are currently in sync, but a test comparing both lists would help catch silent divergence as the UI adds new locales.

🤖 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 `@src/openhuman/channels/providers/web.rs` around lines 1672 - 1701, Add a
unit/integration test that asserts the backend locale mapping in the function
locale_reply_directive covers every locale declared in the frontend Locale type;
implement a test (e.g. test_locale_coverage_for_locale_reply_directive) that
loads/parses the frontend i18n/types.ts Locale variants (or imports a canonical
list if one is exported) and then verifies each frontend tag either maps to
Some(...) by calling locale_reply_directive or is intentionally absent with a
documented exception list; fail the test if any frontend locale is not accounted
for so the backend mapping and the frontend Locale remain in sync.
app/src/features/human/useHumanMascot.test.ts (1)

547-547: ⚡ Quick win

Update test name to match the new assertion.

The test name says "omits the voice override" but the assertion now expects a voiceId to be present ('JBFqnCBsd6RMkjVDRZzb'). The comment at lines 565-567 correctly explains the new behavior: the selector resolves the build-time default eagerly.

📝 Proposed test name update
-    it('omits the voice override when no preference is stored', async () => {
+    it('uses the default mascot voice when no preference is stored', async () => {
       mockMascotVoiceId = null;
🤖 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/src/features/human/useHumanMascot.test.ts` at line 547, Update the test
case name string for the it(...) block currently labelled "omits the voice
override when no preference is stored" to reflect that the selector now resolves
the build-time default eagerly (e.g., "defaults to build-time voice when no
preference is stored"); keep the assertion that expects voiceId
'JBFqnCBsd6RMkjVDRZzb' unchanged so the test name matches the new behavior
described in the surrounding comments and assertions.
app/src/features/human/voice/ttsClient.test.ts (1)

36-36: ⚡ Quick win

Import and use MASCOT_VOICE_ID and MASCOT_VOICE_MODEL_ID constants from config.ts.

The test hard-codes the voice ID 'JBFqnCBsd6RMkjVDRZzb' and model ID 'eleven_multilingual_v2', which are already exported as constants in app/src/utils/config.ts. Since the test name explicitly states it "falls back to the configured mascot voice + multilingual model", using the actual config constants would keep the test aligned with the real configuration and avoid brittleness if defaults change.

🤖 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/src/features/human/voice/ttsClient.test.ts` at line 36, The test
currently hard-codes the mascot voice and model values; import MASCOT_VOICE_ID
and MASCOT_VOICE_MODEL_ID from the config module (the constants exported from
app/src/utils/config.ts) into ttsClient.test.ts and replace the literal strings
('JBFqnCBsd6RMkjVDRZzb' and 'eleven_multilingual_v2') in the params object with
those constants so the test uses the configured mascot voice and multilingual
model; ensure the import is added at the top and the params line uses
MASCOT_VOICE_ID and MASCOT_VOICE_MODEL_ID.
🤖 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/src/components/settings/panels/MascotPanel.tsx`:
- Around line 120-123: The useEffect in MascotPanel currently calls
setVoiceDraft and setVoicePreviewError synchronously; change this to schedule
those updates asynchronously to satisfy the react-hooks/set-state-in-effect rule
— e.g., inside the useEffect that depends on storedVoiceId, capture const newId
= storedVoiceId ?? '' and then call the setters inside a microtask or timeout
(queueMicrotask(() => setVoiceDraft(newId)); queueMicrotask(() =>
setVoicePreviewError(null)); or setTimeout(..., 0)), ensuring you still
reference setVoiceDraft and setVoicePreviewError and retain the same dependency
on storedVoiceId.

---

Nitpick comments:
In `@app/src/features/human/useHumanMascot.test.ts`:
- Line 547: Update the test case name string for the it(...) block currently
labelled "omits the voice override when no preference is stored" to reflect that
the selector now resolves the build-time default eagerly (e.g., "defaults to
build-time voice when no preference is stored"); keep the assertion that expects
voiceId 'JBFqnCBsd6RMkjVDRZzb' unchanged so the test name matches the new
behavior described in the surrounding comments and assertions.

In `@app/src/features/human/voice/ttsClient.test.ts`:
- Line 36: The test currently hard-codes the mascot voice and model values;
import MASCOT_VOICE_ID and MASCOT_VOICE_MODEL_ID from the config module (the
constants exported from app/src/utils/config.ts) into ttsClient.test.ts and
replace the literal strings ('JBFqnCBsd6RMkjVDRZzb' and
'eleven_multilingual_v2') in the params object with those constants so the test
uses the configured mascot voice and multilingual model; ensure the import is
added at the top and the params line uses MASCOT_VOICE_ID and
MASCOT_VOICE_MODEL_ID.

In `@src/openhuman/channels/providers/web.rs`:
- Around line 47-72: SessionCacheFingerprint currently omits locale, so add a
new field (e.g., locale: Option<String> or String) to the
SessionCacheFingerprint struct and include it wherever fingerprints are
constructed/compared so locale changes invalidate the cache; update the struct
declaration (SessionCacheFingerprint) and every place that builds instances of
it (call sites that set model_override, temperature, target_agent_id,
provider_binding) to populate the new locale field and ensure equality/clone
behavior still works.
- Around line 1672-1701: Add a unit/integration test that asserts the backend
locale mapping in the function locale_reply_directive covers every locale
declared in the frontend Locale type; implement a test (e.g.
test_locale_coverage_for_locale_reply_directive) that loads/parses the frontend
i18n/types.ts Locale variants (or imports a canonical list if one is exported)
and then verifies each frontend tag either maps to Some(...) by calling
locale_reply_directive or is intentionally absent with a documented exception
list; fail the test if any frontend locale is not accounted for so the backend
mapping and the frontend Locale remain in sync.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 35121176-3b13-41ac-9f48-1256586fb689

📥 Commits

Reviewing files that changed from the base of the PR and between 983f297 and c0ec737.

📒 Files selected for processing (20)
  • app/src/components/settings/panels/MascotPanel.tsx
  • app/src/components/settings/panels/VoicePanel.tsx
  • app/src/components/settings/panels/__tests__/VoicePanel.test.tsx
  • app/src/components/settings/panels/elevenlabsVoicePresets.ts
  • app/src/features/human/useHumanMascot.test.ts
  • app/src/features/human/useHumanMascot.ts
  • app/src/features/human/voice/ttsClient.test.ts
  • app/src/features/human/voice/ttsClient.ts
  • app/src/lib/i18n/en.ts
  • app/src/pages/Conversations.tsx
  • app/src/pages/__tests__/Conversations.render.test.tsx
  • app/src/services/chatService.ts
  • app/src/services/webviewAccountService.ts
  • app/src/store/mascotSlice.ts
  • app/src/test/setup.ts
  • app/src/utils/config.ts
  • src/core/socketio.rs
  • src/openhuman/channels/bus.rs
  • src/openhuman/channels/providers/web.rs
  • src/openhuman/channels/providers/web_tests.rs
💤 Files with no reviewable changes (1)
  • app/src/components/settings/panels/tests/VoicePanel.test.tsx

Comment thread app/src/components/settings/panels/MascotPanel.tsx Outdated
senamakel added 2 commits May 18, 2026 06:47
The new `settings.mascot.voice.*` keys landed in `en.ts` and `en-5.ts`
but missed every per-locale chunk-5 file, so `pnpm i18n:check` reported
17 missing keys × 10 non-English locales. Translations added for ar /
bn / es / fr / hi / id / it / pt / ru / zh-CN; runtime still falls
back to English for any locale that ends up out of date.
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 18, 2026
Inline the draft + preview-error resets into the handlers that also
dispatch `setMascotVoiceId(...)`, dropping the effect-based mirror
flagged by `react-hooks/set-state-in-effect` (CodeRabbit review).
All writes flow through this component, so an effect to "watch the
slice" was redundant — handlers see the change before dispatch.
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: 2

🤖 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/src/components/settings/panels/MascotPanel.tsx`:
- Around line 117-126: The cleanup only stops existing audio but doesn't prevent
a pending synthesizeSpeech(...) from creating new Audio and updating state after
unmount; add a cancellation guard (e.g., a local isCancelled ref or
currentPreviewId token) that is set in the useEffect cleanup and checked before
creating/playing the Audio and before calling setState in the synthesizeSpeech
completion path (references: previewAudioRef, the useEffect cleanup block around
lines 117-126, and the synthesizeSpeech handler around lines 197-218); ensure
you also stop/clear any created Audio only when not cancelled and avoid calling
setPreviewing/setPreviewAudio after cancellation.
- Around line 154-156: The visiblePresets filter drops a curated preset that's
currently selected (effectiveVoiceId) when voiceGender changes, causing the
controlled <select> (value tied to effectiveVoiceId) to have no matching
<option>; update the filtering logic that builds visiblePresets from
ELEVENLABS_VOICE_PRESETS (and the similar occurrences) to also include any
preset whose id equals effectiveVoiceId regardless of gender or locales, e.g.
keep p if p.gender===voiceGender || p.locales.includes('*') ||
p.id===effectiveVoiceId so the active curated voice remains in the rendered
options.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 526fc0f9-8338-4914-9609-7a2e03235201

📥 Commits

Reviewing files that changed from the base of the PR and between 93d77ac and 8ce11ef.

📒 Files selected for processing (1)
  • app/src/components/settings/panels/MascotPanel.tsx

Comment thread app/src/components/settings/panels/MascotPanel.tsx Outdated
Comment thread app/src/components/settings/panels/MascotPanel.tsx
senamakel added 2 commits May 18, 2026 07:07
CodeRabbit follow-up:
- Add `previewRequestIdRef` so a `synthesizeSpeech` that resolves
  after unmount or another preview click bails before touching refs /
  state. The earlier audio-only cleanup couldn't catch that case.
- Include the currently-selected preset in `visiblePresets` even when
  it doesn't match the active gender filter, so flipping gender can't
  leave the controlled `<select>` pointing at an id with no matching
  `<option>`.
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.

🧹 Nitpick comments (1)
app/src/components/settings/panels/MascotPanel.tsx (1)

224-226: 💤 Low value

Consider localizing the voice preview text.

The preview string is hardcoded in English. Since this PR introduces locale-aware voice behavior and uses eleven_multilingual_v2, users may benefit from hearing the voice in their actual language to better evaluate it.

💡 Suggested change
-      const tts = await synthesizeSpeech("Hi, I'm your assistant. This is a voice preview.", {
+      const tts = await synthesizeSpeech(t('settings.mascot.voice.previewText'), {
         voiceId: effectiveVoiceId,
       });
🤖 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/src/components/settings/panels/MascotPanel.tsx` around lines 224 - 226,
The preview text is hardcoded in English; replace it with a localized string and
pass that into synthesizeSpeech so users hear the preview in their locale.
Locate the call to synthesizeSpeech in MascotPanel (the line using
effectiveVoiceId) and retrieve the translated preview using your app's i18n
utility (e.g., t('mascot.voicePreview') or useTranslation()/useLocale() helper),
add a fallback string if translation is missing, then call
synthesizeSpeech(localizedPreview, { voiceId: effectiveVoiceId }); also add the
new translation key "mascot.voicePreview" to your locale files.
🤖 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.

Nitpick comments:
In `@app/src/components/settings/panels/MascotPanel.tsx`:
- Around line 224-226: The preview text is hardcoded in English; replace it with
a localized string and pass that into synthesizeSpeech so users hear the preview
in their locale. Locate the call to synthesizeSpeech in MascotPanel (the line
using effectiveVoiceId) and retrieve the translated preview using your app's
i18n utility (e.g., t('mascot.voicePreview') or useTranslation()/useLocale()
helper), add a fallback string if translation is missing, then call
synthesizeSpeech(localizedPreview, { voiceId: effectiveVoiceId }); also add the
new translation key "mascot.voicePreview" to your locale files.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e87eba66-681e-4f85-b819-3ca8da8d42cc

📥 Commits

Reviewing files that changed from the base of the PR and between 8ce11ef and 2ba4786.

📒 Files selected for processing (1)
  • app/src/components/settings/panels/MascotPanel.tsx

@senamakel senamakel merged commit 0b053c5 into tinyhumansai:main May 18, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant