Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.0"
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.20"
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
id("kotlin-parcelize")
}

android {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.google.ai.sample.feature.multimodal

import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
Expand All @@ -23,6 +24,8 @@ import com.google.ai.sample.util.ChatHistoryPreferences
import com.google.ai.sample.util.Command
import com.google.ai.sample.util.CommandParser
import com.google.ai.sample.util.SystemMessagePreferences
import com.google.ai.sample.util.SystemMessageEntryPreferences // Added import
import com.google.ai.sample.util.SystemMessageEntry // Added import
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
Expand Down Expand Up @@ -90,15 +93,7 @@ class PhotoReasoningViewModel(
) {
_uiState.value = PhotoReasoningUiState.Loading

// Get the system message
val systemMessageText = _systemMessage.value

// Create the prompt with system message if available
val prompt = if (systemMessageText.isNotBlank()) {
"System Message: $systemMessageText\n\nFOLLOW THE INSTRUCTIONS STRICTLY: $userInput"
} else {
"FOLLOW THE INSTRUCTIONS STRICTLY: $userInput"
}
val prompt = "FOLLOW THE INSTRUCTIONS STRICTLY: $userInput"

// Store the current user input and selected images
currentUserInput = userInput
Expand Down Expand Up @@ -452,7 +447,7 @@ class PhotoReasoningViewModel(
/**
* Update the system message
*/
fun updateSystemMessage(message: String, context: android.content.Context) {
fun updateSystemMessage(message: String, context: Context) {
_systemMessage.value = message

// Save to SharedPreferences for persistence
Expand All @@ -462,13 +457,31 @@ class PhotoReasoningViewModel(
/**
* Load the system message from SharedPreferences
*/
fun loadSystemMessage(context: android.content.Context) {
fun loadSystemMessage(context: Context) {
val message = SystemMessagePreferences.loadSystemMessage(context)
_systemMessage.value = message

// Also load chat history
loadChatHistory(context)
}

/**
* Helper function to format database entries as text.
*/
private fun formatDatabaseEntriesAsText(context: Context): String {
val entries = SystemMessageEntryPreferences.loadEntries(context)
if (entries.isEmpty()) {
return ""
}
val builder = StringBuilder()
builder.append("Available System Guides:\n---\n")
for (entry in entries) {
builder.append("Title: ${entry.title}\n")
builder.append("Guide: ${entry.guide}\n")
builder.append("---\n")
}
return builder.toString()
}

/**
* Process commands found in the AI response
Expand Down Expand Up @@ -513,7 +526,7 @@ class PhotoReasoningViewModel(
/**
* Save chat history to SharedPreferences
*/
private fun saveChatHistory(context: android.content.Context?) {
private fun saveChatHistory(context: Context?) {
context?.let {
ChatHistoryPreferences.saveChatMessages(it, chatMessages)
}
Expand All @@ -522,7 +535,7 @@ class PhotoReasoningViewModel(
/**
* Load chat history from SharedPreferences
*/
fun loadChatHistory(context: android.content.Context) {
fun loadChatHistory(context: Context) {
val savedMessages = ChatHistoryPreferences.loadChatMessages(context)
if (savedMessages.isNotEmpty()) {
_chatState.clearMessages()
Expand All @@ -532,18 +545,29 @@ class PhotoReasoningViewModel(
_chatMessagesFlow.value = chatMessages

// Rebuild the chat history for the AI
rebuildChatHistory()
rebuildChatHistory(context) // Pass context here
}
}

/**
* Rebuild the chat history for the AI based on the current messages
*/
private fun rebuildChatHistory() {
private fun rebuildChatHistory(context: Context) { // Added context parameter
// Convert the current chat messages to Content objects for the chat history
val history = mutableListOf<Content>()

// 1. Active System Message
if (_systemMessage.value.isNotBlank()) {
history.add(content(role = "user") { text(_systemMessage.value) })
}

// 2. Formatted Database Entries
val formattedDbEntries = formatDatabaseEntriesAsText(context)
if (formattedDbEntries.isNotBlank()) {
history.add(content(role = "user") { text(formattedDbEntries) })
}

// Group messages by participant to create proper conversation turns
// 3. Group messages by participant to create proper conversation turns
var currentUserContent = ""
var currentModelContent = ""

Expand Down Expand Up @@ -597,20 +621,30 @@ class PhotoReasoningViewModel(
chat = generativeModel.startChat(
history = history
)
} else {
// Ensure chat is reset even if history is empty (e.g. only system message was there and it's now blank)
chat = generativeModel.startChat(history = emptyList())
}
}

/**
* Clear the chat history
*/
fun clearChatHistory(context: android.content.Context? = null) {
fun clearChatHistory(context: Context? = null) {
_chatState.clearMessages()
_chatMessagesFlow.value = emptyList()

// Reset the chat with empty history
chat = generativeModel.startChat(
history = emptyList()
)
val initialHistory = mutableListOf<Content>()
if (_systemMessage.value.isNotBlank()) {
initialHistory.add(content(role = "user") { text(_systemMessage.value) })
}
context?.let { ctx ->
val formattedDbEntries = formatDatabaseEntriesAsText(ctx)
if (formattedDbEntries.isNotBlank()) {
initialHistory.add(content(role = "user") { text(formattedDbEntries) })
}
}
chat = generativeModel.startChat(history = initialHistory.toList())

// Also clear from SharedPreferences if context is provided
context?.let {
Expand All @@ -627,7 +661,7 @@ class PhotoReasoningViewModel(
*/
fun addScreenshotToConversation(
screenshotUri: Uri,
context: android.content.Context,
context: Context,
screenInfo: String? = null
) {
PhotoReasoningApplication.applicationScope.launch(Dispatchers.Main) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.google.ai.sample.util

import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable

@Parcelize
@Serializable
data class SystemMessageEntry(
val title: String,
val guide: String
)
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ object SystemMessageEntryPreferences {
fun saveEntries(context: Context, entries: List<SystemMessageEntry>) {
try {
val jsonString = Json.encodeToString(ListSerializer(SystemMessageEntry.serializer()), entries)
Log.d(TAG, "Saving entries: $jsonString")
Log.d(TAG, "Saving ${entries.size} entries. First entry title if exists: ${entries.firstOrNull()?.title}.")
// Log.v(TAG, "Saving JSON: $jsonString") // Verbose, uncomment if needed for deep debugging
val editor = getSharedPreferences(context).edit()
editor.putString(KEY_SYSTEM_MESSAGE_ENTRIES, jsonString)
editor.apply()
Expand All @@ -32,8 +33,10 @@ object SystemMessageEntryPreferences {
try {
val jsonString = getSharedPreferences(context).getString(KEY_SYSTEM_MESSAGE_ENTRIES, null)
if (jsonString != null) {
Log.d(TAG, "Loaded entries: $jsonString")
return Json.decodeFromString(ListSerializer(SystemMessageEntry.serializer()), jsonString)
// Log.v(TAG, "Loaded JSON: $jsonString") // Verbose
val loadedEntries = Json.decodeFromString(ListSerializer(SystemMessageEntry.serializer()), jsonString)
Log.d(TAG, "Loaded ${loadedEntries.size} entries. First entry title if exists: ${loadedEntries.firstOrNull()?.title}.")
return loadedEntries
}
Log.d(TAG, "No entries found, returning empty list.")
return emptyList()
Expand All @@ -44,19 +47,22 @@ object SystemMessageEntryPreferences {
}

fun addEntry(context: Context, entry: SystemMessageEntry) {
Log.d(TAG, "Adding entry: Title='${entry.title}'")
val entries = loadEntries(context).toMutableList()
entries.add(entry)
saveEntries(context, entries)
}

fun updateEntry(context: Context, oldEntry: SystemMessageEntry, newEntry: SystemMessageEntry) {
Log.d(TAG, "Updating entry: OldTitle='${oldEntry.title}', NewTitle='${newEntry.title}'")
val entries = loadEntries(context).toMutableList()
val index = entries.indexOfFirst { it.title == oldEntry.title } // Assuming title is unique for now
val index = entries.indexOfFirst { it.title == oldEntry.title }
if (index != -1) {
entries[index] = newEntry
saveEntries(context, entries)
Log.i(TAG, "Entry updated successfully: NewTitle='${newEntry.title}'")
} else {
Log.w(TAG, "Entry with title '${oldEntry.title}' not found for update.")
Log.w(TAG, "Entry with old title '${oldEntry.title}' not found for update.")
// Optionally, add the new entry if the old one is not found
// addEntry(context, newEntry)
}
Expand Down
Empty file modified gradlew
100644 → 100755
Empty file.
9 changes: 1 addition & 8 deletions local.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Tue Apr 01 14:08:59 CEST 2025
sdk.dir=D\:\\AndroidStudioSDK
sdk.dir=/system/sdk