Skip to content

feat: Implement Gemini AI service for AI coach functionality - #49

Merged
Devasy merged 4 commits into
Revamp-of-UIfrom
feat/ai-coach
May 30, 2026
Merged

feat: Implement Gemini AI service for AI coach functionality#49
Devasy merged 4 commits into
Revamp-of-UIfrom
feat/ai-coach

Conversation

@Devasy

@Devasy Devasy commented May 29, 2026

Copy link
Copy Markdown
Owner
  • Added GeminiAiService to handle AI coach chat, program generation, and insights using Google Generative AI.
  • Created IAiService interface to define the contract for AI services.
  • Developed GeminiContextBuilder to construct context for AI interactions.
  • Introduced ConversationManager to manage AI conversations, including persistence and active conversation logic.
  • Implemented storage service methods for saving and retrieving AI conversations.
  • Built AiCoachViewModel to orchestrate AI interactions and manage conversation state.
  • Added unit tests for AiCoachViewModel, CoachToolService, and ConversationManager to ensure functionality and persistence.
  • Updated analytics and exercise progress views to use the new GeminiAiService.

Summary by CodeRabbit

  • New Features

    • AI Coach chat: persistent conversations with history, create/rename/delete, and resumed chats.
    • AI-accessible tools: coach can fetch workout stats, PRs, routines, workouts-in-range, goals, and muscle recovery.
  • Improvements

    • Switched to a new AI backend with token usage tracking and persisted usage; token usage UI with reset.
    • Markdown-formatted AI replies and improved program/insight generation using current-date context.
  • Tests

    • Unit tests added for AI view-model, coach tools, conversation manager, and AI usage persistence.

Review Change Stack

- Added GeminiAiService to handle AI coach chat, program generation, and insights using Google Generative AI.
- Created IAiService interface to define the contract for AI services.
- Developed GeminiContextBuilder to construct context for AI interactions.
- Introduced ConversationManager to manage AI conversations, including persistence and active conversation logic.
- Implemented storage service methods for saving and retrieving AI conversations.
- Built AiCoachViewModel to orchestrate AI interactions and manage conversation state.
- Added unit tests for AiCoachViewModel, CoachToolService, and ConversationManager to ensure functionality and persistence.
- Updated analytics and exercise progress views to use the new GeminiAiService.
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR introduces a comprehensive AI coach chat feature with persistent conversations and tool-calling support, alongside a refactored AI service layer. The feature enables conversational coaching with database-backed exercise/workout/recovery data queries, while updating existing screens to use the new GeminiAiService implementation of IAiService.

Changes

AI Coach Chat with Tool Calling

