fix: share chat exports instead of app-private path#3
Conversation
Accumulate STT until listening ends so early finalResult no longer sends a partial message. Add open-app/alarm intent shortcuts and restore tool capability text when native FC is disabled on Android Gemma 4. Co-authored-by: Cursor <cursoragent@cursor.com>
Normalize umlauts so geöffnet is not treated as an open intent, gate device-tool capability text to Android, and align beginner voice busy checks with the main chat screen. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Reviewer's GuideRefactors chat export to return in-memory text/JSON content for sharing instead of writing app-private files, improves voice input behavior and partial transcription handling, and enhances on-device intent parsing for alarms and app-opening (including German language support) with accompanying tests and prompt wiring. Sequence diagram for chat export sharing flowsequenceDiagram
actor User
participant AssistantScreen
participant ChatHistoryService
participant ExportService
participant SystemShareSheet
User->>AssistantScreen: tap Export conversation
AssistantScreen->>ChatHistoryService: exportAsText()/exportAsJson()
ChatHistoryService-->>AssistantScreen: String content
AssistantScreen->>ExportService: shareText(content, fileName)
ExportService-->>SystemShareSheet: invoke share sheet
SystemShareSheet-->>User: choose target app
Sequence diagram for updated voice input and partial transcriptionsequenceDiagram
actor User
participant VoiceInputButton
participant SpeechToText
participant AssistantScreen
User->>VoiceInputButton: press and hold
VoiceInputButton->>SpeechToText: listen(SpeechListenOptions)
SpeechToText-->>VoiceInputButton: onResult(partial/final)
VoiceInputButton->>AssistantScreen: onPartial(lastWords)
AssistantScreen->>AssistantScreen: update _inputController
User-->>VoiceInputButton: release or pause
SpeechToText-->>VoiceInputButton: onStatus(done / notListening)
VoiceInputButton->>VoiceInputButton: _finishListening()
VoiceInputButton->>SpeechToText: stop()
VoiceInputButton->>AssistantScreen: onTranscription(transcript)
AssistantScreen->>AssistantScreen: set _inputController.text
AssistantScreen->>AssistantScreen: _sendMessage() [if not generating]
Sequence diagram for open app intent handling and tool executionsequenceDiagram
actor User
participant ModelOrchestrator
participant OpenAppIntentParser
participant ToolExecutorService
User->>ModelOrchestrator: send query
ModelOrchestrator->>OpenAppIntentParser: tryParsePackage(query)
OpenAppIntentParser-->>ModelOrchestrator: packageName/null
alt packageName found
ModelOrchestrator->>ToolExecutorService: openApp(packageName)
ToolExecutorService-->>ModelOrchestrator: result{success,error}
ModelOrchestrator-->>User: InferenceResult(text, toolCalls)
else no package
ModelOrchestrator->>ModelOrchestrator: buildSystemInstruction(..., _deviceToolsCapabilitySuffix)
ModelOrchestrator-->>User: normal LLM handling
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Stop writing nova_export files into the app-private documents path. Build export content in memory and open the existing Android share chooser so users can save to Files, Drive, or other apps. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- In
OpenAppIntentParser.tryParsePackage, usinglower.contains(alias)without word boundaries can match substrings inside unrelated words (e.g.chromein a longer token); consider tightening this with tokenization or regex word boundaries to reduce false positives. - The export flows now always show a "Share sheet opened" SnackBar immediately after calling
ExportService.instance.shareText(...)without checking the outcome; it may be more accurate to react to a success/failure signal fromExportService(or catch exceptions) so the UI reflects actual share status.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `OpenAppIntentParser.tryParsePackage`, using `lower.contains(alias)` without word boundaries can match substrings inside unrelated words (e.g. `chrome` in a longer token); consider tightening this with tokenization or regex word boundaries to reduce false positives.
- The export flows now always show a "Share sheet opened" SnackBar immediately after calling `ExportService.instance.shareText(...)` without checking the outcome; it may be more accurate to react to a success/failure signal from `ExportService` (or catch exceptions) so the UI reflects actual share status.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Code Review
This pull request refactors the conversation export feature to return content directly instead of writing to a file, utilizing ExportService to trigger a share sheet. It also introduces a deterministic 'open app' shortcut parser supporting English and German, adds German support to the alarm parser, and improves the voice input button with partial transcription support. The review feedback suggests enhancing the German open-app regex to support multiple words and using word boundaries for app alias matching to prevent false positives.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| static final RegExp _machAufIntent = RegExp( | ||
| r'\bmach(?:en)?\s+\S+\s+auf\b', | ||
| caseSensitive: false, | ||
| ); |
There was a problem hiding this comment.
The current regular expression \bmach(?:en)?\s+\S+\s+auf\b only matches exactly one non-whitespace word between 'mach' and 'auf'. In natural German speech, users often insert filler words (e.g., 'mach bitte youtube auf' or 'mach mal die kamera auf').
Using [^.!?]+? instead of \S+ allows matching multiple words within the same sentence, making the intent parser much more robust.
| static final RegExp _machAufIntent = RegExp( | |
| r'\bmach(?:en)?\s+\S+\s+auf\b', | |
| caseSensitive: false, | |
| ); | |
| static final RegExp _machAufIntent = RegExp( | |
| r'\bmach(?:en)?\s+[^.!?]+?\s+auf\b', | |
| caseSensitive: false, | |
| ); |
| static String? tryParsePackage(String query) { | ||
| if (!_hasOpenIntent(query)) return null; | ||
|
|
||
| final lower = query.toLowerCase(); | ||
| // Prefer longer aliases first (e.g. "google maps" before "maps"). | ||
| final aliases = knownApps.keys.toList() | ||
| ..sort((a, b) => b.length.compareTo(a.length)); | ||
| for (final alias in aliases) { | ||
| if (lower.contains(alias)) { | ||
| return knownApps[alias]; | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| } |
There was a problem hiding this comment.
Using lower.contains(alias) for matching app aliases can lead to false positives when an alias is a substring of another word (for example, 'monochrome' matching 'chrome', or 'bitmaps' matching 'maps').
Using a regular expression with word boundaries (\b) ensures that aliases are only matched as complete words, preventing accidental false positive matches.
static String? tryParsePackage(String query) {
if (!_hasOpenIntent(query)) return null;
final lower = query.toLowerCase();
// Prefer longer aliases first (e.g. "google maps" before "maps").
final aliases = knownApps.keys.toList()
..sort((a, b) => b.length.compareTo(a.length));
for (final alias in aliases) {
final regex = RegExp(r'\b' + RegExp.escape(alias) + r'\b', caseSensitive: false);
if (regex.hasMatch(lower)) {
return knownApps[alias];
}
}
return null;
}
Summary
/data/user/0/.../app_flutterExportService(same path as tasks/notes)Test plan
flutter test test/services/chat_history_export_test.dartSummary by Sourcery
Export chat history as in-memory text/JSON for sharing instead of writing app-private files, improve voice input behavior, and enhance natural-language handling for alarms and app-opening tools.
New Features:
Bug Fixes:
Enhancements:
Tests: