From 36350d35b106a6f63cdf5886f2779138c9779ad6 Mon Sep 17 00:00:00 2001 From: bvandeusen Date: Tue, 7 Apr 2026 09:57:07 -0400 Subject: [PATCH 1/2] feat(stt): pass last assistant response as Whisper context to reduce mishearings --- lib/data/api/voice_api.dart | 10 +++++++--- lib/data/repositories/voice_repository.dart | 3 ++- lib/providers/voice_provider.dart | 11 +++++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/data/api/voice_api.dart b/lib/data/api/voice_api.dart index 6fed7b4..31d4eac 100644 --- a/lib/data/api/voice_api.dart +++ b/lib/data/api/voice_api.dart @@ -40,16 +40,20 @@ class VoiceApi { } /// POST WebM/Opus audio bytes and return the transcript string. + /// [context] is optional recent conversation text passed as initial_prompt + /// to Whisper, reducing mishearings of domain-specific words. /// Returns empty string on empty or error response. - Future transcribe(Uint8List audioBytes) async { + Future transcribe(Uint8List audioBytes, {String? context}) async { try { - final formData = FormData.fromMap({ + final fields = { 'audio': MultipartFile.fromBytes( audioBytes, filename: 'audio.m4a', contentType: DioMediaType('audio', 'mp4'), ), - }); + if (context != null && context.isNotEmpty) 'context': context, + }; + final formData = FormData.fromMap(fields); final response = await _dio.post( '/api/voice/transcribe', data: formData, diff --git a/lib/data/repositories/voice_repository.dart b/lib/data/repositories/voice_repository.dart index 7eb9417..cc8329c 100644 --- a/lib/data/repositories/voice_repository.dart +++ b/lib/data/repositories/voice_repository.dart @@ -7,6 +7,7 @@ class VoiceRepository { const VoiceRepository(this._api); Future checkStatus() => _api.checkStatus(); - Future transcribe(Uint8List audioBytes) => _api.transcribe(audioBytes); + Future transcribe(Uint8List audioBytes, {String? context}) => + _api.transcribe(audioBytes, context: context); Future synthesise(String text) => _api.synthesise(text); } diff --git a/lib/providers/voice_provider.dart b/lib/providers/voice_provider.dart index 9d7cf80..26e9d64 100644 --- a/lib/providers/voice_provider.dart +++ b/lib/providers/voice_provider.dart @@ -108,6 +108,10 @@ class VoiceNotifier extends Notifier { int _lastSeenLength = 0; bool _streamComplete = false; + // Last complete assistant response — passed to Whisper as initial_prompt + // to reduce STT mishearings of domain-specific words. + String _lastAssistantContent = ''; + // TTS playback queue final _ttsQueue = Queue(); bool _ttsPlaying = false; @@ -205,6 +209,7 @@ class VoiceNotifier extends Notifier { _dispatchSentences(flush: isComplete); if (isComplete) { + _lastAssistantContent = fullContent; _streamComplete = true; _checkRestartListening(); } @@ -278,8 +283,10 @@ class VoiceNotifier extends Notifier { if (!state.voiceModeActive) return; - final transcript = - await ref.read(voiceRepositoryProvider).transcribe(bytes); + final transcript = await ref.read(voiceRepositoryProvider).transcribe( + bytes, + context: _lastAssistantContent.isNotEmpty ? _lastAssistantContent : null, + ); if (!state.voiceModeActive) return; From d2582f9111c76cb0466c29880a1efc4b2a08dbfe Mon Sep 17 00:00:00 2001 From: bvandeusen Date: Tue, 7 Apr 2026 17:43:46 -0400 Subject: [PATCH 2/2] feat: reduce resume cooldown to 30s; add pull-to-refresh to News, Calendar; refresh chat on resume --- lib/app.dart | 2 +- lib/screens/calendar/calendar_screen.dart | 18 +++++++++++++----- lib/screens/chat/chat_screen.dart | 17 ++++++++++++++++- lib/screens/news/news_screen.dart | 5 ++++- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 294aec3..aa41f9f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -193,7 +193,7 @@ class _ShellState extends ConsumerState<_Shell> with WidgetsBindingObserver { ]; // Minimum gap between app-resume refreshes to avoid hammering the server. - static const _resumeCooldown = Duration(minutes: 5); + static const _resumeCooldown = Duration(seconds: 30); DateTime? _lastResumeRefresh; int? _prevTabIndex; diff --git a/lib/screens/calendar/calendar_screen.dart b/lib/screens/calendar/calendar_screen.dart index 68751a5..0d0394e 100644 --- a/lib/screens/calendar/calendar_screen.dart +++ b/lib/screens/calendar/calendar_screen.dart @@ -67,10 +67,13 @@ class CalendarScreen extends ConsumerWidget { ), const Divider(height: 1), Expanded( - child: _AgendaList( - events: - cal.eventsByDay[dateOnly(cal.selectedDay)] ?? [], - notifier: notifier, + child: RefreshIndicator( + onRefresh: () async => ref.invalidate(calendarProvider), + child: _AgendaList( + events: + cal.eventsByDay[dateOnly(cal.selectedDay)] ?? [], + notifier: notifier, + ), ), ), ], @@ -106,7 +109,12 @@ class _AgendaList extends StatelessWidget { @override Widget build(BuildContext context) { if (events.isEmpty) { - return const Center(child: Text('No events')); + return ListView( + children: const [ + SizedBox(height: 80), + Center(child: Text('No events')), + ], + ); } return ListView.builder( padding: const EdgeInsets.symmetric(vertical: 8), diff --git a/lib/screens/chat/chat_screen.dart b/lib/screens/chat/chat_screen.dart index 0dbb479..7520aea 100644 --- a/lib/screens/chat/chat_screen.dart +++ b/lib/screens/chat/chat_screen.dart @@ -16,12 +16,27 @@ class ChatScreen extends ConsumerStatefulWidget { ConsumerState createState() => _ChatScreenState(); } -class _ChatScreenState extends ConsumerState { +class _ChatScreenState extends ConsumerState + with WidgetsBindingObserver { final _controller = TextEditingController(); final _scrollController = ScrollController(); + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + ref.invalidate(messagesProvider(widget.conversationId)); + } + } + @override void dispose() { + WidgetsBinding.instance.removeObserver(this); _controller.dispose(); _scrollController.dispose(); // Exit voice mode if the user navigates away mid-session. diff --git a/lib/screens/news/news_screen.dart b/lib/screens/news/news_screen.dart index 3094581..be3d16c 100644 --- a/lib/screens/news/news_screen.dart +++ b/lib/screens/news/news_screen.dart @@ -96,7 +96,9 @@ class _NewsScreenState extends ConsumerState { ), const Divider(height: 1), Expanded( - child: ListView.builder( + child: RefreshIndicator( + onRefresh: () async => ref.invalidate(newsProvider), + child: ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), itemCount: news.items.length + 1, @@ -128,6 +130,7 @@ class _NewsScreenState extends ConsumerState { ); }, ), + ), ), ], ),