Gemini model selector dropdown#9
Conversation
📝 WalkthroughWalkthroughThis pull request adds dynamic model selection functionality to the application, allowing users to choose between different AI models via a dropdown UI. It updates the chatbot service to accept a selected model parameter and modifies the API request structure. Additionally, it introduces comprehensive Windows desktop platform support and web application configuration. Changes
Sequence DiagramsequenceDiagram
participant User as User (UI)
participant App as Main App
participant ChatbotSvc as ChatbotService
participant GeminiAPI as Gemini API
User->>App: Select Model from Dropdown
App->>App: Update selectedValue State
App->>ChatbotSvc: Create ChatbotService(model: selectedValue)
Note over ChatbotSvc: Model parameter captured
User->>App: Submit Transcription
App->>ChatbotSvc: generateResponse(transcription)
ChatbotSvc->>GeminiAPI: POST /v1beta/models/{selectedModel}:generateContent
Note over ChatbotSvc: Dynamic endpoint based on selected model
GeminiAPI-->>ChatbotSvc: Response with generated content
ChatbotSvc-->>App: Return processed response
App-->>User: Display AI-generated content
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
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)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. 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: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/services/chatbot_service.dart (1)
38-38:⚠️ Potential issue | 🟠 MajorUnsafe deep-nested response access will throw on unexpected payloads
If
candidatesis empty,contentis missing, orpartsis empty (e.g., when the Gemini 3 Flash model returns a thinking response with an empty text part), this line throws and falls through to the catch as a generic"Could not connect to API"error — masking the root cause.🛡️ Proposed fix with null-safe traversal
- final result = data['candidates'][0]['content']['parts'][0]['text']; + final candidates = data['candidates'] as List?; + final text = candidates?.isNotEmpty == true + ? (candidates![0]['content']?['parts'] as List?) + ?.cast<Map>() + .firstWhere((p) => p['text'] != null, orElse: () => {})['text'] + : null; + if (text == null) { + developer.log('Unexpected Gemini response structure: ${response.body}'); + return 'Error: Unexpected response format from Gemini API.'; + } + final result = text as String;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/services/chatbot_service.dart` at line 38, The direct deep access in the assignment to the variable "result" (final result = data['candidates'][0]['content']['parts'][0]['text']) is unsafe and will throw on unexpected payloads; update the code in chatbot_service.dart to null-safe traverse and validate each level (ensure data['candidates'] is a List with length>0, that its first element has a Map 'content' with 'parts' as a non-empty List, and that the first part contains a non-null 'text' String), and if any check fails return or throw a clear, specific error (or provide a safe fallback) and log the full payload for diagnostics so the catch block no longer masks the real cause.
🧹 Nitpick comments (8)
web/manifest.json (2)
6-7:background_colorandtheme_colordon't match the app's purple gradient.Both are set to
#0175C2(Flutter's default blue). Based on the PR screenshots, the app uses a purple gradient. These values control the splash screen and browser toolbar tint on Android/Chrome, so a mismatch will be visually jarring on first load and during install.♻️ Proposed fix
- "background_color": "#0175C2", - "theme_color": "#0175C2", + "background_color": "#6A1B9A", + "theme_color": "#6A1B9A",(Adjust the hex value to match the exact purple used in the app's design.)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/manifest.json` around lines 6 - 7, The manifest currently sets "background_color" and "theme_color" to the default blue (`#0175C2`); update both keys in web/manifest.json to the app's exact purple hex used in the UI (replace the values for "background_color" and "theme_color" with the correct purple) so the splash screen and browser toolbar match the app's purple gradient across Android/Chrome.
2-3: Replace default Flutter scaffold placeholders with DocPilot branding.
name,short_name, anddescriptionare all auto-generated defaults (doc_pilot_new_app_gradel_fix/"A new Flutter project."). These strings appear to end users as the installed PWA name in OS launchers and app drawers.♻️ Proposed fix
- "name": "doc_pilot_new_app_gradel_fix", - "short_name": "doc_pilot_new_app_gradel_fix", + "name": "DocPilot", + "short_name": "DocPilot", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", - "description": "A new Flutter project.", + "description": "AI-powered medical transcription, summarization, and prescription generation.",Also applies to: 8-8
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/manifest.json` around lines 2 - 3, Replace the auto-generated Flutter scaffold placeholders in the web manifest by updating the "name", "short_name", and "description" fields to DocPilot-specific strings; locate the keys "name", "short_name", and "description" in manifest.json and replace their current values ("doc_pilot_new_app_gradel_fix" / "A new Flutter project.") with the product-branded app name, a shorter launcher name, and a user-facing description for DocPilot so the PWA shows correct branding in OS launchers and app drawers.web/index.html (2)
20-20: Remove legacy IE compatibility meta tag.
<meta content="IE=Edge" http-equiv="X-UA-Compatible">has been obsolete since IE 11 reached EOL. Flutter web does not support IE, so this tag adds noise without benefit.♻️ Proposed fix
- <meta content="IE=Edge" http-equiv="X-UA-Compatible">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/index.html` at line 20, Remove the obsolete Internet Explorer compatibility meta tag from the HTML head: locate and delete the <meta content="IE=Edge" http-equiv="X-UA-Compatible"> element in web/index.html (the exact string "IE=Edge" with http-equiv="X-UA-Compatible") since Flutter web does not support IE and the tag is no longer useful.
21-21: Update placeholder app title and description with DocPilot branding.Lines 21, 26, and 32 still carry the default Flutter project identity (
doc_pilot_new_app_gradel_fix/"A new Flutter project."). Line 32's<title>is what appears in browser tabs; line 26 appears on iOS home screen.♻️ Proposed fix
- <meta name="description" content="A new Flutter project."> + <meta name="description" content="AI-powered medical transcription, summarization, and prescription generation."> ... - <meta name="apple-mobile-web-app-title" content="doc_pilot_new_app_gradel_fix"> + <meta name="apple-mobile-web-app-title" content="DocPilot"> ... - <title>doc_pilot_new_app_gradel_fix</title> + <title>DocPilot</title>Also applies to: 26-26, 32-32
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/index.html` at line 21, Replace the default Flutter placeholder values in the HTML head: update the meta description tag (currently content="A new Flutter project.") and the apple-mobile-web-app-title (the meta with the app name) and the <title> element (currently showing doc_pilot_new_app_gradel_fix) to DocPilot-branded values; specifically set the meta description to a concise DocPilot product description and change both the apple-mobile-web-app-title and the <title> text to "DocPilot" so browser tabs and iOS home screen show the correct branding.windows/runner/flutter_window.cpp (1)
30-32: Prefer[this]over[&]in the first-frame callback.Only
thisis needed inside the lambda body.[&]captures all locals by reference, which is overly broad and obscures intent even if there is no dangling-reference risk here (the engine lifetime is scoped toflutter_controller_).♻️ Proposed refactor
- flutter_controller_->engine()->SetNextFrameCallback([&]() { + flutter_controller_->engine()->SetNextFrameCallback([this]() { this->Show(); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@windows/runner/flutter_window.cpp` around lines 30 - 32, The lambda passed to flutter_controller_->engine()->SetNextFrameCallback currently uses a broad reference capture ([&]); change the capture to only capture this ([this]) since the lambda only calls this->Show(), so update the SetNextFrameCallback call to use [this] to make intent explicit and avoid unnecessary captures.lib/main.dart (2)
57-65:selectedValueanditemsshould be privateBoth are implementation details of
_TranscriptionScreenStateand have no reason to be publicly accessible. Rename to_selectedValueand_items.♻️ Proposed fix
- String selectedValue = 'gemma-3-27b-it'; - - final List<String> items = [ + String _selectedValue = 'gemma-3-27b-it'; + + final List<String> _items = [ 'gemma-3-27b-it', 'gemini-2.5-flash', - 'gemma-3-12b-it', - 'gemini-3-flash', + 'gemma-3-12b-it', + 'gemini-3-flash-preview', 'gemini-2.0-flash' ];Also update all references to
selectedValue→_selectedValueanditems→_itemsin thebuildmethod and the getter.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/main.dart` around lines 57 - 65, selectedValue and items are public but should be private fields of _TranscriptionScreenState; rename selectedValue → _selectedValue and items → _items, then update all references (uses in the build method and any getter(s)) to the new names so the state fields are private and consistent throughout the class.
365-388: Disable theDropdownButtonduring active recording / processingEven after fixing the
ChatbotServicesnapshot issue, allowing model changes while a recording or processing operation is in progress is confusing UX — the active model shown in the dropdown would no longer reflect the one actually being used for the current run.♻️ Proposed fix
DropdownButton( padding: EdgeInsets.only(left: 5.0), - value: selectedValue, + value: _selectedValue, icon: const Icon(Icons.arrow_drop_down), elevation: 16, - onChanged: (String? newValue) { + onChanged: (_isRecording || _isTranscribing || _isProcessing) + ? null + : (String? newValue) { if (newValue != null) { setState(() { - selectedValue = newValue; + _selectedValue = newValue; }); } }, - items: items.map((String item) { + items: _items.map((String item) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/main.dart` around lines 365 - 388, The dropdown allows changing models mid-run; disable it while an operation is active by wiring its enabled state to the app's recording/processing flag (e.g., isRecording/isProcessing/isRunning) — replace the onChanged handler in the DropdownButton with a conditional that is null when the active-run flag is true (and restores the current handler when false), and optionally provide a disabledHint or style change so the UI makes it clear the control is inactive; update any logic that reads selectedValue to ensure the current run continues using the model captured when the run started.lib/services/chatbot_service.dart (1)
14-15: Replacedeveloper.log
dart:developeris imported but onlydeveloper.logprovides named, leveled log entries and is filterable in DevTools.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/services/chatbot_service.dart` around lines 14 - 15, Replace the two print calls that emit the Gemini prompt header and the prompt body with developer.log calls: use developer.log('=== GEMINI PROMPT ===', name: 'chatbot_service') and developer.log(prompt, name: 'chatbot_service') (or similar log levels/labels) instead of print to produce named, leveled log entries; update the calls where the literal string '=== GEMINI PROMPT ===' and the variable prompt are printed.
🤖 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/main.dart`:
- Line 64: Replace the retiring model identifier 'gemini-2.0-flash' with a
supported model (for example 'gemini-2.5-flash' or 'gemini-2.5-flash-lite')
wherever it appears in the codebase (the literal 'gemini-2.0-flash' in
lib/main.dart); update any related configuration or constants that reference
that string to the new model name or remove the entry if the model is no longer
needed, and ensure any code that selects models (e.g., model selection lists or
defaults) is adjusted accordingly.
- Around line 225-230: The variable result extracted from
decodedResponse['results']['channels'][0]['alternatives'][0]['transcript'] may
be null; update the code around result and the setState block to guard against
null before calling isNotEmpty—e.g., compute a safe string (use a null check or
null-aware operator) like final safeResult = result ?? '' and then set
_transcription = safeResult.isNotEmpty ? safeResult : 'No speech detected' while
still setting _isTranscribing = false; ensure you reference the existing symbols
result, decodedResponse, _isTranscribing, and _transcription in the change.
- Around line 73-74: The _chatbotService getter reads selectedValue each time
which can change between awaits in _processWithGemini causing summary and
prescription to use different models; fix by snapshotting selectedValue at the
start of _processWithGemini into a local variable (e.g., final model =
selectedValue) and then instantiate a single ChatbotService instance (e.g.,
final chatbot = ChatbotService(model: model)) and use that chatbot for both the
summary and prescription calls instead of calling the _chatbotService getter
twice.
- Line 63: Replace the invalid Gemini model id 'gemini-3-flash' with the correct
identifier 'gemini-3-flash-preview' wherever the literal appears (e.g., the
model selection list in lib/main.dart) so API calls use the official model name
and avoid 404 errors; ensure any related documentation/comments or tests that
reference the old string are updated to the new 'gemini-3-flash-preview' as
well.
In `@lib/services/chatbot_service.dart`:
- Line 8: Rename the public field apiKey to a private field _apiKey on
ChatbotService and update all internal references to use _apiKey; additionally,
add an early guard inside getGeminiResponse that checks _apiKey is not empty and
throws or returns a clear, descriptive error (e.g., invalid/ missing API key)
before making any network requests so callers get an informative failure instead
of a 403; ensure any constructor or factory that previously relied on apiKey
still initializes _apiKey from dotenv.env['GEMINI_API_KEY'] ?? ''.
In `@windows/runner/flutter_window.cpp`:
- Around line 64-68: The WM_FONTCHANGE case dereferences flutter_controller_
without a nullptr check causing possible UB in MessageHandler; update
MessageHandler (or the WM_FONTCHANGE branch) to guard access by verifying
flutter_controller_ is non-null before calling
flutter_controller_->engine()->ReloadSystemFonts() (or early-return if nullptr),
ensuring the same lifecycle states handled in OnCreate/OnDestroy are respected;
keep the ReloadSystemFonts call only when flutter_controller_ and
flutter_controller_->engine() are valid.
In `@windows/runner/Runner.rc`:
- Around line 92-99: Update the placeholder PE version resource strings in
Runner.rc by replacing the hardcoded "com.example" CompanyName and all
occurrences of "doc_pilot_new_app_gradel_fix" (FileDescription, InternalName,
OriginalFilename, ProductName) with the real application/company values and
ensure they derive from the build BINARY_NAME; set BINARY_NAME correctly in
windows/CMakeLists.txt (symbol BINARY_NAME) and confirm main.cpp uses that
BINARY_NAME for the window title so the generated metadata picks up the correct
product/file names, then rebuild so Runner.rc receives the propagated values.
In `@windows/runner/utils.cpp`:
- Around line 48-55: The calculation of target_length subtracts 1 from
WideCharToMultiByte's return value, causing unsigned underflow when that call
returns 0; update the logic around WideCharToMultiByte/target_length so you
first capture the raw return (e.g., int bytes = ::WideCharToMultiByte(...)); if
bytes == 0 handle the error path (return empty utf8_string or propagate/log)
before computing target_length = (unsigned)bytes - 1, then proceed to resize
utf8_string and call WideCharToMultiByte again; reference the
WideCharToMultiByte call, the target_length variable, and utf8_string in your
change.
---
Outside diff comments:
In `@lib/services/chatbot_service.dart`:
- Line 38: The direct deep access in the assignment to the variable "result"
(final result = data['candidates'][0]['content']['parts'][0]['text']) is unsafe
and will throw on unexpected payloads; update the code in chatbot_service.dart
to null-safe traverse and validate each level (ensure data['candidates'] is a
List with length>0, that its first element has a Map 'content' with 'parts' as a
non-empty List, and that the first part contains a non-null 'text' String), and
if any check fails return or throw a clear, specific error (or provide a safe
fallback) and log the full payload for diagnostics so the catch block no longer
masks the real cause.
---
Nitpick comments:
In `@lib/main.dart`:
- Around line 57-65: selectedValue and items are public but should be private
fields of _TranscriptionScreenState; rename selectedValue → _selectedValue and
items → _items, then update all references (uses in the build method and any
getter(s)) to the new names so the state fields are private and consistent
throughout the class.
- Around line 365-388: The dropdown allows changing models mid-run; disable it
while an operation is active by wiring its enabled state to the app's
recording/processing flag (e.g., isRecording/isProcessing/isRunning) — replace
the onChanged handler in the DropdownButton with a conditional that is null when
the active-run flag is true (and restores the current handler when false), and
optionally provide a disabledHint or style change so the UI makes it clear the
control is inactive; update any logic that reads selectedValue to ensure the
current run continues using the model captured when the run started.
In `@lib/services/chatbot_service.dart`:
- Around line 14-15: Replace the two print calls that emit the Gemini prompt
header and the prompt body with developer.log calls: use developer.log('===
GEMINI PROMPT ===', name: 'chatbot_service') and developer.log(prompt, name:
'chatbot_service') (or similar log levels/labels) instead of print to produce
named, leveled log entries; update the calls where the literal string '===
GEMINI PROMPT ===' and the variable prompt are printed.
In `@web/index.html`:
- Line 20: Remove the obsolete Internet Explorer compatibility meta tag from the
HTML head: locate and delete the <meta content="IE=Edge"
http-equiv="X-UA-Compatible"> element in web/index.html (the exact string
"IE=Edge" with http-equiv="X-UA-Compatible") since Flutter web does not support
IE and the tag is no longer useful.
- Line 21: Replace the default Flutter placeholder values in the HTML head:
update the meta description tag (currently content="A new Flutter project.") and
the apple-mobile-web-app-title (the meta with the app name) and the <title>
element (currently showing doc_pilot_new_app_gradel_fix) to DocPilot-branded
values; specifically set the meta description to a concise DocPilot product
description and change both the apple-mobile-web-app-title and the <title> text
to "DocPilot" so browser tabs and iOS home screen show the correct branding.
In `@web/manifest.json`:
- Around line 6-7: The manifest currently sets "background_color" and
"theme_color" to the default blue (`#0175C2`); update both keys in
web/manifest.json to the app's exact purple hex used in the UI (replace the
values for "background_color" and "theme_color" with the correct purple) so the
splash screen and browser toolbar match the app's purple gradient across
Android/Chrome.
- Around line 2-3: Replace the auto-generated Flutter scaffold placeholders in
the web manifest by updating the "name", "short_name", and "description" fields
to DocPilot-specific strings; locate the keys "name", "short_name", and
"description" in manifest.json and replace their current values
("doc_pilot_new_app_gradel_fix" / "A new Flutter project.") with the
product-branded app name, a shorter launcher name, and a user-facing description
for DocPilot so the PWA shows correct branding in OS launchers and app drawers.
In `@windows/runner/flutter_window.cpp`:
- Around line 30-32: The lambda passed to
flutter_controller_->engine()->SetNextFrameCallback currently uses a broad
reference capture ([&]); change the capture to only capture this ([this]) since
the lambda only calls this->Show(), so update the SetNextFrameCallback call to
use [this] to make intent explicit and avoid unnecessary captures.
| ChatbotService get _chatbotService => | ||
| ChatbotService(model: selectedValue); //Implemented a getter function |
There was a problem hiding this comment.
Getter creates a fresh ChatbotService per call — model can change between summary and prescription
_chatbotService reads selectedValue at invocation time. Inside _processWithGemini the getter is called twice (once for summary, once for prescription). Because the DropdownButton is not disabled during processing, a user can change the selected model between those two awaits, resulting in the summary and prescription being generated by different models for the same transcript.
Snapshot the selected model once at the start of _processWithGemini:
♻️ Proposed fix
- ChatbotService get _chatbotService =>
- ChatbotService(model: selectedValue); //Implemented a getter function
+ // Instantiate ChatbotService with the snapshot of selectedValue for the processing run.In _processWithGemini:
- Future<void> _processWithGemini(String transcription) async {
+ Future<void> _processWithGemini(String transcription) async {
+ final chatbotService = ChatbotService(model: selectedValue);
try {
final summary = await _chatbotService.getGeminiResponse(
+ final summary = await chatbotService.getGeminiResponse(
"Generate a summary of the conversation based on this transcription: $transcription");
final prescription = await _chatbotService.getGeminiResponse(
+ final prescription = await chatbotService.getGeminiResponse(
"Generate a prescription based on the conversation in this transcription: $transcription");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/main.dart` around lines 73 - 74, The _chatbotService getter reads
selectedValue each time which can change between awaits in _processWithGemini
causing summary and prescription to use different models; fix by snapshotting
selectedValue at the start of _processWithGemini into a local variable (e.g.,
final model = selectedValue) and then instantiate a single ChatbotService
instance (e.g., final chatbot = ChatbotService(model: model)) and use that
chatbot for both the summary and prescription calls instead of calling the
_chatbotService getter twice.
| final result = decodedResponse['results']['channels'][0]['alternatives'] | ||
| [0]['transcript']; | ||
|
|
||
| setState(() { | ||
| _isTranscribing = false; | ||
| _transcription = result.isNotEmpty ? result : 'No speech detected'; |
There was a problem hiding this comment.
result can be null — result.isNotEmpty throws NPE on unexpected Deepgram response shape
If the Deepgram response doesn't include the expected path (results > channels[0] > alternatives[0] > transcript), result is null. The call to result.isNotEmpty on Line 230 then throws a NoSuchMethodError which is caught and surfaces only as a generic 'Error during transcription'.
🛡️ Proposed fix
- final result = decodedResponse['results']['channels'][0]['alternatives']
- [0]['transcript'];
+ final result = (decodedResponse['results']?['channels'] as List?)
+ ?.firstOrNull?['alternatives']?[0]?['transcript'] as String? ?? '';
setState(() {
_isTranscribing = false;
- _transcription = result.isNotEmpty ? result : 'No speech detected';
+ _transcription = result.isNotEmpty ? result : 'No speech detected';📝 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.
| final result = decodedResponse['results']['channels'][0]['alternatives'] | |
| [0]['transcript']; | |
| setState(() { | |
| _isTranscribing = false; | |
| _transcription = result.isNotEmpty ? result : 'No speech detected'; | |
| final result = (decodedResponse['results']?['channels'] as List?) | |
| ?.firstOrNull?['alternatives']?[0]?['transcript'] as String? ?? ''; | |
| setState(() { | |
| _isTranscribing = false; | |
| _transcription = result.isNotEmpty ? result : 'No speech detected'; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/main.dart` around lines 225 - 230, The variable result extracted from
decodedResponse['results']['channels'][0]['alternatives'][0]['transcript'] may
be null; update the code around result and the setState block to guard against
null before calling isNotEmpty—e.g., compute a safe string (use a null check or
null-aware operator) like final safeResult = result ?? '' and then set
_transcription = safeResult.isNotEmpty ? safeResult : 'No speech detected' while
still setting _isTranscribing = false; ensure you reference the existing symbols
result, decodedResponse, _isTranscribing, and _transcription in the change.
There was a problem hiding this comment.
Will look into it in my next PR
not in the current scope of this feature
There was a problem hiding this comment.
@TheAnshulPrakash, understood! Since you're planning to address this in a future PR, would you like me to create a GitHub issue to track the null safety improvement for the Deepgram transcription response handling? This would help ensure it doesn't get overlooked.
| @@ -6,24 +6,30 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; | |||
| class ChatbotService { | |||
| // Get API key from .env file | |||
| final String apiKey = dotenv.env['GEMINI_API_KEY'] ?? ''; | |||
There was a problem hiding this comment.
apiKey is a public field — make it private, and guard against empty value
The API key being public exposes it to any call site holding a ChatbotService instance. Rename to _apiKey. Additionally, there's no guard against an empty key; the request is still dispatched and will fail with a cryptic 403 rather than an early, informative error.
🔒 Proposed fix
- final String apiKey = dotenv.env['GEMINI_API_KEY'] ?? '';
+ final String _apiKey = dotenv.env['GEMINI_API_KEY'] ?? '';And guard at the call site inside getGeminiResponse:
+ if (_apiKey.isEmpty) {
+ return 'Error: GEMINI_API_KEY is not configured.';
+ }
final url = Uri.parse(
- 'https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=$apiKey');
+ 'https://generativelanguage.googleapis.com/v1beta/models/$model:generateContent?key=$_apiKey');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/services/chatbot_service.dart` at line 8, Rename the public field apiKey
to a private field _apiKey on ChatbotService and update all internal references
to use _apiKey; additionally, add an early guard inside getGeminiResponse that
checks _apiKey is not empty and throws or returns a clear, descriptive error
(e.g., invalid/ missing API key) before making any network requests so callers
get an informative failure instead of a 403; ensure any constructor or factory
that previously relied on apiKey still initializes _apiKey from
dotenv.env['GEMINI_API_KEY'] ?? ''.
There was a problem hiding this comment.
Out of scope for current fix
Will look into it in my next PR
There was a problem hiding this comment.
@TheAnshulPrakash, understood! Would you like me to open a GitHub issue to track the API key security improvements for your next PR? I can create one with the details about making apiKey private and adding the empty-key guard.
windows/runner/flutter_window.cpp
Outdated
| switch (message) { | ||
| case WM_FONTCHANGE: | ||
| flutter_controller_->engine()->ReloadSystemFonts(); | ||
| break; | ||
| } |
There was a problem hiding this comment.
Null pointer dereference: flutter_controller_ is not guarded inside the switch.
The if (flutter_controller_) check on line 55 only early-returns when the controller handles the message. If flutter_controller_ is nullptr (e.g., before OnCreate completes or after OnDestroy nulls it) and WM_FONTCHANGE is delivered, execution falls into the switch and dereferences the null pointer on line 66. Since MessageHandler is declared noexcept, any undefined behavior that propagates will call std::terminate.
🐛 Proposed fix
switch (message) {
case WM_FONTCHANGE:
- flutter_controller_->engine()->ReloadSystemFonts();
+ if (flutter_controller_) {
+ flutter_controller_->engine()->ReloadSystemFonts();
+ }
break;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@windows/runner/flutter_window.cpp` around lines 64 - 68, The WM_FONTCHANGE
case dereferences flutter_controller_ without a nullptr check causing possible
UB in MessageHandler; update MessageHandler (or the WM_FONTCHANGE branch) to
guard access by verifying flutter_controller_ is non-null before calling
flutter_controller_->engine()->ReloadSystemFonts() (or early-return if nullptr),
ensuring the same lifecycle states handled in OnCreate/OnDestroy are respected;
keep the ReloadSystemFonts call only when flutter_controller_ and
flutter_controller_->engine() are valid.
There was a problem hiding this comment.
Will look into it in my next PR
not in the current scope of this feature
There was a problem hiding this comment.
@TheAnshulPrakash, understood! I appreciate you acknowledging the issue. Since you plan to address it in a future PR, would you like me to create a GitHub issue to track this null pointer safety fix? That way it won't be forgotten and can be properly scoped in your next PR.
windows/runner/Runner.rc
Outdated
| VALUE "CompanyName", "com.example" "\0" | ||
| VALUE "FileDescription", "doc_pilot_new_app_gradel_fix" "\0" | ||
| VALUE "FileVersion", VERSION_AS_STRING "\0" | ||
| VALUE "InternalName", "doc_pilot_new_app_gradel_fix" "\0" | ||
| VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" | ||
| VALUE "OriginalFilename", "doc_pilot_new_app_gradel_fix.exe" "\0" | ||
| VALUE "ProductName", "doc_pilot_new_app_gradel_fix" "\0" | ||
| VALUE "ProductVersion", VERSION_AS_STRING "\0" |
There was a problem hiding this comment.
Placeholder metadata values will appear in the shipped binary's Windows properties.
CompanyName is still "com.example" and all product/file name fields embed "doc_pilot_new_app_gradel_fix" — the raw Flutter project slug with a gradel_fix suffix that looks like a leftover debug artifact. These strings are embedded in the PE version resource and are visible to users via Windows Explorer → Properties → Details.
The root fix should be applied in windows/CMakeLists.txt where BINARY_NAME is defined (line 7) and propagated to the window title (main.cpp line 30). The metadata fields here will then pick it up automatically. CompanyName should also be corrected directly in this file.
🛠️ Proposed fix
- VALUE "CompanyName", "com.example" "\0"
- VALUE "FileDescription", "doc_pilot_new_app_gradel_fix" "\0"
+ VALUE "CompanyName", "AOSSIE" "\0"
+ VALUE "FileDescription", "DocPilot" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
- VALUE "InternalName", "doc_pilot_new_app_gradel_fix" "\0"
- VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0"
- VALUE "OriginalFilename", "doc_pilot_new_app_gradel_fix.exe" "\0"
- VALUE "ProductName", "doc_pilot_new_app_gradel_fix" "\0"
+ VALUE "InternalName", "doc_pilot" "\0"
+ VALUE "LegalCopyright", "Copyright (C) 2026 AOSSIE. All rights reserved." "\0"
+ VALUE "OriginalFilename", "doc_pilot.exe" "\0"
+ VALUE "ProductName", "DocPilot" "\0"And in windows/CMakeLists.txt line 7:
-set(BINARY_NAME "doc_pilot_new_app_gradel_fix")
+set(BINARY_NAME "doc_pilot")📝 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.
| VALUE "CompanyName", "com.example" "\0" | |
| VALUE "FileDescription", "doc_pilot_new_app_gradel_fix" "\0" | |
| VALUE "FileVersion", VERSION_AS_STRING "\0" | |
| VALUE "InternalName", "doc_pilot_new_app_gradel_fix" "\0" | |
| VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" | |
| VALUE "OriginalFilename", "doc_pilot_new_app_gradel_fix.exe" "\0" | |
| VALUE "ProductName", "doc_pilot_new_app_gradel_fix" "\0" | |
| VALUE "ProductVersion", VERSION_AS_STRING "\0" | |
| VALUE "CompanyName", "AOSSIE" "\0" | |
| VALUE "FileDescription", "DocPilot" "\0" | |
| VALUE "FileVersion", VERSION_AS_STRING "\0" | |
| VALUE "InternalName", "doc_pilot" "\0" | |
| VALUE "LegalCopyright", "Copyright (C) 2026 AOSSIE. All rights reserved." "\0" | |
| VALUE "OriginalFilename", "doc_pilot.exe" "\0" | |
| VALUE "ProductName", "DocPilot" "\0" | |
| VALUE "ProductVersion", VERSION_AS_STRING "\0" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@windows/runner/Runner.rc` around lines 92 - 99, Update the placeholder PE
version resource strings in Runner.rc by replacing the hardcoded "com.example"
CompanyName and all occurrences of "doc_pilot_new_app_gradel_fix"
(FileDescription, InternalName, OriginalFilename, ProductName) with the real
application/company values and ensure they derive from the build BINARY_NAME;
set BINARY_NAME correctly in windows/CMakeLists.txt (symbol BINARY_NAME) and
confirm main.cpp uses that BINARY_NAME for the window title so the generated
metadata picks up the correct product/file names, then rebuild so Runner.rc
receives the propagated values.
windows/runner/utils.cpp
Outdated
| unsigned int target_length = ::WideCharToMultiByte( | ||
| CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, | ||
| -1, nullptr, 0, nullptr, nullptr) | ||
| -1; // remove the trailing null character | ||
| int input_length = (int)wcslen(utf16_string); | ||
| std::string utf8_string; | ||
| if (target_length == 0 || target_length > utf8_string.max_size()) { | ||
| return utf8_string; |
There was a problem hiding this comment.
Unsigned underflow when WideCharToMultiByte fails silently bypasses the guard.
If the first WideCharToMultiByte call returns 0 (failure), target_length becomes (unsigned int)(0 - 1) = UINT_MAX. On 64-bit Windows, std::string::max_size() is typically ≈ 2⁶³, which is far larger than UINT_MAX, so the guard on line 54 passes and utf8_string.resize(UINT_MAX) is attempted, throwing std::bad_alloc.
The guard should explicitly check for the failure return value before subtracting:
🛠️ Proposed fix
- unsigned int target_length = ::WideCharToMultiByte(
- CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
- -1, nullptr, 0, nullptr, nullptr)
- -1; // remove the trailing null character
- int input_length = (int)wcslen(utf16_string);
- std::string utf8_string;
- if (target_length == 0 || target_length > utf8_string.max_size()) {
- return utf8_string;
- }
+ int raw_length = ::WideCharToMultiByte(
+ CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+ -1, nullptr, 0, nullptr, nullptr);
+ if (raw_length <= 1) {
+ return std::string();
+ }
+ unsigned int target_length = static_cast<unsigned int>(raw_length - 1);
+ int input_length = static_cast<int>(wcslen(utf16_string));
+ std::string utf8_string;
+ if (target_length > utf8_string.max_size()) {
+ return utf8_string;
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@windows/runner/utils.cpp` around lines 48 - 55, The calculation of
target_length subtracts 1 from WideCharToMultiByte's return value, causing
unsigned underflow when that call returns 0; update the logic around
WideCharToMultiByte/target_length so you first capture the raw return (e.g., int
bytes = ::WideCharToMultiByte(...)); if bytes == 0 handle the error path (return
empty utf8_string or propagate/log) before computing target_length =
(unsigned)bytes - 1, then proceed to resize utf8_string and call
WideCharToMultiByte again; reference the WideCharToMultiByte call, the
target_length variable, and utf8_string in your change.
|
Please resolve the merge conflicts before review. Your PR will only be reviewed by a maintainer after all conflicts have been resolved. 📺 Watch this video to understand why conflicts occur and how to resolve them: |
Addressed Issues:
Fixes #8
Screenshots/Recordings:
Additional Notes/Description:
This PR introduces a model selection feature in the DocPilot UI, transitioning the AI service from a hardcoded model to a dynamic configuration. Users can now choose between various Google-provided models based on their specific needs for medical summarization and prescription generation.
Key Additions:
Model Selection UI: Integrated a dropdown/selector to toggle between models.
Gemini 3 Support: Added support for the state-of-the-art Gemini 3 Flash and Pro models for high-reasoning tasks.
Gemma Integration: Included the capable open-weight Gemma models for efficient, fast processing.
Checklist
AI Usage Disclosure
Check one of the checkboxes below:
I have used the following AI models and tools: NONE
Summary by CodeRabbit
Release Notes