Skip to content

Gemini model selector dropdown#9

Open
TheAnshulPrakash wants to merge 3 commits intoAOSSIE-Org:mainfrom
TheAnshulPrakash:gemini-model-selector-dropdown
Open

Gemini model selector dropdown#9
TheAnshulPrakash wants to merge 3 commits intoAOSSIE-Org:mainfrom
TheAnshulPrakash:gemini-model-selector-dropdown

Conversation

@TheAnshulPrakash
Copy link

@TheAnshulPrakash TheAnshulPrakash commented Feb 21, 2026

Addressed Issues:

Fixes #8

Screenshots/Recordings:

Screenshot_20260221_233943
Screenshot_20260221_232521

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

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions
  • [] If applicable, I have made corresponding changes or additions to the documentation
  • [] If applicable, I have made corresponding changes or additions to tests
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contribution Guidelines
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.

AI Usage Disclosure

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: NONE

Summary by CodeRabbit

Release Notes

  • New Features
    • Added model selection feature to the chatbot interface, enabling users to choose between different AI models for their interactions
    • Added Progressive Web App (PWA) support with improved web deployment configuration
    • Added Windows desktop platform support for expanded device compatibility

@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Model Selection Feature
lib/main.dart, lib/services/chatbot_service.dart
Adds dropdown UI for model selection in main.dart; converts ChatbotService to accept a required model parameter and dynamically constructs API endpoints. Updates request payload structure from flat to nested format with "parts" array.
Metadata
.metadata
Updates global and platform-specific revision identifiers across all platforms (android, ios, linux, macos, web, windows, root).
Web Platform
web/index.html, web/manifest.json
Introduces Flutter web app HTML scaffold with meta tags, manifest link, and PWA configuration including app metadata, icons, and theme colors.
Windows Build Configuration
windows/CMakeLists.txt, windows/.gitignore, windows/flutter/CMakeLists.txt, windows/flutter/generated_plugins.cmake, windows/flutter/generated_plugin_registrant.*
Establishes Windows CMake build system, plugin registration infrastructure, and ephemeral directory cleanup. Integrates Flutter build artifacts and plugin linkage.
Windows Runner Implementation
windows/runner/CMakeLists.txt, windows/runner/main.cpp, windows/runner/flutter_window.*, windows/runner/win32_window.*, windows/runner/utils.*, windows/runner/Runner.rc, windows/runner/runner.exe.manifest, windows/runner/resource.h
Implements Windows application entry point, Flutter window integration, Win32 window management with DPI awareness, theme support, console utilities, and resource definitions. Includes complete window lifecycle, message handling, and Windows-specific utilities for UTF-16/UTF-8 conversion and command-line argument parsing.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • AOSSIE-Org/DocPilot#1: Adds initial ChatbotService and main application structure; this PR extends it with dynamic model selection and Windows platform support.

Poem

🐰 A dropdown appears, oh what a delight!
Models to choose—Gemini Flash or bright Pro!
Windows now runs where the rabbit takes flight,
Web and desktop in harmony glow.
From Dart to C\++, the platforms now grow! 🚀

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes extensive Windows platform support files (.gitignore, CMakeLists.txt, runner configuration, Flutter plugin integration) that are not related to the model selector feature described in issue #8. The Windows runner and build configuration files should be separated into a distinct PR focused on Windows platform support, keeping this PR focused solely on the model selector feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 15.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Gemini model selector dropdown' is clear, concise, and accurately describes the main change: adding a dropdown UI component to select between different AI models.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #8: adds a UI dropdown for model selection (implemented in lib/main.dart), updates the service layer to pass selected model to the API (lib/services/chatbot_service.dart), and supports multiple models (Gemma-3-27b, Gemini-2.5-flash, Gemma-3-12b-lt, Gemini-3-flash).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 | 🟠 Major

Unsafe deep-nested response access will throw on unexpected payloads

If candidates is empty, content is missing, or parts is 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_color and theme_color don'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, and description are 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 this is 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 to flutter_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: selectedValue and items should be private

Both are implementation details of _TranscriptionScreenState and have no reason to be publicly accessible. Rename to _selectedValue and _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_selectedValue and items_items in the build method 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 the DropdownButton during active recording / processing

Even after fixing the ChatbotService snapshot 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: Replace print with developer.log

dart:developer is imported but only print is used for logging. developer.log provides 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.

Comment on lines +73 to +74
ChatbotService get _chatbotService =>
ChatbotService(model: selectedValue); //Implemented a getter function
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +225 to 230
final result = decodedResponse['results']['channels'][0]['alternatives']
[0]['transcript'];

setState(() {
_isTranscribing = false;
_transcription = result.isNotEmpty ? result : 'No speech detected';
Copy link

@coderabbitai coderabbitai bot Feb 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Copy link
Author

Choose a reason for hiding this comment

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

Will look into it in my next PR

not in the current scope of this feature

Copy link

Choose a reason for hiding this comment

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

@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'] ?? '';
Copy link

@coderabbitai coderabbitai bot Feb 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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'] ?? ''.

Copy link
Author

@TheAnshulPrakash TheAnshulPrakash Feb 21, 2026

Choose a reason for hiding this comment

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

Out of scope for current fix

Will look into it in my next PR

Copy link

Choose a reason for hiding this comment

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

@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.

Comment on lines +64 to +68
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
Copy link

@coderabbitai coderabbitai bot Feb 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Copy link
Author

Choose a reason for hiding this comment

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

Will look into it in my next PR

not in the current scope of this feature

Copy link

Choose a reason for hiding this comment

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

@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.

Comment on lines +92 to +99
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"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines +48 to +55
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;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

@github-actions
Copy link

⚠️ This PR has merge conflicts.

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:
https://www.youtube.com/watch?v=Sqsz1-o7nXk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Enable Dynamic Model Selection (Gemini 3 & Gemma Support)

1 participant