Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ import 'screens/summary_screen.dart';
import 'screens/prescription_screen.dart';

Future<void> main() async {
await dotenv.load();
// Try loading .env if present; if not, continue and rely on --dart-define.
try {
await dotenv.load(fileName: '.env');
} catch (e) {
// No .env found in production builds; use --dart-define fallback
developer.log('No .env loaded: $e');
}

runApp(const MyApp());
}

Expand Down Expand Up @@ -186,7 +193,7 @@ class _TranscriptionScreenState extends State<TranscriptionScreen> with SingleTi

Future<void> _transcribeAudio() async {
try {
final apiKey = dotenv.env['DEEPGRAM_API_KEY'] ?? '';
final apiKey = getDeepgramApiKey();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing import for getDeepgramApiKey will cause a compile error.

The function getDeepgramApiKey() is called but not imported from config.dart. This will fail at compile time.

🔎 Proposed fix

Add the import at the top of the file with the other service imports:

 import 'services/chatbot_service.dart';
 import 'screens/transcription_detail_screen.dart';
 import 'screens/summary_screen.dart';
 import 'screens/prescription_screen.dart';
+import 'services/config.dart';

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In lib/main.dart around line 196, the call to getDeepgramApiKey() is missing its
import which will cause a compile error; add the import for the symbol from its
defining file (config.dart) alongside the other service imports at the top of
lib/main.dart so the function is recognized by the compiler.

final uri = Uri.parse('https://api.deepgram.com/v1/listen?model=nova-2');

final file = File(_recordingPath);
Expand Down Expand Up @@ -474,7 +481,13 @@ class _TranscriptionScreenState extends State<TranscriptionScreen> with SingleTi
() => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SummaryScreen(summary: _summaryContent),
builder: (context) => SummaryScreen(
summary: _summaryContent,
onRetry: () {
Navigator.pop(context);
_processWithGemini(_transcription);
Comment on lines +486 to +488
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add validation and concurrency guard before retrying Gemini processing.

The retry callback directly passes _transcription to _processWithGemini without validating its content or checking if processing is already in progress. This can lead to:

  1. Invalid input: _transcription might contain error messages like 'No speech detected', 'Transcription failed', or 'Error during transcription'. Line 228 shows the proper validation that should be replicated here.
  2. Concurrent processing: Users can trigger multiple retries before the previous one completes, causing race conditions and wasted API calls.
🔎 Proposed fix with validation and concurrency guard
  onRetry: () {
+   if (_isProcessing) return;
+   if (_transcription.isEmpty || _transcription == 'No speech detected') {
+     ScaffoldMessenger.of(context).showSnackBar(
+       const SnackBar(content: Text('No valid transcription to retry')),
+     );
+     return;
+   }
    Navigator.pop(context);
    _processWithGemini(_transcription);
  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onRetry: () {
Navigator.pop(context);
_processWithGemini(_transcription);
onRetry: () {
if (_isProcessing) return;
if (_transcription.isEmpty || _transcription == 'No speech detected') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No valid transcription to retry')),
);
return;
}
Navigator.pop(context);
_processWithGemini(_transcription);
},
🤖 Prompt for AI Agents
In lib/main.dart around lines 479-481, the onRetry callback pops the dialog and
calls _processWithGemini(_transcription) without validating the transcription or
checking for ongoing work; replicate the validation used at line 228 (reject
values like 'No speech detected', 'Transcription failed', 'Error during
transcription', empty or null) and add a concurrency guard (e.g., check an
_isProcessing boolean, return early if true, set it true before calling
_processWithGemini and false in finally/whenComplete). Also only call
Navigator.pop and start processing if validation passes and processing is not
already in progress.

},
),
),
),
),
Expand All @@ -487,7 +500,13 @@ class _TranscriptionScreenState extends State<TranscriptionScreen> with SingleTi
() => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PrescriptionScreen(prescription: _prescriptionContent),
builder: (context) => PrescriptionScreen(
prescription: _prescriptionContent,
onRetry: () {
Navigator.pop(context);
_processWithGemini(_transcription);
},
),
),
),
),
Expand Down
109 changes: 71 additions & 38 deletions lib/screens/prescription_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import 'package:share_plus/share_plus.dart';

