-
Notifications
You must be signed in to change notification settings - Fork 5
add a custom prompt feature #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Summary of ChangesHello @QianYu-u, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request integrates a new custom prompt feature into the application, enabling users to personalize the summarization process. It provides a robust mechanism for storing and managing these prompts, along with a dedicated user interface for easy interaction. The core logic for generating summaries has been updated to incorporate the user's chosen custom prompt, allowing for more tailored AI responses. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a new feature allowing users to create, manage, and use custom prompts. The implementation spans across the data layer, view models, and UI components, and is well-executed. I've identified a few minor opportunities for code refinement to improve readability and maintainability, primarily related to code style and leveraging language features more effectively. Overall, the changes are solid and the feature is a great addition.
app/src/main/kotlin/me/nanova/summaryexpressive/ui/page/ManagePromptsDialog.kt
Outdated
Show resolved
Hide resolved
| androidx.compose.foundation.layout.Box { | ||
| TextButton(onClick = { expanded = true }) { | ||
| Text(selectedPrompt?.title ?: "Default") | ||
| } | ||
| androidx.compose.material3.DropdownMenu( | ||
| expanded = expanded, | ||
| onDismissRequest = { expanded = false } | ||
| ) { | ||
| androidx.compose.material3.DropdownMenuItem( | ||
| text = { Text("Default") }, | ||
| onClick = { | ||
| actions.onSelectPrompt(null) | ||
| expanded = false | ||
| } | ||
| ) | ||
| state.savedPrompts.forEach { prompt -> | ||
| androidx.compose.material3.DropdownMenuItem( | ||
| text = { Text(prompt.title) }, | ||
| onClick = { | ||
| actions.onSelectPrompt(prompt.id) | ||
| expanded = false | ||
| } | ||
| ) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code uses fully qualified names for several Composables (Box, DropdownMenu, DropdownMenuItem), which can make it harder to read. It's a best practice to add the necessary imports at the top of the file and use the simple names for better code clarity and maintainability.
Missing imports:
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem Box {
TextButton(onClick = { expanded = true }) {
Text(selectedPrompt?.title ?: "Default")
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(
text = { Text("Default") },
onClick = {
actions.onSelectPrompt(null)
expanded = false
}
)
state.savedPrompts.forEach { prompt ->
DropdownMenuItem(
text = { Text(prompt.title) },
onClick = {
actions.onSelectPrompt(prompt.id)
expanded = false
}
)
}
}
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should fix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a custom prompt feature that allows users to create, manage, and select custom prompts in the Settings screen to override the default system prompt for AI summarization.
Changes:
- Added
CustomPromptdata model with fields for id, title, and content - Extended user preferences to store and manage a list of custom prompts and track the selected prompt
- Added UI components for managing prompts (create, edit, delete) via a bottom sheet dialog
- Integrated custom prompt selection into the summarization flow
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
model/CustomPrompt.kt |
New data model for custom prompts |
UserPreferencesRepository.kt |
Added methods to save, delete, and select prompts; extended preferences data class |
vm/AppViewModel.kt |
Added ViewModel methods to expose prompt management to UI |
vm/SummaryViewModel.kt |
Integrated custom prompt into summarization agent creation |
vm/UiState.kt |
Extended settings state with saved prompts and selected prompt ID |
ui/page/SettingsScreen.kt |
Added UI for selecting and managing custom prompts |
ui/page/ManagePromptsDialog.kt |
New bottom sheet component for prompt management |
llm/Prompts.kt |
Modified to accept and use custom prompts when provided |
llm/LLMHandler.kt |
Added custom prompt parameter to agent creation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| val appLanguage = application.resources.configuration.locales[0] | ||
| val customPrompt = settings.savedPrompts.find { it.id == settings.selectedPromptId }?.content |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a potential edge case where a user could select a prompt but then delete it from another screen/session. When this happens, customPrompt would be null (line 102), but the selectedPromptId would still be set in the state. This could lead to confusion in the UI where a prompt appears selected but doesn't actually override the default behavior.
Consider clearing the selectedPromptId when the app starts if the corresponding prompt no longer exists in the saved prompts list, or handle this scenario in the ViewModel initialization.
| - If the text is from a document, focus on the core information and purpose. | ||
| - If the text is a video transcript, focus on the key topics and speakers' points. |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order of these bullet points has been swapped compared to the original. This appears to be an unintentional change that doesn't relate to the custom prompt feature. Unless there's a specific reason for this reordering, it should be reverted to maintain consistency with the original prompt structure.
| - If the text is from a document, focus on the core information and purpose. | |
| - If the text is a video transcript, focus on the key topics and speakers' points. | |
| - If the text is a video transcript, focus on the key topics and speakers' points. | |
| - If the text is from a document, focus on the core information and purpose. |
|
|
||
| androidx.compose.foundation.layout.Box { | ||
| TextButton(onClick = { expanded = true }) { | ||
| Text(selectedPrompt?.title ?: "Default") |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The selected prompt title is displayed in a TextButton without text overflow handling. If a user creates a prompt with a long title, it could overflow the button and cause layout issues. Consider adding maxLines = 1 and overflow = TextOverflow.Ellipsis to the Text component, or set a maximum width for the button.
| Text(selectedPrompt?.title ?: "Default") | |
| Text( | |
| text = selectedPrompt?.title ?: "Default", | |
| maxLines = 1, | |
| overflow = TextOverflow.Ellipsis | |
| ) |
| val onAutoExtractUrlChange: (Boolean) -> Unit, | ||
| val onSessDataChange: (String, Long) -> Unit, | ||
| val onSessDataClear: () -> Unit, | ||
| val onSavePrompt: (me.nanova.summaryexpressive.model.CustomPrompt) -> Unit, |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a fully qualified class name here is inconsistent with the rest of the codebase. Add an import statement for CustomPrompt at the top of the file and use the short name here, following the pattern used for other model classes in this file.
| package me.nanova.summaryexpressive.ui.page | ||
|
|
||
| import androidx.compose.foundation.clickable | ||
| import androidx.compose.foundation.layout.Arrangement | ||
| import androidx.compose.foundation.layout.Column | ||
| import androidx.compose.foundation.layout.Row | ||
| import androidx.compose.foundation.layout.Spacer | ||
| import androidx.compose.foundation.layout.fillMaxSize | ||
| import androidx.compose.foundation.layout.fillMaxWidth | ||
| import androidx.compose.foundation.layout.padding | ||
| import androidx.compose.foundation.layout.size | ||
| import androidx.compose.foundation.lazy.LazyColumn | ||
| import androidx.compose.foundation.lazy.items | ||
| import androidx.compose.material.icons.Icons | ||
| import androidx.compose.material.icons.filled.Add | ||
| import androidx.compose.material.icons.filled.Delete | ||
| import androidx.compose.material.icons.filled.Edit | ||
| import androidx.compose.material3.AlertDialog | ||
| import androidx.compose.material3.Button | ||
| import androidx.compose.material3.Card | ||
| import androidx.compose.material3.CardDefaults | ||
| import androidx.compose.material3.ExperimentalMaterial3Api | ||
| import androidx.compose.material3.FloatingActionButton | ||
| import androidx.compose.material3.Icon | ||
| import androidx.compose.material3.IconButton | ||
| import androidx.compose.material3.MaterialTheme | ||
| import androidx.compose.material3.ModalBottomSheet | ||
| import androidx.compose.material3.OutlinedTextField | ||
| import androidx.compose.material3.Scaffold | ||
| import androidx.compose.material3.SheetState | ||
| import androidx.compose.material3.Text | ||
| import androidx.compose.material3.TextButton | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.runtime.getValue | ||
| import androidx.compose.runtime.mutableStateOf | ||
| import androidx.compose.runtime.remember | ||
| import androidx.compose.runtime.setValue | ||
| import androidx.compose.ui.Alignment | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.ui.unit.dp | ||
| import me.nanova.summaryexpressive.model.CustomPrompt | ||
| import java.util.UUID | ||
|
|
||
| @OptIn(ExperimentalMaterial3Api::class) | ||
| @Composable | ||
| fun ManagePromptsSheet( | ||
| onDismissRequest: () -> Unit, | ||
| sheetState: SheetState, | ||
| prompts: List<CustomPrompt>, | ||
| onAddPrompt: (CustomPrompt) -> Unit, | ||
| onEditPrompt: (CustomPrompt) -> Unit, | ||
| onDeletePrompt: (String) -> Unit | ||
| ) { | ||
| var showEditDialog by remember { mutableStateOf<CustomPrompt?>(null) } | ||
| var showAddDialog by remember { mutableStateOf(false) } | ||
|
|
||
| ModalBottomSheet( | ||
| onDismissRequest = onDismissRequest, | ||
| sheetState = sheetState | ||
| ) { | ||
| Scaffold( | ||
| floatingActionButton = { | ||
| FloatingActionButton(onClick = { showAddDialog = true }) { | ||
| Icon(Icons.Default.Add, contentDescription = "Add Prompt") | ||
| } | ||
| } | ||
| ) { innerPadding -> | ||
| Column( | ||
| modifier = Modifier | ||
| .fillMaxSize() | ||
| .padding(innerPadding) | ||
| .padding(horizontal = 16.dp) | ||
| ) { | ||
| Text( | ||
| text = "Manage Custom Prompts", | ||
| style = MaterialTheme.typography.headlineSmall, | ||
| modifier = Modifier.padding(vertical = 16.dp) | ||
| ) | ||
|
|
||
| if (prompts.isEmpty()) { | ||
| Column( | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .weight(1f), | ||
| horizontalAlignment = Alignment.CenterHorizontally, | ||
| verticalArrangement = Arrangement.Center | ||
| ) { | ||
| Text("No custom prompts yet.", style = MaterialTheme.typography.bodyLarge) | ||
| } | ||
| } else { | ||
| LazyColumn( | ||
| verticalArrangement = Arrangement.spacedBy(8.dp), | ||
| modifier = Modifier.weight(1f) | ||
| ) { | ||
| items(prompts) { prompt -> | ||
| PromptItem( | ||
| prompt = prompt, | ||
| onEdit = { showEditDialog = prompt }, | ||
| onDelete = { onDeletePrompt(prompt.id) } | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| Spacer(modifier = Modifier.size(16.dp)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (showAddDialog) { | ||
| PromptEditorDialog( | ||
| title = "Add New Prompt", | ||
| onDismiss = { showAddDialog = false }, | ||
| onConfirm = { title, content -> | ||
| onAddPrompt(CustomPrompt(id = UUID.randomUUID().toString(), title = title, content = content)) | ||
| showAddDialog = false | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| showEditDialog?.let { prompt -> | ||
| PromptEditorDialog( | ||
| title = "Edit Prompt", | ||
| initialTitle = prompt.title, | ||
| initialContent = prompt.content, | ||
| onDismiss = { showEditDialog = null }, | ||
| onConfirm = { title, content -> | ||
| onEditPrompt(prompt.copy(title = title, content = content)) | ||
| showEditDialog = null | ||
| } | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| @Composable | ||
| fun PromptItem( | ||
| prompt: CustomPrompt, | ||
| onEdit: () -> Unit, | ||
| onDelete: () -> Unit | ||
| ) { | ||
| Card( | ||
| modifier = Modifier.fillMaxWidth(), | ||
| colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) | ||
| ) { | ||
| Row( | ||
| modifier = Modifier | ||
| .padding(16.dp) | ||
| .fillMaxWidth(), | ||
| horizontalArrangement = Arrangement.SpaceBetween, | ||
| verticalAlignment = Alignment.CenterVertically | ||
| ) { | ||
| Column(modifier = Modifier.weight(1f)) { | ||
| Text(text = prompt.title, style = MaterialTheme.typography.titleMedium) | ||
| Text( | ||
| text = prompt.content, | ||
| style = MaterialTheme.typography.bodySmall, | ||
| maxLines = 1, | ||
| color = MaterialTheme.colorScheme.onSurfaceVariant | ||
| ) | ||
| } | ||
| Row { | ||
| IconButton(onClick = onEdit) { | ||
| Icon(Icons.Default.Edit, contentDescription = "Edit") | ||
| } | ||
| IconButton(onClick = onDelete) { | ||
| Icon(Icons.Default.Delete, contentDescription = "Delete") | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Composable | ||
| fun PromptEditorDialog( | ||
| title: String, | ||
| initialTitle: String = "", | ||
| initialContent: String = "", | ||
| onDismiss: () -> Unit, | ||
| onConfirm: (String, String) -> Unit | ||
| ) { | ||
| var promptTitle by remember { mutableStateOf(initialTitle) } | ||
| var promptContent by remember { mutableStateOf(initialContent) } | ||
|
|
||
| AlertDialog( | ||
| onDismissRequest = onDismiss, | ||
| title = { Text(title) }, | ||
| text = { | ||
| Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { | ||
| OutlinedTextField( | ||
| value = promptTitle, | ||
| onValueChange = { promptTitle = it }, | ||
| label = { Text("Title") }, | ||
| singleLine = true, | ||
| modifier = Modifier.fillMaxWidth() | ||
| ) | ||
| OutlinedTextField( | ||
| value = promptContent, | ||
| onValueChange = { promptContent = it }, | ||
| label = { Text("Prompt Content") }, | ||
| minLines = 3, | ||
| maxLines = 10, | ||
| modifier = Modifier.fillMaxWidth() | ||
| ) | ||
| } | ||
| }, | ||
| confirmButton = { | ||
| Button( | ||
| onClick = { onConfirm(promptTitle, promptContent) }, | ||
| enabled = promptTitle.isNotBlank() && promptContent.isNotBlank() | ||
| ) { | ||
| Text("Save") | ||
| } | ||
| }, | ||
| dismissButton = { | ||
| TextButton(onClick = onDismiss) { | ||
| Text("Cancel") | ||
| } | ||
| } | ||
| ) | ||
| } |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded strings are used throughout this file instead of string resources. The codebase consistently uses stringResource for all user-facing text (see SettingsScreen.kt and other UI files). For internationalization and maintainability, these strings should be moved to string resources in res/values/strings.xml and referenced using stringResource(id = R.string....).
Affected strings include: "Manage Custom Prompts", "No custom prompts yet.", "Add New Prompt", "Edit Prompt", "Title", "Prompt Content", "Save", "Cancel", "Edit", "Delete", "Add Prompt".
|
|
||
| @Serializable | ||
| data class CustomPrompt( | ||
| val id: String = UUID.randomUUID().toString(), |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The UUID type is not natively supported by kotlinx.serialization without a custom serializer. When this data class is serialized/deserialized as part of UserPreferences, it will fail at runtime with a serialization exception because there's no built-in serializer for java.util.UUID.
You should either:
- Use
Stringtype for theidfield and callUUID.randomUUID().toString()at creation time, or - Implement a custom serializer for UUID using
@Serializable(with = UUIDSerializer::class)
Given that other parts of the codebase (e.g., HistorySummary.kt:22) use String for UUID-based IDs, option 1 is recommended for consistency.
| var promptTitle by remember { mutableStateOf(initialTitle) } | ||
| var promptContent by remember { mutableStateOf(initialContent) } |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The remember blocks will retain the state of promptTitle and promptContent even after the dialog is dismissed and reopened. When editing a prompt, if you dismiss the dialog and then open it again to edit the same or a different prompt, the remembered state from the previous session might be displayed until the composition resets.
Use remember(initialTitle, initialContent) with keys to ensure the state is reset when the initial values change, or use rememberSaveable if you want to preserve state across configuration changes.
| var promptTitle by remember { mutableStateOf(initialTitle) } | |
| var promptContent by remember { mutableStateOf(initialContent) } | |
| var promptTitle by remember(initialTitle) { mutableStateOf(initialTitle) } | |
| var promptContent by remember(initialContent) { mutableStateOf(initialContent) } |
| headlineContent = { Text("Custom Prompt") }, | ||
| supportingContent = { Text("Override system prompt") }, |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded strings "Custom Prompt" and "Override system prompt" should be moved to string resources for consistency with the rest of the codebase. All other settings items in this file use stringResource for internationalization.
| title = "Add New Prompt", | ||
| onDismiss = { showAddDialog = false }, | ||
| onConfirm = { title, content -> | ||
| onAddPrompt(CustomPrompt(id = UUID.randomUUID().toString(), title = title, content = content)) |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The UUID is generated here as a String using UUID.randomUUID().toString(), which is correct. However, this approach becomes inconsistent once the CustomPrompt data model is fixed to not use UUID as a default parameter. The UUID generation should ideally be centralized - either always in the UI layer or always in the data model creation.
| text = { Text("Default") }, | ||
| onClick = { | ||
| actions.onSelectPrompt(null) | ||
| expanded = false | ||
| } | ||
| ) | ||
| state.savedPrompts.forEach { prompt -> | ||
| androidx.compose.material3.DropdownMenuItem( | ||
| text = { Text(prompt.title) }, | ||
| onClick = { | ||
| actions.onSelectPrompt(prompt.id) | ||
| expanded = false | ||
| } | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| IconButton(onClick = onShowManagePromptsSheet) { | ||
| Icon( | ||
| Icons.Default.Edit, | ||
| contentDescription = "Manage Prompts" |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded strings "Default" and "Manage Prompts" should be moved to string resources for consistency with the rest of the codebase and to support internationalization.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
hi @QianYu-u , thank u so much for bringing this feature to us. I think some of the AI reviews are valid, pls update accordingly. And also pls provide the SQL migration in AppDatabase. |
Added a feature to allow users to manage and use custom prompts in Settings.