92 Commits

Author SHA1 Message Date
59f1f088ed Keep STT listening during TTS playback for interruption detection
- Add sessionActiveRef to track when voice session is active
- Add shouldRestartSTTRef to auto-restart STT after it ends
- STT now continues listening during TTS playback
- Voice detection callback checks both status and isSpeaking
- Final results during TTS are ignored (user interrupted)
- STT automatically restarts after ending if session is still active

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:36:08 -08:00
3c7a48df5b Integrate TTS interruption in VoiceFAB when voice detected
- Add onVoiceDetected callback to useSpeechRecognition hook
  - Triggered on first interim result (voice activity detected)
  - Uses voiceDetectedRef to ensure callback fires only once per session
  - Reset flag on session start/end

- Connect STT to VoiceContext in _layout.tsx
  - Use useSpeechRecognition with onVoiceDetected callback
  - Call interruptIfSpeaking() when voice detected during 'speaking' state
  - Forward STT results to VoiceContext (setTranscript, sendTranscript)
  - Start/stop STT based on isListening state

- Export interruptIfSpeaking from VoiceContext provider

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:34:07 -08:00
dbf6a8a74a Add red pulsing animation to VoiceFAB when listening
- Add pulse ring that expands and fades out while in listening mode
- Animation uses native driver for smooth 60fps performance
- Ring starts at FAB size and scales to 1.8x with opacity fade

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:31:19 -08:00
764c149e2e Add toggle listening mode on VoiceFAB tap
- FAB now toggles between idle and listening states
- Green background (idle) → Red background (listening)
- Icon switches between mic-outline and mic
- Connects to VoiceContext for state management

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:30:20 -08:00
1c23ca41b8 Center VoiceFAB on all tabs
- Changed positioning from right-aligned to centered
- FAB now appears in the center above the tab bar on all screens

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:28:57 -08:00
356205d8c0 Remove LiveKit SDK and related code
- Remove @livekit/react-native-expo-plugin from app.json
- Remove @config-plugins/react-native-webrtc plugin
- Delete utils/audioSession.ts (depended on LiveKit AudioSession)
- Update VoiceCallContext.tsx comments
- Update callManager.ts comments
- Update _layout.tsx TODO comment
- Remove LiveKit documentation files
- Add interruptIfSpeaking to VoiceContext for TTS interrupt

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:28:23 -08:00
66a8395602 Add haptic feedback to VoiceFAB on press
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:26:18 -08:00
9b4d39fdc5 Integrate VoiceFAB into tabs layout
Add floating action button for voice calls to the tab bar layout:
- Import and render VoiceFAB component
- Add placeholder handler for voice call initiation
- Wrap Tabs in View to properly position FAB overlay
- FAB automatically hides when a call is active (via VoiceCallContext)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:25:35 -08:00
6abc1f0382 Add VoiceFAB component for voice call initiation
Floating action button that triggers voice calls with Julia AI.
Features:
- Animated show/hide based on active call state
- Press animation feedback
- Positioned above tab bar with safe area support
- Disabled state styling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:22:57 -08:00
cc89c2d154 Add expo-speech-recognition plugin with permissions
Configure @jamsch/expo-speech-recognition plugin in app.json with
custom permission messages for microphone and speech recognition
access. This enables native STT functionality for the Voice FAB feature.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:21:56 -08:00
62eb7c4de0 Add useTextToSpeech hook for TTS operations
Create a reusable hook wrapping expo-speech that provides:
- speak/stop controls
- isSpeaking state tracking
- Voice listing support
- Promise-based API for async flows
- Proper cleanup on unmount

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:20:51 -08:00
54bff8d9d5 Add useSpeechRecognition hook for voice input
Implements speech recognition hook wrapping @jamsch/expo-speech-recognition:
- startListening/stopListening/abortListening controls
- Real-time transcript updates with interim results
- Permission handling with user feedback
- Platform-specific options (Android silence timeout, iOS punctuation)
- Error handling with graceful degradation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:19:31 -08:00
caf47ead9c Add VoiceContext with API integration and TTS
- Create VoiceContext.tsx with sendTranscript() for WellNuo API calls
- Integrate expo-speech TTS for response playback
- Add VoiceProvider to app layout
- Flow: transcript → normalize → API → response → TTS → continue listening

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:16:50 -08:00
bd12aadfb3 Remove LiveKit integration from chat.tsx
- Remove all LiveKit imports (registerGlobals, LiveKitRoom, useVoiceAssistant, etc.)
- Remove VoiceCallTranscriptHandler component
- Remove voice call state management (callState, isCallActive)
- Remove voice call functions (startVoiceCall, endVoiceCall, handleVoiceTranscript)
- Remove LiveKitRoom component from JSX
- Remove debug panel for voice calls
- Clean up unused imports (Clipboard, Animated, expo-keep-awake, useAuth, useVoiceTranscript, useVoiceCall)
- Remove unused styles (voiceButton, callActiveIndicator, debug panel styles)
- Update initial message text to remove voice call references

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:13:44 -08:00
260a722cd9 Remove unused useLiveKitRoom hook
This LiveKit hook was no longer used after switching to speech recognition.
Also removed the outdated comment referencing it in _layout.tsx.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:08:41 -08:00
432964c4d0 Remove livekitService.ts
Removed deprecated LiveKit service file as part of voice integration cleanup.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:07:49 -08:00
3c58ff20f9 Add speech recognition dependencies
- @jamsch/expo-speech-recognition ^0.3.3
- expo-speech ~14.0.6

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:07:18 -08:00
de2563fec6 Remove LiveKit dependencies from package.json
Removed packages:
- @livekit/react-native
- @livekit/react-native-expo-plugin
- livekit-client
- @config-plugins/react-native-webrtc

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-27 16:06:45 -08:00
Sergei
ef533de4d5 Fix Android audio to use speaker instead of earpiece
- Configure LiveKit Expo plugin with audioType: "media" in app.json
  This forces speaker output on Android instead of earpiece
- Remove microphone icon from voice messages in chat
- Remove audio output picker button (no longer needed)
- Clean up audioSession.ts configuration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-26 14:02:27 -08:00
Sergei
5b5cdf1098 Add audio output switcher for voice calls (Android speaker fix)
- Add Audio button during active calls to switch output
- Fallback to Speaker/Earpiece options when LiveKit API unavailable
- Speaker now works correctly on Android

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-26 13:25:19 -08:00
Sergei
8dd8590c1c Add audio output device enumeration and selection utils
- Add AudioOutputDevice interface with id, name, type fields
- Add getAvailableAudioOutputs() to list available audio devices
- Add selectAudioOutput(deviceId) to switch to specific device
- Add mapDeviceType() helper for device type normalization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-26 13:05:12 -08:00
Sergei
f2e633df99 Fix audio playback: add room.startAudio() call
Root cause: Audio from remote participant (Julia AI) was not playing
because room.startAudio() was never called after connecting.

This is REQUIRED by LiveKit WebRTC to enable audio playback.
The fix matches the working implementation in debug.tsx (Robert version).

Changes:
- Add room.startAudio() call after room.connect()
- Add canPlayAudio state tracking
- Add proper error handling for startAudio

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-25 18:03:56 -08:00
Sergei
cd4137ef36 Fix Android speaker: use music stream type instead of voiceCall
- audioStreamType: music (routes to SPEAKER by default)
- audioMode: normal (not inCommunication which uses earpiece)
- audioAttributesUsageType: media
- audioAttributesContentType: music

Previous voiceCall stream was routing to earpiece on Android devices.
2026-01-25 13:12:16 -08:00
Sergei
8240e51bc5 Fix Android speaker output + keyboard-aware modal
Android Audio:
- Use inCommunication mode with forceHandleAudioRouting
- Explicit selectAudioOutput('speaker') after session start
- Keeps echo cancellation while forcing speaker output

Profile Modal:
- Add KeyboardAvoidingView for deployment ID input
- Prevents modal buttons from being hidden by keyboard

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-25 11:47:53 -08:00
Sergei
85896f442f Show beneficiary name instead of deployment ID in chat
- Add deploymentName state to chat screen
- Load and display beneficiary name in initial welcome message
- Save deployment name to SecureStore when validating in profile
- End call and clear chat when deployment changes
- Fix text input not clearing after sending message

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-25 10:30:01 -08:00
Sergei
ad0fe41ee9 Improve voice call UX and disable agent interruption
Chat improvements:
- Add pulsing animation to voice call button during active call
- Log call start/end with duration to chat history
- End call automatically when deployment ID changes
- Reduce bottom padding (removed SafeArea bottom edge)