class PrescriptionScreen extends StatefulWidget {
final String prescription;
final VoidCallback? onRetry;

const PrescriptionScreen({super.key, required this.prescription});
const PrescriptionScreen({super.key, required this.prescription, this.onRetry});

@override
State<PrescriptionScreen> createState() => _PrescriptionScreenState();
Expand Down Expand Up @@ -125,43 +126,75 @@ class _PrescriptionScreenState extends State<PrescriptionScreen> {
Expanded(
child: SingleChildScrollView(
child: widget.prescription.isEmpty
? const Text(
'No prescription available',
style: TextStyle(
fontSize: 16,
fontStyle: FontStyle.italic,
color: Colors.grey,
),
)
? Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.medication, size: 64, color: Colors.deepPurple.withOpacity(0.9)),
const SizedBox(height: 16),
const Text(
'No prescription available',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
const SizedBox(height: 8),
const Text(
'Generate a prescription by recording a conversation and processing it.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
if (widget.onRetry != null) ...[
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: widget.onRetry,
icon: const Icon(Icons.refresh),
label: const Text('Retry'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple,
),
),
]
],
),
),
)
: MarkdownBody(
data: widget.prescription,
styleSheet: MarkdownStyleSheet(
p: const TextStyle(
fontSize: 16,
height: 1.5,
color: Colors.black87,
),
h1: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
h2: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
h3: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
listBullet: const TextStyle(
fontSize: 16,
color: Colors.deepPurple,
),
),
),
data: widget.prescription,
styleSheet: MarkdownStyleSheet(
p: const TextStyle(
fontSize: 16,
height: 1.5,
color: Colors.black87,
),
h1: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
h2: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
h3: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
listBullet: const TextStyle(
fontSize: 16,
color: Colors.deepPurple,
),
),
),
),
),
],
Expand All @@ -171,7 +204,7 @@ class _PrescriptionScreenState extends State<PrescriptionScreen> {
),
),
floatingActionButton: FloatingActionButton(
onPressed: _isSaving ? null : _savePrescription,
onPressed: (widget.prescription.isEmpty || _isSaving) ? null : _savePrescription,
backgroundColor: Colors.deepPurple,
foregroundColor: Colors.white,
elevation: 8,
Expand Down
107 changes: 70 additions & 37 deletions lib/screens/summary_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import 'package:flutter_markdown/flutter_markdown.dart';

class SummaryScreen extends StatelessWidget {
final String summary;
final VoidCallback? onRetry;

const SummaryScreen({super.key, required this.summary});
const SummaryScreen({super.key, required this.summary, this.onRetry});

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -56,43 +57,75 @@ class SummaryScreen extends StatelessWidget {
Expanded(
child: SingleChildScrollView(
child: summary.isEmpty
? const Text(
'No summary available',
style: TextStyle(
fontSize: 16,
fontStyle: FontStyle.italic,
color: Colors.grey,
),
)
? Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.summarize, size: 64, color: Colors.deepPurple.withOpacity(0.9)),
const SizedBox(height: 16),
const Text(
'No summary available',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
const SizedBox(height: 8),
const Text(
'Try recording again or tap retry to process the latest transcription.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
if (onRetry != null) ...[
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: onRetry,
icon: const Icon(Icons.refresh),
label: const Text('Retry'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple,
),
),
]
],
),
),
)
: MarkdownBody(
data: summary,
styleSheet: MarkdownStyleSheet(
p: const TextStyle(
fontSize: 16,
height: 1.5,
color: Colors.black87,
),
h1: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
h2: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
h3: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
listBullet: const TextStyle(
fontSize: 16,
color: Colors.deepPurple,
),
),
),
data: summary,
styleSheet: MarkdownStyleSheet(
p: const TextStyle(
fontSize: 16,
height: 1.5,
color: Colors.black87,
),
h1: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
h2: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
h3: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
listBullet: const TextStyle(
fontSize: 16,
color: Colors.deepPurple,
),
),
),
),
),
],
Expand Down
3 changes: 2 additions & 1 deletion lib/services/chatbot_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';

class ChatbotService {
// Get API key from .env file
final String apiKey = dotenv.env['GEMINI_API_KEY'] ?? '';
// Get API key using config fallback (dart-define > .env)
String get apiKey => getGeminiApiKey();
Comment on lines 7 to +9
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing import for getGeminiApiKey will cause a compile error.

The getter references getGeminiApiKey() but the function is not imported from config.dart. This will fail at compile time.

Also, the flutter_dotenv import on line 4 is now unused and the comment is stale.

🔎 Proposed fix
 import 'dart:convert';
 import 'dart:developer' as developer;
 import 'package:http/http.dart' as http;
-import 'package:flutter_dotenv/flutter_dotenv.dart';
+import 'config.dart';

 class ChatbotService {
-  // Get API key from .env file
     // Get API key using config fallback (dart-define > .env)
     String get apiKey => getGeminiApiKey();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Get API key from .env file
final String apiKey = dotenv.env['GEMINI_API_KEY'] ?? '';
// Get API key using config fallback (dart-define > .env)
String get apiKey => getGeminiApiKey();
import 'dart:convert';
import 'dart:developer' as developer;
import 'package:http/http.dart' as http;
import 'config.dart';
class ChatbotService {
// Get API key using config fallback (dart-define > .env)
String get apiKey => getGeminiApiKey();
🤖 Prompt for AI Agents
In lib/services/chatbot_service.dart around lines 7 to 9, the getter references
getGeminiApiKey() but the function isn't imported which causes a compile error;
add the proper import for the file that exports getGeminiApiKey (e.g., import
'package:your_package/config.dart'; or the correct relative path) at the top of
the file, remove the now-unused flutter_dotenv import, and update or remove the
stale comment so imports and comments match the code.


// Get a response from Gemini based on a prompt
Future<String> getGeminiResponse(String prompt) async {
Expand Down
15 changes: 15 additions & 0 deletions lib/services/config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';

/// Return GEMINI API key from --dart-define or .env as fallback.
String getGeminiApiKey() {
const envValue = String.fromEnvironment('GEMINI_API_KEY');
if (envValue.isNotEmpty) return envValue;
return dotenv.env['GEMINI_API_KEY'] ?? '';
}

/// Return DEEPGRAM API key from --dart-define or .env as fallback.
String getDeepgramApiKey() {
const envValue = String.fromEnvironment('DEEPGRAM_API_KEY');
if (envValue.isNotEmpty) return envValue;
return dotenv.env['DEEPGRAM_API_KEY'] ?? '';
}