Skip to content

Conversation

@QianYu-u
Copy link

Added a feature to allow users to manage and use custom prompts in Settings.

Copilot AI review requested due to automatic review settings January 27, 2026 11:33
@gemini-code-assist
Copy link

Summary of Changes

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

  • Custom Prompt Feature: Introduced the ability for users to define, save, edit, and select custom prompts for summarization, enhancing flexibility in AI output.
  • Data Persistence: Added savedPrompts and selectedPromptId to UserPreferences to store user-defined prompts and their current selection.
  • User Interface: Implemented a new ManagePromptsSheet UI component within the settings screen, allowing users to interactively manage their custom prompts.
  • LLM Integration: Modified the LLMHandler and Prompts.kt to accept and utilize a selected custom prompt, overriding the default summarization prompt when provided.

🧠 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 Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a 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.

Comment on lines +590 to +615
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
}
)
}
}
}

Choose a reason for hiding this comment

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

medium

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
                                            }
                                        )
                                    }
                                }
                            }

Copy link
Owner

Choose a reason for hiding this comment

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

should fix

Copy link

Copilot AI left a 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 CustomPrompt data 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
Copy link

Copilot AI Jan 27, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines 37 to +44
- 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.
Copy link

Copilot AI Jan 27, 2026

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.

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

Copilot uses AI. Check for mistakes.

androidx.compose.foundation.layout.Box {
TextButton(onClick = { expanded = true }) {
Text(selectedPrompt?.title ?: "Default")
Copy link

Copilot AI Jan 27, 2026

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.

Suggested change
Text(selectedPrompt?.title ?: "Default")
Text(
text = selectedPrompt?.title ?: "Default",
maxLines = 1,
overflow = TextOverflow.Ellipsis
)

Copilot uses AI. Check for mistakes.
val onAutoExtractUrlChange: (Boolean) -> Unit,
val onSessDataChange: (String, Long) -> Unit,
val onSessDataClear: () -> Unit,
val onSavePrompt: (me.nanova.summaryexpressive.model.CustomPrompt) -> Unit,
Copy link

Copilot AI Jan 27, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines 1 to 219
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")
}
}
)
}
Copy link

Copilot AI Jan 27, 2026

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

Copilot uses AI. Check for mistakes.

@Serializable
data class CustomPrompt(
val id: String = UUID.randomUUID().toString(),
Copy link

Copilot AI Jan 27, 2026

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:

  1. Use String type for the id field and call UUID.randomUUID().toString() at creation time, or
  2. 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.

Copilot uses AI. Check for mistakes.
Comment on lines +180 to +181
var promptTitle by remember { mutableStateOf(initialTitle) }
var promptContent by remember { mutableStateOf(initialContent) }
Copy link

Copilot AI Jan 27, 2026

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.

Suggested change
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) }

Copilot uses AI. Check for mistakes.
Comment on lines +576 to +577
headlineContent = { Text("Custom Prompt") },
supportingContent = { Text("Override system prompt") },
Copy link

Copilot AI Jan 27, 2026

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.

Copilot uses AI. Check for mistakes.
title = "Add New Prompt",
onDismiss = { showAddDialog = false },
onConfirm = { title, content ->
onAddPrompt(CustomPrompt(id = UUID.randomUUID().toString(), title = title, content = content))
Copy link

Copilot AI Jan 27, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +599 to +620
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"
Copy link

Copilot AI Jan 27, 2026

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.

Copilot uses AI. Check for mistakes.
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>
@kid1412621
Copy link
Owner

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.

@kid1412621 kid1412621 added the enhancement New feature or request label Jan 31, 2026
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.

2 participants