Julia Agent:
- Disable user interruption (min_interruption_duration=999)
- Agent now speaks without being interrupted

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 21:51:20 -08:00
Sergei
5d2e8c029f fix: Prevent tab bar from being overlapped by Android navigation buttons
- Add SafeAreaProvider wrapper in root layout for reliable insets on Android
- Add minimum 16px bottom padding on Android to prevent overlap with
  gesture navigation or software buttons (Samsung, Pixel, etc.)
- Keep 10px minimum for other platforms

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:59:49 -08:00
Sergei
fa5d4ffb23 fix: Correct chat scroll behavior for both sort modes
Add scrollToLatestMessage helper that respects sortNewestFirst setting:
- When newest first: scroll to top (offset 0)
- When oldest first: scroll to end

Applied to keyboard show, voice messages, and content size changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:57:23 -08:00
Sergei
45ac102157 fix: Prevent FloatingCallBubble from overlapping tab bar
Account for tab bar height (60px) when calculating maximum Y position
for the draggable bubble. Previously the bubble could be dragged over
the tab bar navigation, now it stays above it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:55:35 -08:00
Sergei
06ab4722e5 feat: Remove Debug (Voice) tab from navigation
- Delete voice.tsx debug screen file
- Remove voice tab configuration from tabs layout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:52:58 -08:00
Sergei
b851e40f33 fix: Resolve TypeScript errors in Button and ultravoxService
- Button.tsx: Replace `condition && style` with ternary operators
  to ensure array elements are always ViewStyle/TextStyle (not false)
- ultravoxService.ts: Add 'in' type guard before accessing optional
  'location' property on alert objects

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:51:52 -08:00
Sergei
51d533f133 feat: Validate Deployment ID through API before saving
- Add validateDeploymentId() method in api.ts that checks if ID exists
  in user's deployments list
- Update profile.tsx to validate deployment ID before saving
- Show validation error message if ID is invalid
- Display deployment name alongside ID after validation
- Add loading state during validation
2026-01-24 20:50:40 -08:00
Sergei
9ae23cfef3 feat: Add Deployment ID setting in Profile
- Add deploymentId storage methods in api.ts (set/get/clear)
- Add Settings section in Profile with Deployment ID menu item
- Add modal dialog to edit deployment ID
- Update chat.tsx to use custom deployment ID from settings
- Priority: custom > currentBeneficiary > first beneficiary > fallback

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:48:18 -08:00
Sergei
664759dee9 feat: Add sort button to toggle message order in chat
Add a sort button in the chat header that toggles between oldest-first
(default) and newest-first message ordering. The arrow icon indicates
the current sort direction.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:45:42 -08:00
Sergei
5724e7ab76 feat: Add erase button to clear chat messages
Add trash icon button in chat header that clears all messages
after confirmation. Resets chat to initial welcome message.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:44:49 -08:00
Sergei
09fc6ce8ad feat: Display voice messages in chat in real-time
Voice transcripts from both user and Julia now appear in the chat
immediately during the call, not just after it ends. Each voice call
shows a "Voice Call" separator, and voice messages display with a
microphone icon indicator.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:43:24 -08:00
Sergei
560722e8af fix: Tap on floating bubble ends the call
Changed tap behavior on the floating call bubble to end the call
instead of maximizing it. Removed the separate small end call button
since it's no longer needed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:41:36 -08:00
Sergei
aec300bd98 feat: Add floating bubble during voice calls
- Create VoiceCallContext for global voice call state management
- Add FloatingCallBubble component with drag support
- Add minimize button to voice call screen
- Show bubble when call is minimized, tap to return to call
- Button shows active call state with green color

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:39:27 -08:00
Sergei
513d9c3c7d fix: Use safe area insets for tab bar to avoid Samsung nav button overlap
Use useSafeAreaInsets() to dynamically calculate tab bar height and bottom
padding instead of hardcoded values. This ensures the tab bar properly
accounts for Android navigation buttons (Samsung) and iOS home indicator.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:33:59 -08:00
Sergei
89afe86f54 feat: Integrate LiveKit voice calls into chat screen
- Add voice call button in chat input area
- Implement LiveKit room connection with Julia AI agent
- Create full-screen voice call modal with:
  - Visual avatar with speaking indicator
  - Call duration timer
  - Agent state display (listening/thinking/speaking)
  - Hang up button