Layer / File(s) Summary
AI Service Contract & Chat Models
lib/services/interfaces/ai_service_interface.dart, lib/models/models.dart
IAiService defines the contract for AI backend with streaming replies and generation methods. New ChatMessage and Conversation models represent persisted chat data with JSON serialization, metadata, and copyWith support.
Storage & Conversation Manager
lib/services/interfaces/storage_service_interface.dart, lib/services/storage_service.dart, lib/services/managers/conversation_manager.dart
Storage interface extended with conversation CRUD methods. StorageService adds a Hive box for Conversation persistence. ConversationManager provides in-memory state management with async loading, active conversation tracking, title derivation, and listener-based updates.
Coach Tool Service
lib/services/ai/coach_tool_service.dart
Exposes six callable tools for the AI coach: exercise performance, workouts in range, routine performance, personal records, goal progress, and muscle recovery. Each tool resolves names, applies optional date filters, and returns structured JSON results with error payloads for failed resolutions.
Gemini AI Service with Tool Support
lib/services/ai/gemini_ai_service.dart, lib/services/gemini_context_builder.dart
GeminiAiService implements IAiService with updated default model (gemini-3.1-flash-lite). Introduces _makeModel helper supporting optional JSON and tool registration. streamCoachReply supports iterative tool-calling loops with onToolCall callback, bounded execution, and fallback messages. Updated system prompt injects current date and tool-calling guidance.
App Initialization & Dependency Injection
lib/main.dart
Replaces GeminiService with GeminiAiService provider, adds ConversationManager and CoachToolService providers using WorkoutProvider and PRManager dependencies. Initialization switches to reading GeminiAiService and loads persisted token usage.
AI Coach View Model
lib/viewmodels/ai_coach_view_model.dart
Orchestrates conversation management, AI streaming, and system prompt assembly. Manages _loading and _streamingText state, forwards ConversationManager listener updates, builds context via GeminiContextBuilder, and implements sendMessage with tool invocation via CoachToolService.
AI Coach Screen UI
lib/screens/ai_coach_screen.dart
Refactored from StatefulWidget to StatelessWidget providing AiCoachViewModel. Renders chat list from vm.messages with markdown rendering for model replies. Includes auto-scroll via listener, conversation history bottom sheet, new chat action, suggestion chips with local controller updates, and configuration gating.
Existing Feature Updates to GeminiAiService
lib/screens/ai_program_generator_screen.dart, lib/screens/home_screen.dart, lib/screens/widgets/exercise_progress_view.dart, lib/screens/widgets/muscle_detail_sheet.dart, lib/screens/widgets/profile_sections.dart, lib/screens/widgets/targets_tab.dart
Several screens updated to import and use GeminiAiService instead of GeminiService; all existing functionality preserved with only provider resolution and import changes.
Test Coverage
test/ai_coach_view_model_test.dart, test/coach_tool_service_test.dart, test/conversation_manager_test.dart, test/test_utils/mock_storage_service.dart, test/analytics_screen_test.dart, test/exercise_progress_view_test.dart, test/gemini_ai_service_usage_test.dart
AiCoachViewModel tests verify message flow, streaming aggregation, tool invocation, and conversation switching. CoachToolService tests validate all six tool handlers. ConversationManager tests cover lifecycle, persistence, sorting, and title derivation. GeminiAiService usage counters tested. Mock storage extended with conversation CRUD. Existing widget tests migrated to GeminiAiService.

Possibly related PRs

  • Devasy/Workout-logger#19: Extends the storage-layer code refactored in PR #19 by adding new conversation-related methods (saveConversation, getAllConversations, getConversation, deleteConversation) and Hive box support to the same IStorageService/StorageService abstraction.
