feat: add persistent transcription history with reusable insights viewer#34
feat: add persistent transcription history with reusable insights viewer#34Shweta-281 wants to merge 17 commits intoAOSSIE-Org:mainfrom
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 7 minutes and 31 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds persistent transcription history and a Medical Insights viewer; refactors Deepgram (retry+timeout, parsing) and Gemini (single insights JSON) services; introduces domain models, secure local storage, controller and UI updates to generate/save insights, and removes example API keys from Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as TranscriptionScreen
participant Controller as TranscriptionController
participant Deepgram as DeepgramService
participant Gemini as GeminiService
participant Storage as LocalStorageService
User->>UI: start recording / stop recording
UI->>Controller: provide audio file path
Controller->>Deepgram: transcribe(path)
Deepgram->>Deepgram: _retryPost (30s timeout, retries on 5xx/timeout)
Deepgram-->>Controller: raw transcript
Controller->>Gemini: generateInsights(transcript)
Gemini->>Gemini: getGeminiResponse + _extractJson + json.decode
Gemini-->>Controller: MedicalInsights
Controller->>Storage: save(TranscriptionHistoryModel)
Storage-->>Controller: persisted confirmation
Controller-->>UI: update state with insights
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (1)
lib/features/transcription/domain/transcription_model.dart (1)
12-18: MakecopyWithable to clearinsights.
insights: insights ?? this.insightsmeanscopyWith(insights: null)can never remove an existing value.lib/features/transcription/presentation/transcription_controller.dart:82currently works around this by allocating a freshTranscriptionModel, but the model API still can't express “clear insights”.Possible fix
class TranscriptionModel { + static const _unset = Object(); + final String rawTranscript; final MedicalInsights? insights; const TranscriptionModel({ this.rawTranscript = '', this.insights, }); TranscriptionModel copyWith({ String? rawTranscript, - MedicalInsights? insights, + Object? insights = _unset, }) { return TranscriptionModel( rawTranscript: rawTranscript ?? this.rawTranscript, - insights: insights ?? this.insights, + insights: identical(insights, _unset) + ? this.insights + : insights as MedicalInsights?, ); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/features/transcription/domain/transcription_model.dart` around lines 12 - 18, The copyWith cannot clear insights because `insights: insights ?? this.insights` treats a passed null as "no change"; update TranscriptionModel.copyWith to differentiate "parameter omitted" vs "explicit null" by using a sentinel default (e.g., change the signature to accept `Object? insights = _sentinel` and add a private sentinel like `static const _sentinel = Object()`), then set the returned model's insights to `this.insights` when `insights == _sentinel` otherwise to `insights as MedicalInsights?`, so callers can pass null to clear insights while preserving the same behavior when the parameter is omitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/features/transcription/data/deepgram_service.dart`:
- Around line 38-51: The code currently throws Exception('Deepgram failed:
${response.statusCode} - ${response.body}') but that exception is caught by the
generic catch (e) and retried; change the logic so non-retryable HTTP errors
(4xx, auth/validation) do not get swallowed and retried: after receiving
response, if statusCode >= 500 keep the existing exponential backoff and
continue, but for any non-5xx (statusCode < 500) immediately rethrow or return
the specific failure so it bypasses the retry loop; update the catch (e) block
to only handle transient errors (or rethrow for other Exceptions), and preserve
the existing TimeoutException handling that only retries up to retries using
attempt and retries variables.
In `@lib/features/transcription/data/local_storage_service.dart`:
- Around line 5-25: LocalStorageService currently stores sensitive medical data
in plaintext SharedPreferences under key "transcription_history" and rewrites
the entire collection in save/getAll; replace this with an encrypted persistent
store (e.g., Hive with encryptionCipher, Drift with SQLCipher, or
platform-backed secure storage) and stop using SharedPreferences for
TranscriptionHistoryModel; update the LocalStorageService implementation
(methods save and getAll and the static key) to use the chosen encrypted client,
derive/store the encryption key securely (e.g., from flutter_secure_storage or
platform keychain/keystore), ensure entries are encrypted at rest and migrated
from old plaintext safely (provide a one-time migration routine that reads
existing SharedPreferences, encrypts records into the new store, and then
securely deletes the plaintext), and avoid rewriting the whole dataset
unnecessarily by appending/updating single records via the encrypted DB APIs.
In `@lib/features/transcription/domain/transcription_history_model.dart`:
- Around line 24-31: TranscriptionHistoryModel.fromJson currently uses
List<String>.from(json['symptoms']) and List<String>.from(json['medicines'])
which will throw if those keys are missing or not lists; change the deserializer
in TranscriptionHistoryModel.fromJson to defensively read symptoms and medicines
(e.g., check for null and that the value is Iterable/List) and default to an
empty List<String> when missing or invalid so a single partial/old row cannot
break HistoryScreen.loadHistory; keep using DateTime.parse(json['createdAt'])
and the same field names (transcript, summary, symptoms, medicines) while
implementing this tolerant behavior.
In `@lib/features/transcription/presentation/history_screen.dart`:
- Around line 39-52: The history item tap currently only forwards symptoms and
medicines and the list title shows a truncated summary (item.summary); fix by
passing the full persisted data into the detail screen: include item.transcript
and the full item.summary (not just truncated display) in the Navigator.push
call to MedicalInsightsScreen inside the onTap handler, and update
MedicalInsightsScreen's constructor/signature to accept and use those new
parameters (e.g., transcript and fullSummary) so the detail view can show the
complete transcript and summary while keeping the list title truncated for
brevity.
- Around line 22-24: The async loadHistory method currently awaits
LocalStorageService().getAll() and calls setState() unguarded; change it to
store the result in a local variable, check if (!mounted) return; and only call
setState(() { history = localVar; }) so you never call setState after the widget
has been disposed (update the loadHistory function and references to history
accordingly).
In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 129-137: The TranscriptionHistoryModel instances (transcript,
insights.symptoms, insights.medicines) are being saved via
_localStorageService.save which uses SharedPreferences; move sensitive health
data to encrypted storage or require explicit user consent and deletion
controls: replace the call to _localStorageService.save(historyItem) with a
secure alternative (e.g., _secureStorageService.saveEncryptedHistory or a
repository that encrypts the model before persisting) and implement
corresponding methods to encrypt/decrypt TranscriptionHistoryModel, add an
explicit user consent check before saving (e.g., isHealthDataConsentGranted),
and expose clear/delete methods (e.g., deleteEncryptedHistory,
clearAllEncryptedHistory) so users can remove stored medical data. Ensure the
TranscriptionHistoryModel serialization supports encryption (to/from JSON) and
update any getters to read from the secure store instead of SharedPreferences.
- Around line 123-143: The current try/catch wraps both insight generation and
persistence so a save failure can overwrite a successful result; in
_processWithGemini move the state update (data = data.copyWith(...), state =
TranscriptionState.done, notifyListeners(), developer.log(...)) to occur
immediately after _geminiService.generateInsights completes, then perform await
_localStorageService.save(historyItem) inside a separate try/catch so
persistence errors are caught/logged or surfaced via a persistence-specific
handler without calling _setError which would flip the UI back to error; refer
to _processWithGemini, _geminiService.generateInsights, data.copyWith,
_localStorageService.save, TranscriptionState.done and _setError when making
this change.
In `@lib/features/transcription/presentation/transcription_screen.dart`:
- Around line 155-161: The status helper methods (_statusText and
_statusDetailText) currently ignore explicit error/permission messages and treat
the sentinel "No speech detected" as a normal success; update these helpers in
transcription_controller.dart to check for controller.state ==
TranscriptionState.error and return the controller.error or permission/config
messages directly, and also treat controller.transcription == "No speech
detected" as a distinct no-speech state (return a no-speech-specific
status/detail instead of the generic "done" messages) so the UI's empty-state
logic (which checks controller.state and controller.transcription) and the
error/permission flows are shown consistently.
- Around line 204-218: The navigation button for "Medical Insights" uses
controller.summary.isNotEmpty to set enabled but the screen actually consumes
controller.symptoms and controller.medicines; change the gating to a dedicated
check that reflects the actual data used (e.g. add/use controller.hasInsights or
compute (controller.symptoms.isNotEmpty || controller.medicines.isNotEmpty)) and
pass that to _buildNavigationButton instead of controller.summary.isNotEmpty so
MedicalInsightsScreen is only enabled when it has relevant data; update the
controller to expose hasInsights if needed and replace the enabled argument in
the call that creates MedicalInsightsScreen.
---
Nitpick comments:
In `@lib/features/transcription/domain/transcription_model.dart`:
- Around line 12-18: The copyWith cannot clear insights because `insights:
insights ?? this.insights` treats a passed null as "no change"; update
TranscriptionModel.copyWith to differentiate "parameter omitted" vs "explicit
null" by using a sentinel default (e.g., change the signature to accept `Object?
insights = _sentinel` and add a private sentinel like `static const _sentinel =
Object()`), then set the returned model's insights to `this.insights` when
`insights == _sentinel` otherwise to `insights as MedicalInsights?`, so callers
can pass null to clear insights while preserving the same behavior when the
parameter is omitted.
🪄 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
Run ID: 68602584-03e9-4ff6-93f2-68d3306fd4ef
⛔ Files ignored due to path filters (1)
pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (13)
.env.examplelib/features/transcription/data/deepgram_service.dartlib/features/transcription/data/gemini_service.dartlib/features/transcription/data/local_storage_service.dartlib/features/transcription/domain/medical_insights.dartlib/features/transcription/domain/transcription_history_model.dartlib/features/transcription/domain/transcription_model.dartlib/features/transcription/presentation/history_screen.dartlib/features/transcription/presentation/transcription_controller.dartlib/features/transcription/presentation/transcription_screen.dartlib/main.dartlib/screens/medical_insights_screen.dartpubspec.yaml
💤 Files with no reviewable changes (2)
- pubspec.yaml
- .env.example
lib/features/transcription/domain/transcription_history_model.dart
Outdated
Show resolved
Hide resolved
lib/features/transcription/presentation/transcription_controller.dart
Outdated
Show resolved
Hide resolved
lib/features/transcription/presentation/transcription_controller.dart
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
lib/features/transcription/data/deepgram_service.dart (1)
18-57: Previous retry bug is fixed; minor optimization possible.The restructured catch blocks correctly handle error cases:
on Exception { rethrow; }at lines 44-45 ensures non-5xx errors (thrown at line 40) exit immediately without retrying.- 5xx errors retry with backoff via
continueat line 38.- TimeoutException correctly throws on the last attempt.
Minor optimization: On the last 5xx attempt, line 37 delays before the loop naturally exits and line 53 throws. This adds unnecessary latency.
♻️ Optional: Skip delay on final 5xx attempt
if (response.statusCode >= 500) { + if (attempt == retries - 1) { + throw Exception( + 'Deepgram failed: ${response.statusCode} - ${response.body}', + ); + } await Future.delayed(Duration(seconds: 2 * (attempt + 1))); continue; } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/features/transcription/data/deepgram_service.dart` around lines 18 - 57, In _retryPost, avoid the extra backoff delay after the final 5xx attempt: when handling the 5xx branch inside the loop (the block that currently does await Future.delayed(Duration(seconds: 2 * (attempt + 1))); and then continue), only perform that await when attempt < retries - 1 so the last attempt does not sleep before the method falls through to the final throw; keep the rest of the retry/backoff logic (including the unconditional end-of-loop delay for non-5xx paths) unchanged.lib/features/transcription/presentation/transcription_controller.dart (1)
120-124: Minor: Inconsistent indentation incheckConfigStatus.The method body uses 2-space indentation while the rest of the file uses standard Dart indentation.
🧹 Fix indentation
void checkConfigStatus(bool isLoaded) { - if (!isLoaded) { - _setError('Configuration Error: API keys could not be loaded. Please check your .env file.'); - } + if (!isLoaded) { + _setError('Configuration Error: API keys could not be loaded. Please check your .env file.'); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/features/transcription/presentation/transcription_controller.dart` around lines 120 - 124, The method checkConfigStatus has inconsistent 2-space indentation; reformat its body to use standard Dart indentation (typically 2 spaces per indent level? actually file uses different—match the rest of the file) so it aligns with the surrounding code: update the indentation inside checkConfigStatus and its if-block and the _setError call to match the file's existing style, leaving the logic unchanged (function name checkConfigStatus and call to _setError remain as-is).lib/features/transcription/data/local_storage_service.dart (1)
46-48: Consider logging decryption/parsing failures for diagnostics.Returning an empty list on error provides resilience, but silently swallowing exceptions makes debugging data corruption difficult. Adding a log statement would help diagnose issues without breaking the user experience.
🔧 Add error logging
} catch (e) { + developer.log('Failed to load history: $e', name: 'LocalStorageService'); return []; }Add import at top:
import 'dart:developer' as developer;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/features/transcription/data/local_storage_service.dart` around lines 46 - 48, The catch block that currently swallows exceptions in local_storage_service.dart should log decryption/parsing failures for diagnostics: add the import "import 'dart:developer' as developer;", change the naked catch to capture the stack (catch (e, st)) in the method that returns the empty list, and call developer.log with a clear message (e.g., "Failed to load/decrypt/parsing transcriptions"), passing error: e and stackTrace: st before returning the empty list so failures are recorded without changing behavior.lib/features/transcription/domain/transcription_model.dart (1)
1-1: Unused import ofmedical_insights.dart.The import is not referenced anywhere in this file.
TranscriptionHistoryModelstores the flattened fields (summary,symptoms,medicines) rather than aMedicalInsightsobject.🧹 Remove unused import
-import 'medical_insights.dart'; - class TranscriptionHistoryModel {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/features/transcription/domain/transcription_model.dart` at line 1, The file contains an unused import "import 'medical_insights.dart';" which is not referenced by TranscriptionHistoryModel (it stores flattened fields like summary, symptoms, medicines); remove that import line to eliminate the unused dependency and keep the file focused on TranscriptionHistoryModel.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/features/transcription/data/local_storage_service.dart`:
- Around line 15-28: The save() implementation currently calls getAll() (which
returns a reversed newest-first list) causing order corruption; change save() to
read the raw stored value from _storage.read(key: _key), jsonDecode it into a
List preserving the stored oldest→newest order (or use an existing raw-get
helper if available), append the new TranscriptionHistoryModel.toJson() to that
list (or start a new list if null/empty), jsonEncode the result and write it
back with _storage.write(key: _key, value: ...); keep getAll()'s reversed
display behavior untouched.
In `@lib/features/transcription/domain/transcription_model.dart`:
- Around line 25-27: The createdAt parsing in the Transcription model's fromJson
uses DateTime.parse directly and can throw FormatException on malformed input;
add a defensive static helper (e.g., static DateTime _parseDate(dynamic value))
in the Transcription model/class that attempts DateTime.parse inside a try-catch
and returns DateTime.now() on any error or when value is null, then replace the
inline DateTime.parse(json['createdAt']) usage with a call to
_parseDate(json['createdAt']) to ensure fromJson never throws on bad date
strings.
In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Line 11: The code imports a non-existent TranscriptionModel and then
instantiates and calls copyWith on it; either define TranscriptionModel or
switch to TranscriptionHistoryModel — to fix, add a TranscriptionModel class (or
adapt TranscriptionHistoryModel) that includes fields rawTranscript, insights
(MedicalInsights), prescription, a const default constructor, and a
copyWith({String? rawTranscript, MedicalInsights? insights, String?
prescription}) returning a new instance so lines using TranscriptionModel data =
const TranscriptionModel(); and data.copyWith(...) compile; ensure the class
name and copyWith signature match usages in transcription_controller.dart
(transcriptionModel/TranscriptionModel and copyWith).
- Around line 17-31: The UI calls controller.toggleRecording but that method and
its helpers were removed; restore toggleRecording plus implementations of
_startRecording and _stopRecording that use the existing _audioRecorder to
begin/stop capture, set/clear _recordingPath, update the isRecording
getter/state (TranscriptionState or a boolean), manage the _waveformTimer to
periodically update waveformValues and call notifyListeners(), persist recording
metadata via _localStorageService as needed, and handle errors by setting
errorMessage and notifying listeners so microphone taps no longer cause
NoSuchMethodError; locate these changes around the TranscriptionController class
members (_audioRecorder, _recordingPath, waveformValues, _waveformTimer,
isRecording) and wire them to the existing state transitions and
notifyListeners() calls.
- Around line 69-77: The controller currently calls non-existent methods and
uses a missing model/copyWith; fix by (1) adding a TranscriptionModel class (or
extend TranscriptionHistoryModel) with fields rawTranscript, insights
(MedicalInsights), prescription (String) and a copyWith({MedicalInsights?
insights, String? prescription, String? rawTranscript}) method so
data.copyWith(...) works; (2) change the controller code that calls
_geminiService.generateSummary/generatePrescription to call only
_geminiService.generateInsights(transcript) via Future or await, then extract
summary and prescription from the returned MedicalInsights (e.g.,
insights.summary and insights.medicines or insights.prescription field) and
assign them to the TranscriptionModel via copyWith; keep references to
_geminiService.generateInsights, TranscriptionModel, MedicalInsights, and
data.copyWith to locate the changes.
---
Nitpick comments:
In `@lib/features/transcription/data/deepgram_service.dart`:
- Around line 18-57: In _retryPost, avoid the extra backoff delay after the
final 5xx attempt: when handling the 5xx branch inside the loop (the block that
currently does await Future.delayed(Duration(seconds: 2 * (attempt + 1))); and
then continue), only perform that await when attempt < retries - 1 so the last
attempt does not sleep before the method falls through to the final throw; keep
the rest of the retry/backoff logic (including the unconditional end-of-loop
delay for non-5xx paths) unchanged.
In `@lib/features/transcription/data/local_storage_service.dart`:
- Around line 46-48: The catch block that currently swallows exceptions in
local_storage_service.dart should log decryption/parsing failures for
diagnostics: add the import "import 'dart:developer' as developer;", change the
naked catch to capture the stack (catch (e, st)) in the method that returns the
empty list, and call developer.log with a clear message (e.g., "Failed to
load/decrypt/parsing transcriptions"), passing error: e and stackTrace: st
before returning the empty list so failures are recorded without changing
behavior.
In `@lib/features/transcription/domain/transcription_model.dart`:
- Line 1: The file contains an unused import "import 'medical_insights.dart';"
which is not referenced by TranscriptionHistoryModel (it stores flattened fields
like summary, symptoms, medicines); remove that import line to eliminate the
unused dependency and keep the file focused on TranscriptionHistoryModel.
In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 120-124: The method checkConfigStatus has inconsistent 2-space
indentation; reformat its body to use standard Dart indentation (typically 2
spaces per indent level? actually file uses different—match the rest of the
file) so it aligns with the surrounding code: update the indentation inside
checkConfigStatus and its if-block and the _setError call to match the file's
existing style, leaving the logic unchanged (function name checkConfigStatus and
call to _setError remain as-is).
🪄 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
Run ID: e69c8c56-08ed-4f90-9f50-492644bf0c6e
📒 Files selected for processing (5)
lib/features/transcription/data/deepgram_service.dartlib/features/transcription/data/local_storage_service.dartlib/features/transcription/domain/transcription_model.dartlib/features/transcription/presentation/history_screen.dartlib/features/transcription/presentation/transcription_controller.dart
🚧 Files skipped from review as they are similar to previous changes (1)
- lib/features/transcription/presentation/history_screen.dart
lib/features/transcription/presentation/transcription_controller.dart
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
lib/features/transcription/presentation/transcription_controller.dart (2)
102-106: Minor: Inconsistent indentation in method body.The body of
checkConfigStatususes 2-space indentation while the rest of the file uses 4-space indentation.Suggested fix
void checkConfigStatus(bool isLoaded) { - if (!isLoaded) { - _setError('Configuration Error: API keys could not be loaded. Please check your .env file.'); - } + if (!isLoaded) { + _setError('Configuration Error: API keys could not be loaded. Please check your .env file.'); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/features/transcription/presentation/transcription_controller.dart` around lines 102 - 106, Fix the inconsistent 2-space indentation inside the checkConfigStatus method by aligning its body to the file's 4-space indentation style; update the lines within the checkConfigStatus function (the if block and call to _setError) so they use 4 spaces and match surrounding methods' formatting.
1-12: Remove unused import.
dart:developeris imported (line 2) but is not used anywhere in the file. Remove the import to clean up unused dependencies.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/features/transcription/presentation/transcription_controller.dart` around lines 1 - 12, Remove the unused import of dart:developer from transcription_controller.dart by deleting the line importing 'dart:developer' (the unused developer alias), leaving only the necessary imports (e.g., dart:async, package:flutter/foundation.dart, package:record/record.dart, etc.); then run the analyzer/formatter to ensure no remaining unused-import warnings for the TranscriptionController file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 108-113: The dispose() method references an undeclared
_waveformTimer which causes a compile error; either declare a nullable Timer
field named _waveformTimer and add import 'dart:async' to the file (if waveform
animation/timer is required) or remove the _waveformTimer?.cancel() call from
dispose() (if waveform functionality was removed) — locate the dispose method in
the TranscriptionController class and update accordingly alongside
_audioRecorder.dispose().
---
Nitpick comments:
In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 102-106: Fix the inconsistent 2-space indentation inside the
checkConfigStatus method by aligning its body to the file's 4-space indentation
style; update the lines within the checkConfigStatus function (the if block and
call to _setError) so they use 4 spaces and match surrounding methods'
formatting.
- Around line 1-12: Remove the unused import of dart:developer from
transcription_controller.dart by deleting the line importing 'dart:developer'
(the unused developer alias), leaving only the necessary imports (e.g.,
dart:async, package:flutter/foundation.dart, package:record/record.dart, etc.);
then run the analyzer/formatter to ensure no remaining unused-import warnings
for the TranscriptionController file.
🪄 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
Run ID: 31a9bd48-0215-4201-a5e0-12a1bf01addb
⛔ Files ignored due to path filters (1)
pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
android/gradle/wrapper/gradle-wrapper.propertiesandroid/settings.gradlelib/features/transcription/data/local_storage_service.dartlib/features/transcription/domain/transcription_history_model.dartlib/features/transcription/presentation/transcription_controller.dartlinux/flutter/generated_plugin_registrant.cclinux/flutter/generated_plugins.cmakemacos/Flutter/GeneratedPluginRegistrant.swiftpubspec.yaml
✅ Files skipped from review due to trivial changes (3)
- android/gradle/wrapper/gradle-wrapper.properties
- pubspec.yaml
- android/settings.gradle
🚧 Files skipped from review as they are similar to previous changes (2)
- lib/features/transcription/domain/transcription_history_model.dart
- lib/features/transcription/data/local_storage_service.dart
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
lib/features/transcription/presentation/transcription_controller.dart (2)
79-99:⚠️ Potential issue | 🟠 MajorKeep successful insights as
doneeven if history save fails.The same
try/catchhandles both AI generation and persistence, so a local save failure can incorrectly flip a successful result intoerror.Suggested restructuring
try { final insights = await _geminiService.generateInsights(transcript); data = data.copyWith( rawTranscript: transcript, insights: insights, summary: insights.summary, ); + state = TranscriptionState.done; + notifyListeners(); - final history = TranscriptionHistoryModel( - transcript: transcript, - summary: insights.summary, - symptoms: insights.symptoms, - medicines: insights.medicines, - createdAt: DateTime.now(), - ); - - await _localStorageService.save(history); - state = TranscriptionState.done; + try { + final history = TranscriptionHistoryModel( + transcript: transcript, + summary: insights.summary, + symptoms: insights.symptoms, + medicines: insights.medicines, + createdAt: DateTime.now(), + ); + await _localStorageService.save(history); + } catch (e) { + developer.log('History save failed: $e'); + } } catch (e) { _setError("AI Processing failed: $e"); - } finally { - notifyListeners(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/features/transcription/presentation/transcription_controller.dart` around lines 79 - 99, The AI generation and persistence are wrapped in one try/catch so a storage failure can mark a successful insights run as an error; split the flow so you call _geminiService.generateInsights(transcript), update data via data.copyWith(...) and set state = TranscriptionState.done immediately after successful generation, then separately attempt to create the TranscriptionHistoryModel and await _localStorageService.save(history) inside its own try/catch—on save failure call _setError or log the failure but do NOT change the previously set state; keep references to _geminiService.generateInsights, data.copyWith, TranscriptionHistoryModel, _localStorageService.save, state, and _setError to locate the code.
88-97:⚠️ Potential issue | 🟠 MajorVerify medical-history storage is encrypted at rest.
This path persists transcript/symptoms/medicines; if
LocalStorageServicestill uses plainSharedPreferences, it is a privacy/compliance risk.#!/bin/bash # Verify storage backend + encryption posture for transcription history set -euo pipefail echo "== Locate LocalStorageService ==" fd -i "local_storage_service.dart" lib echo "" echo "== Inspect implementation ==" cat lib/features/transcription/data/local_storage_service.dart echo "" echo "== Check for plain SharedPreferences usage ==" rg -n "SharedPreferences|getStringList|setStringList|setString" lib/features/transcription/data/local_storage_service.dart echo "" echo "== Check for encryption/secure storage usage ==" rg -n "flutter_secure_storage|encrypt|AES|Cipher|Key|secure" lib/features/transcription/data/local_storage_service.dartExpected verification outcome:
- If only
SharedPreferencesappears and no encryption/secure-storage signal exists, this finding is confirmed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/features/transcription/presentation/transcription_controller.dart` around lines 88 - 97, The TranscriptionHistoryModel (transcript/summary/symptoms/medicines) is being persisted via _localStorageService.save in transcription_controller.dart and must be stored encrypted at rest; update LocalStorageService (the class handling save/load) to use a secure backend (e.g., flutter_secure_storage or platform keystore) or perform strong client-side encryption (AES with a key stored in secure enclave/keystore) before writing, update its save/load APIs to accept encrypted payloads, and ensure _localStorageService.save used by TranscriptionState transitions encrypts data; add a short migration/compatibility path for existing plaintext entries and unit/integration tests that assert saved bytes are not plaintext and that decryption works via the LocalStorageService methods.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 58-60: When _audioRecorder.stop() returns null, the method
currently returns early without updating controller state; update the state to a
deterministic value before returning (e.g., set recording state to idle). Locate
the block where you call final path = await _audioRecorder.stop(); and replace
the early return with a state update then return — for example call your
existing state setter (emit(TranscriptionState.idle) or setRecording(false) or
_setIdleState()) or set an error state if you prefer
(emit(TranscriptionState.error) / setError(...)) before the return so the UI
flow remains consistent.
- Around line 45-46: The permission check currently returns silently when
!_audioRecorder.hasPermission(), leaving the UI unchanged; update the method
containing this code to set a user-visible error/state before returning — for
example, call a controller/state method (e.g., setError("Microphone permission
denied") or set transcriptionState = TranscriptionState.permissionDenied and
notify listeners) immediately before the return so the UI can show an error or
open a permission prompt; ensure you reference and update the existing UI/state
handling mechanism in TranscriptionController (or the surrounding class) rather
than just returning from the function that calls _audioRecorder.hasPermission().
---
Duplicate comments:
In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 79-99: The AI generation and persistence are wrapped in one
try/catch so a storage failure can mark a successful insights run as an error;
split the flow so you call _geminiService.generateInsights(transcript), update
data via data.copyWith(...) and set state = TranscriptionState.done immediately
after successful generation, then separately attempt to create the
TranscriptionHistoryModel and await _localStorageService.save(history) inside
its own try/catch—on save failure call _setError or log the failure but do NOT
change the previously set state; keep references to
_geminiService.generateInsights, data.copyWith, TranscriptionHistoryModel,
_localStorageService.save, state, and _setError to locate the code.
- Around line 88-97: The TranscriptionHistoryModel
(transcript/summary/symptoms/medicines) is being persisted via
_localStorageService.save in transcription_controller.dart and must be stored
encrypted at rest; update LocalStorageService (the class handling save/load) to
use a secure backend (e.g., flutter_secure_storage or platform keystore) or
perform strong client-side encryption (AES with a key stored in secure
enclave/keystore) before writing, update its save/load APIs to accept encrypted
payloads, and ensure _localStorageService.save used by TranscriptionState
transitions encrypts data; add a short migration/compatibility path for existing
plaintext entries and unit/integration tests that assert saved bytes are not
plaintext and that decryption works via the LocalStorageService methods.
🪄 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
Run ID: ad19f1a1-fafc-484f-8351-d58dcd1e7f60
📒 Files selected for processing (1)
lib/features/transcription/presentation/transcription_controller.dart
Overview
This PR introduces persistent transcription history, enabling users to store and revisit past AI-generated medical insights.
Features
1. Persistent Storage
2. History Screen
3. Reusable Insights UI
Changes Made
TranscriptionHistoryModelLocalStorageServiceTranscriptionControllerto store historyHistoryScreenImpact
Related Issue
Closes #33
✅ Checklist
We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact.
Summary by CodeRabbit
New Features
Improvements