- Add real-time transcription tracking for voice calls
- Keep screen awake during active calls
- Integrate with existing VoiceTranscriptContext for history

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:27:40 -08:00
Sergei
6f7c79f601 Remove separate voice call screen
Deleted app/voice-call.tsx fullscreen modal and removed all navigation
references to it from chat.tsx, voice.tsx and _layout.tsx.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:19:19 -08:00
Sergei
a23116a796 Remove Debug tab completely
- Remove debug screen from tab navigator
- Delete app/(tabs)/debug.tsx file

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 20:14:33 -08:00
Sergei
a578ec8081 feat: Pass Debug tab deployment ID to voice calls
- Add debugDeploymentId to BeneficiaryContext for sharing between screens
- Sync Debug tab's deploymentId state with global context
- voice-call.tsx now prioritizes debugDeploymentId when starting calls
- Enables testing voice calls with specific deployment IDs from Debug screen
2026-01-24 00:05:47 -08:00
Sergei
5ecb5f9683 Fix Julia AI voice: use SINGLE_DEPLOYMENT_MODE for Lite
- livekitService.ts: send empty beneficiaryNamesDict in Lite mode
- agent.py: handle None beneficiary_names_dict correctly
- chat.tsx: align text chat with same SINGLE_DEPLOYMENT_MODE flag

This fixes Julia saying "I didn't get the name of beneficiary"
by letting WellNuo API use the default beneficiary for deployment_id.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-22 16:49:55 -08:00
Sergei
8d98bab3cf Remove expo-speech-recognition plugin from app.json
The package was removed in commit d9fff44 but the plugin
entry was left in app.json, causing EAS Build to fail with
'Unknown error in Read app config' phase.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-22 10:25:29 -08:00
Sergei
e36b9bbf4a Add fallback audio configurations for iOS
If primary audio config fails (OSStatus -50), automatically try:
1. videoChat mode (speaker default)
2. voiceChat mode (earpiece default)
3. minimal config (most compatible)

Also make speaker output setting non-critical - call will work
even if output can't be changed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-22 09:43:05 -08:00
Sergei
d9fff44fc9 Remove unused expo-speech packages to avoid AudioSession conflicts
- Remove expo-speech (TTS) - not used
- Remove expo-speech-recognition (STT) - not used
- Delete dead code: hooks/useSpeechRecognition.ts

These packages add native audio modules that can conflict with
LiveKit's AudioSession management on iOS.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-22 09:41:35 -08:00
Sergei
42e3f389f7 Fix iOS AudioSession OSStatus error -50
- Remove 'defaultToSpeaker' from audioCategoryOptions (conflicts with some iOS versions)
- Use 'videoChat' mode instead of 'voiceChat' for speaker output
- Reorder operations: set config first, start session, then configure output
- Keep audioCategoryOptions minimal to avoid param conflicts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-22 09:36:20 -08:00
Sergei
ac6d458aae Fix Julia AI agent race condition - wait for participant metadata
The agent was reading participant metadata immediately after connecting,
but the mobile user might not have joined yet or metadata wasn't synced.

Changes:
- Replace extract_beneficiary_data() with wait_for_participant_with_metadata()
- Wait up to 10 seconds for participant with deploymentId
- Log waiting status every 2 seconds for debugging
- Fallback gracefully if timeout reached

This fixes "I couldn't get that information right now" error in voice calls.
2026-01-21 14:51:53 -08:00
Sergei
204cb87f05 Fix voice call race condition - ensure beneficiaryData is passed
- Fix race condition where connect() was called before beneficiaryData loaded
- Add connectCalledRef to prevent duplicate connect calls
- Wait for beneficiaryData.deploymentId before initiating call
- Add 5s timeout fallback for edge cases (API failure/no beneficiaries)
- Hide Debug tab, show only Julia tab in navigation
- Add Android keyboard layout fix for password fields
- Bump version to 1.0.5

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-21 14:30:59 -08:00