πŸš₯ Pre-merge checks | βœ… 5
βœ… Passed checks (5 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title accurately summarizes the main change: implementing GeminiAiService for AI coach functionality, which is the primary focus of the changeset.
Docstring Coverage βœ… Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.

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


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.

@Devasy

Devasy commented May 29, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor
βœ… Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Devasy Devasy self-assigned this May 29, 2026
@Devasy Devasy added the enhancement New feature or request label May 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
workout-logger/lib/services/storage_service.dart (1)

345-376: 🧹 Nitpick | πŸ”΅ Trivial | πŸ—οΈ Heavy lift

Consider including conversations in export/import for data portability.

The exportAllData method does not include AI conversations (or training programs and personal records). While this is consistent with existing exclusions, users may expect their conversation history to transfer when exporting and importing data across devices.

πŸ’‘ Suggested enhancement

Add conversations to the export data map and corresponding import logic:

   final data = {
     'sessions': _sessionsBox.values
         .map(_normalizeExportValue)
         .toList(growable: false),
     'routines': _routinesBoxInstance.values
         .map(_normalizeExportValue)
         .toList(growable: false),
     'targets': _targetsBoxInstance.values
         .map(_normalizeExportValue)
         .toList(growable: false),
     'muscleGroups': _muscleGroupsBoxInstance.values
         .map(_normalizeExportValue)
         .toList(growable: false),
     'customExercises': _customExercisesBoxInstance.values
         .map(_normalizeExportValue)
         .toList(growable: false),
+    'aiConversations': _aiConversationsBoxInstance.values
+        .map(_normalizeExportValue)
+        .toList(growable: false),
     'settings': settingsMap,
     'exportDate': DateTime.now().toIso8601String(),
     'appVersion': _appVersion,
   };

And add corresponding import logic after line 451 following the existing merge pattern.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workout-logger/lib/services/storage_service.dart` around lines 345 - 376, The
exportAllData method currently omits AI conversations (and related user
artifacts); update exportAllData to include conversations by adding a
'conversations' entry to the exported data map that mirrors the other
collections (e.g., 'conversations':
_conversationsBoxInstance.values.map(_normalizeExportValue).toList(growable:
false)), and then implement corresponding import logic in the import path (the
method that consumes this JSON, e.g., importAllData or similar) to merge/restore
conversation entries using the same merge pattern used for sessions/routines
(follow existing behavior for deduping/merging and use the same helper(s) that
process normalized values).
πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@workout-logger/lib/services/ai/coach_tool_service.dart`:
- Around line 375-398: The current _resolveExercise and _resolveRoutine
functions pick the first partial match which can return the wrong entity; change
them to collect all partial matches (search _wp.allExercises and _wp.routines
using contains on lowercased names), return the single match if exactly one,
return null if none, and when more than one partial match exists return an
ambiguity signal (e.g., throw an AmbiguousMatchException or return a result type
carrying an ambiguity with the list of candidate names/IDs) so callers can
surface available_examples-style choices to the user instead of confidently
using the wrong entity.
- Around line 263-285: The routine and exercise performance outputs are
truncating the oldest 40 entries instead of the most recent 40; in
_routinePerformance, replace the final volume_over_time generation that uses
sessions.take(40) with logic that selects the most recent 40 sessions (e.g.,
iterate over sessions.reversed.take(40).toList().reversed) so the presented list
remains chronological but contains the latest data, and in _exercisePerformance
(and places using _wp.getVolumeProgression) similarly select the last 40 items
from the progression (e.g., progression.reversed.take(40).toList().reversed)
before mapping to the output so the newest logs are included.

In `@workout-logger/lib/services/managers/conversation_manager.dart`:
- Around line 83-94: The current renameConversation uses
_conversations.where(...).toList() to find a match which is inefficient; replace
that with _conversations.firstWhere((c) => c.id == id, orElse: () => null) (or
an equivalent null-safe check) to find the conversation directly, return if
null, then create updated via copyWith, update _active if ids match, call
_upsert(updated), await _storage.saveConversation(updated), and
notifyListeners(); keep the same symbols (renameConversation, _conversations,
_active, _upsert, _storage.saveConversation) but use firstWhere+orElse for
cleaner code.
- Around line 46-51: The selectConversation method creates an intermediate list
via _conversations.where(...).toList(); replace it with
_conversations.firstWhere(...) to avoid allocation: call
_conversations.firstWhere((c) => c.id == id, orElse: () => null) (adjust the
orElse return type to your Conversation nullable type if needed), check for null
and return early, then assign to _active and call notifyListeners(); this
changes only the body of selectConversation and references the existing symbols
_conversations, selectConversation, _active, and notifyListeners.

In `@workout-logger/lib/viewmodels/ai_coach_view_model.dart`:
- Around line 71-114: The sendMessage method can permanently leave _loading true
if any persistence (_conversations.appendMessage) or streaming code throws; wrap
the entire send flow in a try/finally so _loading and _streamingText are always
reset (set _streamingText = '' and _loading = false in finally) and keep
notifyListeners calls as needed; also fix the interpolation in the catch by
using $e instead of ${e} to satisfy flutter_lints. Use the existing sendMessage
function, the calls to _conversations.appendMessage, the buffer/catch block and
ensure the finally block clears state even on errors.
- Around line 28-37: Change the five-argument positional AiCoachViewModel
constructor to use named, required parameters to avoid ordering mistakes: update
the constructor signature to AiCoachViewModel({required GeminiAiService ai,
required CoachToolService coachTools, required ConversationManager
conversations, required WorkoutProvider wp, required SettingsProvider settings})
and assign these to the existing private fields (e.g. this._ai = ai;
this._coachTools = coachTools; etc.) while preserving the constructor body that
adds the listener (_conversations.addListener(notifyListeners)). Then update all
call sites (notably in ai_coach_screen.dart and the VM test) to call
AiCoachViewModel with named arguments (ai:, coachTools:, conversations:, wp:,
settings:) and keep the chained ..loadConversations() where used.
- Around line 56-67: newConversation and selectConversation can change the
active conversation while a sendMessage stream is in progress (when _loading is
true), causing ConversationManager.appendMessage to attach the model reply to
the wrong _active; update AiCoachViewModel.newConversation and
AiCoachViewModel.selectConversation to check the _loading flag and no-op (or
return a failed Future) when a send is active, and ensure any UI triggers (e.g.,
header β€œ+” / conversations sheet) are disabled when vm._loading or
vm.isConfigured && vm._loading is true so users cannot navigate mid-stream; this
prevents sendMessage from persisting the final model reply to the wrong
conversation by keeping _active stable during streaming.

---

Outside diff comments:
In `@workout-logger/lib/services/storage_service.dart`:
- Around line 345-376: The exportAllData method currently omits AI conversations
(and related user artifacts); update exportAllData to include conversations by
adding a 'conversations' entry to the exported data map that mirrors the other
collections (e.g., 'conversations':
_conversationsBoxInstance.values.map(_normalizeExportValue).toList(growable:
false)), and then implement corresponding import logic in the import path (the
method that consumes this JSON, e.g., importAllData or similar) to merge/restore
conversation entries using the same merge pattern used for sessions/routines
(follow existing behavior for deduping/merging and use the same helper(s) that
process normalized values).
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3dc0216f-b48c-472e-b156-7ec9aa3bf212

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 5bcf86d and 36a0f10.

πŸ“’ Files selected for processing (23)
  • workout-logger/lib/main.dart
  • workout-logger/lib/models/models.dart
  • workout-logger/lib/screens/ai_coach_screen.dart
  • workout-logger/lib/screens/ai_program_generator_screen.dart
  • workout-logger/lib/screens/home_screen.dart
  • workout-logger/lib/screens/widgets/exercise_progress_view.dart
  • workout-logger/lib/screens/widgets/muscle_detail_sheet.dart
  • workout-logger/lib/screens/widgets/profile_sections.dart
  • workout-logger/lib/screens/widgets/targets_tab.dart
  • workout-logger/lib/services/ai/coach_tool_service.dart
  • workout-logger/lib/services/ai/gemini_ai_service.dart
  • workout-logger/lib/services/gemini_context_builder.dart
  • workout-logger/lib/services/interfaces/ai_service_interface.dart
  • workout-logger/lib/services/interfaces/storage_service_interface.dart
  • workout-logger/lib/services/managers/conversation_manager.dart
  • workout-logger/lib/services/storage_service.dart
  • workout-logger/lib/viewmodels/ai_coach_view_model.dart
  • workout-logger/test/ai_coach_view_model_test.dart
  • workout-logger/test/analytics_screen_test.dart
  • workout-logger/test/coach_tool_service_test.dart
  • workout-logger/test/conversation_manager_test.dart
  • workout-logger/test/exercise_progress_view_test.dart
  • workout-logger/test/test_utils/mock_storage_service.dart

Comment thread workout-logger/lib/services/ai/coach_tool_service.dart
Comment thread workout-logger/lib/services/ai/coach_tool_service.dart
Comment thread workout-logger/lib/services/managers/conversation_manager.dart
Comment thread workout-logger/lib/services/managers/conversation_manager.dart
Comment thread workout-logger/lib/viewmodels/ai_coach_view_model.dart Outdated
Comment thread workout-logger/lib/viewmodels/ai_coach_view_model.dart
Comment thread workout-logger/lib/viewmodels/ai_coach_view_model.dart
@Devasy

Devasy commented May 29, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor
βœ… Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Devasy

Devasy commented May 30, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor
βœ… Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
workout-logger/lib/viewmodels/ai_coach_view_model.dart (1)

113-113: ⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Use $e instead of ${e} to satisfy flutter_lints.

The braces are unnecessary in '${e}' and trigger the unnecessary_brace_in_string_interps lint rule.

♻️ Simplify string interpolation
-      buffer.write('\n\n_Error: ${e}_');
+      buffer.write('\n\n_Error: $e_');

As per coding guidelines: Follow flutter_lints rules as enforced by analysis_options.yaml.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workout-logger/lib/viewmodels/ai_coach_view_model.dart` at line 113, Replace
the unnecessary braced interpolation in the error write call: locate the
buffer.write call that currently uses '\n\n_Error: ${e}_' (in
ai_coach_view_model.dart, inside the AI coach view model error handling) and
change it to use '\n\n_Error: $e_' so the string interpolation uses $e without
braces to satisfy the unnecessary_brace_in_string_interps flutter_lints rule.
πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@workout-logger/lib/main.dart`:
- Line 164: Wrap the call to gemini.loadUsage() in its own try-catch so failures
don't abort app startup: replace the direct await gemini.loadUsage() with a try
{ await gemini.loadUsage(); } catch (e, st) { /* log error via your logger
(e.g., logger.error or print) with e and st */ } so token-usage load errors are
logged and swallowed and initialization continues; reference gemini.loadUsage()
and the surrounding init/main function where it is called.

In `@workout-logger/test/gemini_ai_service_usage_test.dart`:
- Around line 34-37: The test is flaky because it relies on event-loop timing
after calling service.recordUsage; update the implementation and/or test so
persistence can be awaited deterministically: modify recordUsage in
GeminiAiService (or the class under test) to return a Future that completes when
persistence finishes OR add a new method like flush() or
waitForPendingPersistence() on GeminiAiService that completes when all pending
writes finish, and update the test to await that instead of awaiting
Future.delayed(Duration.zero); alternatively, allow injecting a synchronous/mock
storage into the service for tests and use that in this test to make persistence
immediate.
- Around line 48-50: The test assumes persistence from asynchronous operations
in service.recordUsage and service.resetUsage completes immediately; instead,
change the test to await the async persistence step β€” either by awaiting the
Futures returned by recordUsage/resetUsage (ensure recordUsage and resetUsage
return Futures if they don't) or by awaiting a provided flush/persist method on
the service or using an event-pump helper before asserting; update calls to
service.recordUsage(...) and await service.resetUsage() (or await
service.persist/flush) so the test waits for async persistence to finish.

---

Duplicate comments:
In `@workout-logger/lib/viewmodels/ai_coach_view_model.dart`:
- Line 113: Replace the unnecessary braced interpolation in the error write
call: locate the buffer.write call that currently uses '\n\n_Error: ${e}_' (in
ai_coach_view_model.dart, inside the AI coach view model error handling) and
change it to use '\n\n_Error: $e_' so the string interpolation uses $e without
braces to satisfy the unnecessary_brace_in_string_interps flutter_lints rule.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 99809e62-4e0a-4b16-a2d2-9208ce2ff608

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 36a0f10 and bde8be4.

πŸ“’ Files selected for processing (12)
  • workout-logger/lib/main.dart
  • workout-logger/lib/screens/ai_coach_screen.dart
  • workout-logger/lib/screens/widgets/profile_sections.dart
  • workout-logger/lib/services/ai/coach_tool_service.dart
  • workout-logger/lib/services/ai/gemini_ai_service.dart
  • workout-logger/lib/services/gemini_context_builder.dart
  • workout-logger/lib/services/managers/conversation_manager.dart
  • workout-logger/lib/services/storage_service.dart
  • workout-logger/lib/viewmodels/ai_coach_view_model.dart
  • workout-logger/pubspec.yaml
  • workout-logger/test/ai_coach_view_model_test.dart
  • workout-logger/test/gemini_ai_service_usage_test.dart

Comment thread workout-logger/lib/main.dart Outdated
Comment thread workout-logger/test/gemini_ai_service_usage_test.dart Outdated
Comment thread workout-logger/test/gemini_ai_service_usage_test.dart
@Devasy

Devasy commented May 30, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor
βœ… Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
workout-logger/lib/services/ai/gemini_ai_service.dart (1)

101-118: ⚠️ Potential issue | 🟠 Major | ⚑ Quick win

Await recordUsage() from _recordUsage() to keep persistence/exception handling consistent

recordUsage() is async and awaits _persistUsage() before notifyListeners(), but _recordUsage() is still void and calls it without await. This makes persistence fire-and-forget, so errors from saveSetting() won’t be caught by the surrounding try/catch in streamCoachReply() / generate*().

Proposed fix
-  void _recordUsage(UsageMetadata? m) {
+  Future<void> _recordUsage(UsageMetadata? m) async {
     if (m == null) return;
     final p = m.promptTokenCount ?? 0;
     final r = m.candidatesTokenCount ?? 0;
-    recordUsage(prompt: p, response: r, total: m.totalTokenCount ?? (p + r));
+    await recordUsage(
+      prompt: p,
+      response: r,
+      total: m.totalTokenCount ?? (p + r),
+    );
   }
-        _recordUsage(roundUsage);
+        await _recordUsage(roundUsage);
-      _recordUsage(response.usageMetadata);
+      await _recordUsage(response.usageMetadata);
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workout-logger/lib/services/ai/gemini_ai_service.dart` around lines 101 -
118, The helper _recordUsage currently returns void and calls the async
recordUsage without awaiting, causing persistence errors to escape the callers'
try/catch; change _recordUsage to async Future<void> _recordUsage(UsageMetadata?
m) async { ... await recordUsage(...); } and update all call sites (e.g., where
streamCoachReply()/generate* invoke _recordUsage) to await the returned Future
so exceptions from _persistUsage()/saveSetting propagate and are caught by the
existing try/catch.
πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@workout-logger/lib/services/ai/gemini_ai_service.dart`:
- Around line 101-118: The helper _recordUsage currently returns void and calls
the async recordUsage without awaiting, causing persistence errors to escape the
callers' try/catch; change _recordUsage to async Future<void>
_recordUsage(UsageMetadata? m) async { ... await recordUsage(...); } and update
all call sites (e.g., where streamCoachReply()/generate* invoke _recordUsage) to
await the returned Future so exceptions from _persistUsage()/saveSetting
propagate and are caught by the existing try/catch.

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bc13c13f-f04d-47b1-bf7f-3ab534f21ef9

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between bde8be4 and 27cdd28.

πŸ“’ Files selected for processing (3)
  • workout-logger/lib/main.dart
  • workout-logger/lib/services/ai/gemini_ai_service.dart
  • workout-logger/test/gemini_ai_service_usage_test.dart

@Devasy
Devasy merged commit 380ae18 into Revamp-of-UI May 30, 2026
2 checks passed
@Devasy
Devasy deleted the feat/ai-coach branch May 30, 2026 07:31
Devasy added a commit that referenced this pull request Jun 16, 2026
* feat: add workout summary screen and enhance app theme colors

- Implemented WorkoutSummaryScreen to display post-workout details including duration, volume, sets, and exercises.
- Added visual elements such as trophy header and muscle groups trained section.
- Refactored AppTheme to centralize color management through AppColors, improving maintainability and consistency across the app.
- Updated theme properties for better visual coherence and modern styling.

* feat: add predictive back page transitions for Android in AppTheme

* Refactor Programs Screen and add Week Structure Editor

- Updated ProgramsScreen to improve UI elements and replace AppTheme with AppColors.
- Enhanced FloatingActionButton styles and added new RFWidgets for better consistency.
- Implemented a new ProgramWeekEditorStep widget for editing week structures in training programs.
- Introduced ProgramWeekTile for displaying collapsible week cards in ProgramDetailScreen.
- Added functionality for managing deload weeks and intensity factors within the week editor.
- Improved overall code organization and readability across the modified files.

* feat: Enhance UI with new glassmorphic components and charts

- Introduced `GlassCard` with gradient background and accent border option.
- Added `AmbientGlow` for decorative ambient effects.
- Implemented `RFNavBar` for a custom bottom navigation bar with glassmorphism.
- Created `Sparkline` and `VolumeChart` widgets for data visualization.
- Developed `WheelPickerField` for weight and reps input with haptic feedback.
- Updated `WorkoutHeader` with gradient background and improved text styles.
- Refined `AppTheme` with new color definitions and text styles using Google Fonts.
- Added Google Fonts dependency for enhanced typography.

* feat: add personal records feature

- Introduced PersonalRecord model to track best weight, reps, and volume for exercises.
- Implemented PRManager to manage personal records, including checking and updating records after workouts.
- Added methods in storage service for saving and retrieving personal records.
- Updated UI to display personal records in the Analytics screen and Workout Summary screen.
- Enhanced onboarding process to prompt for user name and handle version updates.
- Refactored various screens to improve layout and user experience.

* feat: update exercise library screen tests with new icon and text changes

* feat: update .gitignore and CLAUDE.md for new graphify output and project guidelines; remove MainActivity.kt

* analytics_screen.dart

_FrequencyGrid: added w >= 0 && guard so future-dated sessions can't insert negative map keys
_MuscleVolumeChart: early-return isEmpty when maxVol == 0 (prevents NaN); converted volume display via settings.toDisplay + settings.unitLabel
dashboard_widgets.dart

startOfWeek now truncated to midnight (DateTime(y,m,d)) before subtracting weekdays β€” fixes the bug where sessions earlier in the day than "now" were excluded
Volume StatGridCard now uses settings.toDisplay(weeklyVolume) and 'Volume (${settings.unitLabel})'
edit_workout_session_screen.dart

drops: s.drops?.toList() β€” defensive copy prevents shared-mutation with the original WorkoutSession
exercise_details_sheet.dart

Added else branch on delete failure β€” shows error SnackBar instead of silently doing nothing
Set chips now use settings.toDisplay(s.weight) + settings.unitLabel
Growth trend label uses settings.toDisplay(growthModel.slope) + settings.unitLabel
exercise_input_section.dart

Icon(Icons.auto_awesome_rounded, …) β†’ const Icon(…)
exercise_progress_view.dart

DropdownButton.value guarded with ids.contains(selected) ? selected : null β€” prevents assert/crash when the selected exercise id is no longer in the performed set
profile_sections.dart

const _SectionDivider() and const _ComingSoonBadge() constructors + all call sites updated
rf_inputs.dart

Added onLongPressEnd to _StepButton (wired to GestureDetector.onLongPressEnd)
_NumberPickerSheetState now holds Timer? _holdTimer; onLongPress assigns _holdTimer = Timer.periodic(…), onLongPressEnd cancels it, dispose() also cancels it β€” no more leaked timers
session_details_sheet.dart

Volume banner: settings.toDisplay(session.totalVolume) + 'Volume ${settings.unitLabel}'
Per-exercise total: settings.toDisplay(log.totalVolume) + settings.unitLabel
Per-set row: settings.toDisplay(set.weight) formatted + settings.unitLabel
targets_tab.dart

Added _isSubmitting bool; _submit guards against re-entry and wraps provider call in try/finally; GlowButton.onPressed is null while submitting
workout_flow_screen.dart

_toggleDropset: weight controller text now uses settings.toDisplay(_currentWeight) with proper decimal formatting (matching _loadLastSessionData)
_addDrop: new drop controller text also uses settings.toDisplay(newWeight)
WorkoutHeader now receives restSeconds: _restSeconds
workout_header.dart

Added optional restSeconds field (defaults to 90 for backwards compatibility); _OptionsMenu receives widget.restSeconds instead of the hardcoded literal
workout_summary_screen.dart

volStr built from settings.toDisplay(session.totalVolume); label updated to 'Volume (${settings.unitLabel})'

* feat: enhance UI components and improve data handling across screens

* feat: update ExerciseLibraryScreen tests to include SettingsProvider in widget setup

* feat: enhance workout session data collection by including weight in HealthConnectService

* feat: update Android build configuration, enhance analytics screen, and improve UI components

* feat: add muscle recovery and growth tracking features in WorkoutProvider and MLService

* feat: refactor NumberInputCard to StatefulWidget and enhance RFNavBar design

* feat: enhance ProfileScreen and ProfileSections with Google Fonts for improved typography

* feat: update Create button in ProgramsScreen to allow for non-full width display

* feat: Integrate Gemini AI features for personalized coaching and insights

- Added GeminiService for AI integration, including chat and program generation capabilities.
- Implemented GeminiContextBuilder to create context strings for AI prompts.
- Introduced _WeeklyInsightsCard to display weekly insights based on user workouts.
- Added AiSettingsSection in profile settings for managing Gemini API key and model selection.
- Updated home screen to include weekly insights and AI coach button.
- Enhanced ProgramsScreen with AI program generator button.
- Updated SettingsProvider to manage Gemini API key and insights storage.
- Added necessary dependencies for Google Generative AI.

* feat: enhance various screens and models with improved error handling, UI adjustments, and new features

* feat: improve error handling in program saving and adjust opacity calculation in body heatmap

* Add comprehensive tests for MLService, model serialization, PRManager, and WorkoutProvider

- Implement tests for MLService covering growth model training, set recommendations, default recommendations, target completion prediction, and muscle recovery score computation.
- Create model serialization tests for WorkoutSet, ExerciseLog, WorkoutSession, Exercise, MuscleGroup, Target, GrowthModel, Routine, PersonalRecord, TrainingProgram, and ProgramExerciseSlot to ensure data integrity during JSON serialization/deserialization.
- Introduce tests for PRManager to validate loading records, backfilling from sessions, checking and updating personal records, and retrieving records.
- Enhance WorkoutProvider tests to verify initialization, session and routine loading, active workout management, and exercise name retrieval.

* fix: ensure opacity calculation in heatmap drawing is explicitly a double

* chore: update version to 2.0.0+21 in pubspec.yaml

* feat: enhance UI responsiveness with layout adjustments and breakpoints across multiple screens

* feat: enhance UI layout and responsiveness with padding adjustments for floating action buttons and improved data representation

* feat: add advanced metrics toggle and display estimated 1RM badge in workout input section

* Add widget tests for AnalyticsScreen and ExerciseProgressView

- Implement comprehensive widget tests for the AnalyticsScreen, covering the Overview, Targets, and Records tabs.
- Validate functionality such as tab switching, data display, and interaction with UI elements.
- Add widget tests for ExerciseProgressView, including exercise picker, chart mode toggle, and set progression chart.
- Ensure tests cover empty states, search functionality, and interaction with various UI components.

* feat: update app label handling and improve navigation in home screen

* feat: Implement Gemini AI service for AI coach functionality (#49)

* feat: Implement Gemini AI service for AI coach functionality

- Added GeminiAiService to handle AI coach chat, program generation, and insights using Google Generative AI.
- Created IAiService interface to define the contract for AI services.
- Developed GeminiContextBuilder to construct context for AI interactions.
- Introduced ConversationManager to manage AI conversations, including persistence and active conversation logic.
- Implemented storage service methods for saving and retrieving AI conversations.
- Built AiCoachViewModel to orchestrate AI interactions and manage conversation state.
- Added unit tests for AiCoachViewModel, CoachToolService, and ConversationManager to ensure functionality and persistence.
- Updated analytics and exercise progress views to use the new GeminiAiService.

* feat: Enhance Gemini AI service with token usage tracking and UI updates for AI coach

* feat: Refactor AiCoachViewModel and related services for improved readability and error handling

* feat: Improve error handling and async behavior in Gemini AI service usage tracking

---------

Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>

* feat: Add muscle volume trend chart and recent sessions section in MuscleDetailSheet

* docs: add conversational routine optimizer design spec

Replaces the one-shot optimizer sheet with a dedicated conversational
optimizer screen that reuses the coach streaming tool-loop, adds an
interactive ask_user_questions tool, gates on insufficient data, and
persists sessions to a separate optimizer inbox.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor: remove one-shot routine optimizer (replaced by conversational flow)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add Conversation.kind and AI question models (QuestionSpec, AnswerSpec, PendingQuestions)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: scope ConversationManager by kind ('coach' | 'optimizer')

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add ask_user_questions tool declaration and optimizer system prompt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add RFQuestionCard reusable widget (option chips + custom input)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: rewrite RoutineOptimizerViewModel as conversational streaming VM with ask_user_questions pausing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: handle aborted completer and await appendMessage in submitAnswers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add RoutineOptimizerScreen (conversational UI with question card + history inbox)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style: fix magic padding values and header button consistency in RoutineOptimizerScreen

* feat: add data gate (<3 sessions) and navigate to RoutineOptimizerScreen from routine card

* feat: add testBody method for RoutineOptimizerScreen to facilitate widget testing

* feat: add workout summary screen and enhance app theme colors (#50)

feat: add workout summary screen, health analytics, and production APK signing

- Workout summary screen with session details
- Sleep HR chart and debug log buffer  
- HealthHistoryManager for sleep/heart rate data
- Workout heart rate analysis and recovery metrics
- EC P-256 production keystore signing via GitHub Secrets
- Split-per-ABI APK builds for arm64, armeabi-v7a, x86_64
- FUTURE_IMPROVEMENTS.md documenting Options B & C (F-Droid, fastlane)

* test: update lastNightSleep test to sum all periods in the night window

* feat: bundle Geist fonts locally and add F-Droid metadata (Option B) (#51)

- Remove google_fonts dependency; replace with bundled variable font files
  (Geist-Variable.ttf + GeistMono-Variable.ttf from Vercel v1.7.2, MIT licensed)
- Replace all 377 GoogleFonts.geist/geistMono() calls with TextStyle(fontFamily:)
  across 31 dart files β€” no runtime Google CDN fetch, F-Droid build-compatible
- Declare fonts in pubspec.yaml flutter.fonts section
- Add fdroid/metadata/com.devasy.repforge.yml with anti-features (NonFreeNet)
  and auto-update config for F-Droid submission

Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant