From bc4bcc65d1d78a1327b2413e89fbee3549ac76c3 Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Mon, 10 Nov 2025 20:59:59 -0700 Subject: [PATCH 01/11] Server Prompt Templates --- .../quickstart/ai/FirebaseAISamples.kt | 27 +++ .../firebase/quickstart/ai/MainActivity.kt | 8 + .../ai/feature/media/imagen/EditingMode.kt | 1 + .../feature/media/imagen/ImagenViewModel.kt | 22 +- .../ai/feature/text/TextGenScreen.kt | 197 ++++++++++++++++++ .../ai/feature/text/TextGenViewModel.kt | 81 +++++++ .../quickstart/ai/ui/navigation/Sample.kt | 2 + 7 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index ad3a5cd43..65d30d802 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -339,4 +339,31 @@ val FIREBASE_AI_SAMPLES = listOf( ) }, ), + Sample( + title = "Imagen 3 - Server Template Generation", + description = "Generate an image using a server prompt template.", + backend = GenerativeBackend.vertexAI(), + navRoute = "imagen", + categories = listOf(Category.IMAGE), + initialPrompt = content { text("List of things that should be in the image") }, + allowEmptyPrompt = false, + editingMode = EditingMode.TEMPLATE, + // To make this work on your project, create an `Imagen (Basic)` template in your project with this name + templateId = "imagen-test-template", + templateKey = "prompt" + ), + Sample( + title = "Server Prompt Templates", + description = "Generate an invoice using server prompt templates", + backend = GenerativeBackend.vertexAI(), + navRoute = "text", + categories = listOf(Category.TEXT), + initialPrompt = content { text("Customer Name") }, + allowEmptyPrompt = false, + editingMode = EditingMode.TEMPLATE, + // To make this work on your project, create an `Input + System Instructions` template in your project with this name + templateId = "textgen-test-template", + templateKey = "customerName" + ), + ) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt index 54eaff654..8c9a5188c 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt @@ -35,6 +35,8 @@ import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenRoute import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenScreen import com.google.firebase.quickstart.ai.feature.text.ChatRoute import com.google.firebase.quickstart.ai.feature.text.ChatScreen +import com.google.firebase.quickstart.ai.feature.text.TextGenRoute +import com.google.firebase.quickstart.ai.feature.text.TextGenScreen import com.google.firebase.quickstart.ai.ui.navigation.MainMenuScreen import com.google.firebase.quickstart.ai.ui.theme.FirebaseAILogicTheme @@ -90,6 +92,9 @@ class MainActivity : ComponentActivity() { "stream" -> { navController.navigate(StreamRealtimeRoute(it.id)) } + "text" -> { + navController.navigate(TextGenRoute(it.id)) + } } } ) @@ -106,6 +111,9 @@ class MainActivity : ComponentActivity() { composable { StreamRealtimeScreen() } + composable { + TextGenScreen() + } } } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt index 6e997092d..10093e735 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt @@ -6,4 +6,5 @@ enum class EditingMode { OUTPAINTING, SUBJECT_REFERENCE, STYLE_TRANSFER, + TEMPLATE, } \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt index 8b6f223a6..6a885ae98 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import androidx.core.graphics.scale +import com.google.firebase.ai.TemplateImagenModel import com.google.firebase.ai.type.Dimensions import com.google.firebase.ai.type.ImagenBackgroundMask import com.google.firebase.ai.type.ImagenEditMode @@ -67,6 +68,10 @@ class ImagenViewModel( val additionalImage = sample.additionalImage + val templateId = sample.templateId + + val templateKey = sample.templateKey + private val _attachedImage = MutableStateFlow(null) val attachedImage: StateFlow = _attachedImage @@ -103,12 +108,18 @@ class ImagenViewModel( EditingMode.OUTPAINTING -> outpaint(imagenModel, inputText) EditingMode.SUBJECT_REFERENCE -> drawReferenceSubject(imagenModel, inputText) EditingMode.STYLE_TRANSFER -> transferStyle(imagenModel, inputText) + EditingMode.TEMPLATE -> generateWithTemplate(Firebase.ai.templateImagenModel(), templateId!!, templateKey!!, inputText) else -> generate(imagenModel, inputText) } _generatedBitmaps.value = imageResponse.images.map { it.asBitmap() } _errorMessage.value = null // clear error message } catch (e: Exception) { - _errorMessage.value = e.localizedMessage + val errorMessage = if ((e.localizedMessage?.contains("not found") == true) && (templateId != null)) { + "Template was not found, please add a template named $templateId to your project." + } else { + e.localizedMessage + } + _errorMessage.value = errorMessage } finally { _isLoading.value = false } @@ -212,4 +223,13 @@ class ImagenViewModel( inputText ) } + + suspend fun generateWithTemplate( + model: TemplateImagenModel, + templateId: String, + templateKey: String, + inputText: String, + ): ImagenGenerationResponse { + return model.generateImages(templateId, mapOf(templateKey to inputText)) + } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt new file mode 100644 index 000000000..5c4619f89 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt @@ -0,0 +1,197 @@ +package com.google.firebase.quickstart.ai.feature.text + +import android.net.Uri +import android.provider.OpenableColumns +import android.text.format.Formatter +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.firebase.quickstart.ai.R +import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable + +@Serializable +class TextGenRoute(val sampleId: String) + +@Composable +fun TextGenScreen( + textGenViewModel: TextGenViewModel = viewModel() +) { + var textPrompt by rememberSaveable { mutableStateOf(textGenViewModel.initialPrompt) } + val errorMessage by textGenViewModel.errorMessage.collectAsStateWithLifecycle() + val isLoading by textGenViewModel.isLoading.collectAsStateWithLifecycle() + val generatedText by textGenViewModel.generatedText.collectAsStateWithLifecycle() + + Column( + modifier = Modifier.verticalScroll(rememberScrollState()) + ) { + ElevatedCard( + modifier = Modifier + .padding(all = 16.dp) + .fillMaxWidth(), + shape = MaterialTheme.shapes.large + ) { + OutlinedTextField( + value = textPrompt, + label = { Text("Prompt") }, + placeholder = { Text("Enter text to generate") }, + onValueChange = { textPrompt = it }, + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + ) + Row() { + TextButton( + onClick = { + if (textGenViewModel.allowEmptyPrompt || textPrompt.isNotBlank()) { + textGenViewModel.generate(textPrompt) + } + }, + modifier = Modifier + .padding(end = 16.dp, bottom = 16.dp) + ) { + Text("Generate") + } + } + + } + + if (isLoading) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .padding(all = 8.dp) + .align(Alignment.CenterHorizontally) + ) { + CircularProgressIndicator() + } + } + errorMessage?.let { + Card( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + shape = MaterialTheme.shapes.large, + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer + ) + ) { + Text( + text = it, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(all = 16.dp) + ) + } + } + generatedText?.let { + Card( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + shape = MaterialTheme.shapes.large, + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ) + ) { + Text( + text = it, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(all = 16.dp) + ) + } + } + } +} + +@Composable +fun DropDownMenu(items: List, onClick: (String) -> Unit) { + + val isDropDownExpanded = remember { + mutableStateOf(false) + } + + val itemPosition = remember { + mutableIntStateOf(0) + } + + + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top, + modifier = Modifier.padding(horizontal = 10.dp) + ) { + + Box { + Row( + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.Top, + modifier = Modifier.clickable { + isDropDownExpanded.value = true + } + ) { + Text(text = items[itemPosition.intValue]) + Image( + painter = painterResource(id = R.drawable.round_arrow_drop_down_24), + contentDescription = "Dropdown Icon" + ) + } + DropdownMenu( + expanded = isDropDownExpanded.value, + onDismissRequest = { + isDropDownExpanded.value = false + }) { + items.forEachIndexed { index, item -> + DropdownMenuItem( + text = { + Text(text = item) + }, + onClick = { + isDropDownExpanded.value = false + itemPosition.intValue = index + onClick(item) + }) + } + } + } + + } +} \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt new file mode 100644 index 000000000..3b8bd7c76 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt @@ -0,0 +1,81 @@ +package com.google.firebase.quickstart.ai.feature.text + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import com.google.firebase.Firebase +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.ai.type.asTextOrNull +import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import com.google.firebase.ai.GenerativeModel + +@OptIn(PublicPreviewAPI::class) +class TextGenViewModel( + savedStateHandle: SavedStateHandle +) : ViewModel() { + private val sampleId = savedStateHandle.toRoute().sampleId + private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId } + val initialPrompt = sample.initialPrompt?.parts?.first()?.asTextOrNull().orEmpty() + + private val _errorMessage: MutableStateFlow = MutableStateFlow(null) + val errorMessage: StateFlow = _errorMessage + + private val _isLoading = MutableStateFlow(false) + val isLoading: StateFlow = _isLoading + + + val allowEmptyPrompt = sample.allowEmptyPrompt + + val templateId = sample.templateId + + val templateKey = sample.templateKey + + + private val _generatedText = MutableStateFlow(null) + val generatedText: StateFlow = _generatedText + + // Firebase AI Logic + private val generativeModel: GenerativeModel + + init { + generativeModel = Firebase.ai( + backend = sample.backend // GenerativeBackend.googleAI() by default + ).generativeModel( + modelName = sample.modelName ?: "gemini-2.5-flash", + systemInstruction = sample.systemInstructions, + generationConfig = sample.generationConfig, + tools = sample.tools + ) + } + + fun generate(inputText: String) { + viewModelScope.launch { + _isLoading.value = true + try { + val generativeResponse = if (templateId != null) { + Firebase.ai.templateGenerativeModel() + .generateContent(templateId, mapOf(templateKey!! to inputText)) + } else { + generativeModel.generateContent(inputText) + } + _generatedText.value = generativeResponse.text + _errorMessage.value = null // clear error message + } catch (e: Exception) { + val errorMessage = + if ((e.localizedMessage?.contains("not found") == true) && (templateId != null)) { + "Template was not found, please add a template named $templateId to your project." + } else { + e.localizedMessage + } + _errorMessage.value = errorMessage + } finally { + _isLoading.value = false + } + } + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt index ad0cccbec..3704b2b44 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt @@ -45,4 +45,6 @@ data class Sample( val imageLabels: List = emptyList(), val selectionOptions: List = emptyList(), val editingMode: EditingMode? = null, + val templateId: String? = null, + val templateKey: String? = null, ) From af7b3c8a33db456c8fc7949b36908ec6fb58d77d Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Tue, 11 Nov 2025 10:09:15 -0800 Subject: [PATCH 02/11] fixes for comments --- .../quickstart/ai/FirebaseAISamples.kt | 2 +- .../feature/media/imagen/ImagenViewModel.kt | 22 +++---- .../ai/feature/text/TextGenScreen.kt | 57 +------------------ .../ai/feature/text/TextGenViewModel.kt | 11 ++-- 4 files changed, 20 insertions(+), 72 deletions(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index 65d30d802..58f23601b 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -362,7 +362,7 @@ val FIREBASE_AI_SAMPLES = listOf( allowEmptyPrompt = false, editingMode = EditingMode.TEMPLATE, // To make this work on your project, create an `Input + System Instructions` template in your project with this name - templateId = "textgen-test-template", + templateId = "input-system-instructions", templateKey = "customerName" ), diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt index 6a885ae98..86e9f6e3a 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt @@ -80,6 +80,7 @@ class ImagenViewModel( // Firebase AI Logic private val imagenModel: ImagenModel + private val templateImagenModel: TemplateImagenModel init { val config = imagenGenerationConfig { @@ -97,28 +98,30 @@ class ImagenViewModel( generationConfig = config, safetySettings = settings ) + templateImagenModel = Firebase.ai.templateImagenModel() } fun generateImages(inputText: String) { viewModelScope.launch { _isLoading.value = true + _errorMessage.value = null // clear error message try { val imageResponse = when(sample.editingMode) { EditingMode.INPAINTING -> inpaint(imagenModel, inputText) EditingMode.OUTPAINTING -> outpaint(imagenModel, inputText) EditingMode.SUBJECT_REFERENCE -> drawReferenceSubject(imagenModel, inputText) EditingMode.STYLE_TRANSFER -> transferStyle(imagenModel, inputText) - EditingMode.TEMPLATE -> generateWithTemplate(Firebase.ai.templateImagenModel(), templateId!!, templateKey!!, inputText) + EditingMode.TEMPLATE -> generateWithTemplate(templateImagenModel, templateId!!, mapOf(templateKey!! to inputText)) else -> generate(imagenModel, inputText) } _generatedBitmaps.value = imageResponse.images.map { it.asBitmap() } - _errorMessage.value = null // clear error message } catch (e: Exception) { - val errorMessage = if ((e.localizedMessage?.contains("not found") == true) && (templateId != null)) { - "Template was not found, please add a template named $templateId to your project." - } else { - e.localizedMessage - } + val errorMessage = + if ((e.localizedMessage?.contains("not found") == true) && sample.editingMode == EditingMode.TEMPLATE) { + "Template was not found, please verify that your project contains a template named \"$templateId\"." + } else { + e.localizedMessage + } _errorMessage.value = errorMessage } finally { _isLoading.value = false @@ -227,9 +230,8 @@ class ImagenViewModel( suspend fun generateWithTemplate( model: TemplateImagenModel, templateId: String, - templateKey: String, - inputText: String, + inputMap: Map ): ImagenGenerationResponse { - return model.generateImages(templateId, mapOf(templateKey to inputText)) + return model.generateImages(templateId, inputMap) } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt index 5c4619f89..26331333b 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt @@ -86,8 +86,7 @@ fun TextGenScreen( textGenViewModel.generate(textPrompt) } }, - modifier = Modifier - .padding(end = 16.dp, bottom = 16.dp) + modifier = Modifier.padding(end = 16.dp, bottom = 16.dp) ) { Text("Generate") } @@ -141,57 +140,3 @@ fun TextGenScreen( } } } - -@Composable -fun DropDownMenu(items: List, onClick: (String) -> Unit) { - - val isDropDownExpanded = remember { - mutableStateOf(false) - } - - val itemPosition = remember { - mutableIntStateOf(0) - } - - - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.Top, - modifier = Modifier.padding(horizontal = 10.dp) - ) { - - Box { - Row( - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.Top, - modifier = Modifier.clickable { - isDropDownExpanded.value = true - } - ) { - Text(text = items[itemPosition.intValue]) - Image( - painter = painterResource(id = R.drawable.round_arrow_drop_down_24), - contentDescription = "Dropdown Icon" - ) - } - DropdownMenu( - expanded = isDropDownExpanded.value, - onDismissRequest = { - isDropDownExpanded.value = false - }) { - items.forEachIndexed { index, item -> - DropdownMenuItem( - text = { - Text(text = item) - }, - onClick = { - isDropDownExpanded.value = false - itemPosition.intValue = index - onClick(item) - }) - } - } - } - - } -} \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt index 3b8bd7c76..3601a6e12 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import com.google.firebase.ai.GenerativeModel +import com.google.firebase.ai.TemplateGenerativeModel @OptIn(PublicPreviewAPI::class) class TextGenViewModel( @@ -28,19 +29,18 @@ class TextGenViewModel( private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow = _isLoading - val allowEmptyPrompt = sample.allowEmptyPrompt val templateId = sample.templateId val templateKey = sample.templateKey - private val _generatedText = MutableStateFlow(null) val generatedText: StateFlow = _generatedText // Firebase AI Logic private val generativeModel: GenerativeModel + private val templateGenerativeModel: TemplateGenerativeModel init { generativeModel = Firebase.ai( @@ -51,24 +51,25 @@ class TextGenViewModel( generationConfig = sample.generationConfig, tools = sample.tools ) + templateGenerativeModel = Firebase.ai.templateGenerativeModel() } fun generate(inputText: String) { viewModelScope.launch { _isLoading.value = true + _errorMessage.value = null // clear error message try { val generativeResponse = if (templateId != null) { - Firebase.ai.templateGenerativeModel() + templateGenerativeModel .generateContent(templateId, mapOf(templateKey!! to inputText)) } else { generativeModel.generateContent(inputText) } _generatedText.value = generativeResponse.text - _errorMessage.value = null // clear error message } catch (e: Exception) { val errorMessage = if ((e.localizedMessage?.contains("not found") == true) && (templateId != null)) { - "Template was not found, please add a template named $templateId to your project." + "Template was not found, please verify that your project contains a template named \"$templateId\"." } else { e.localizedMessage } From 50c531436a46c2580ee4e888b21728172759e240 Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Tue, 11 Nov 2025 10:11:23 -0800 Subject: [PATCH 03/11] clean up samples --- .../com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index 58f23601b..ddcad6441 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -342,7 +342,6 @@ val FIREBASE_AI_SAMPLES = listOf( Sample( title = "Imagen 3 - Server Template Generation", description = "Generate an image using a server prompt template.", - backend = GenerativeBackend.vertexAI(), navRoute = "imagen", categories = listOf(Category.IMAGE), initialPrompt = content { text("List of things that should be in the image") }, @@ -355,12 +354,10 @@ val FIREBASE_AI_SAMPLES = listOf( Sample( title = "Server Prompt Templates", description = "Generate an invoice using server prompt templates", - backend = GenerativeBackend.vertexAI(), navRoute = "text", categories = listOf(Category.TEXT), initialPrompt = content { text("Customer Name") }, allowEmptyPrompt = false, - editingMode = EditingMode.TEMPLATE, // To make this work on your project, create an `Input + System Instructions` template in your project with this name templateId = "input-system-instructions", templateKey = "customerName" From 1afd252daded749ab30a4ef47d1ac62da0c1ea79 Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Wed, 12 Nov 2025 10:23:48 -0700 Subject: [PATCH 04/11] Update FirebaseAISamples.kt --- .../java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index ddcad6441..a5440a6eb 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -348,7 +348,7 @@ val FIREBASE_AI_SAMPLES = listOf( allowEmptyPrompt = false, editingMode = EditingMode.TEMPLATE, // To make this work on your project, create an `Imagen (Basic)` template in your project with this name - templateId = "imagen-test-template", + templateId = "imagen-basic", templateKey = "prompt" ), Sample( From 0b3d397fede57aff2ef711f72dfcedc832f92ed4 Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Thu, 13 Nov 2025 17:20:40 -0700 Subject: [PATCH 05/11] Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rosário P. Fernandes --- .../java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index a5440a6eb..11e456b82 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -353,7 +353,7 @@ val FIREBASE_AI_SAMPLES = listOf( ), Sample( title = "Server Prompt Templates", - description = "Generate an invoice using server prompt templates", + description = "Generate an invoice using server prompt templates. Note that you need to setup the template in the Firebase console before running this demo.", navRoute = "text", categories = listOf(Category.TEXT), initialPrompt = content { text("Customer Name") }, From b744e2b0cbb7d7522514ae3e05ade07d74122a7e Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Thu, 13 Nov 2025 17:20:53 -0700 Subject: [PATCH 06/11] Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rosário P. Fernandes --- .../java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index 11e456b82..84a04c66e 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -358,7 +358,7 @@ val FIREBASE_AI_SAMPLES = listOf( categories = listOf(Category.TEXT), initialPrompt = content { text("Customer Name") }, allowEmptyPrompt = false, - // To make this work on your project, create an `Input + System Instructions` template in your project with this name + // To make this work, create an `Input + System Instructions` template in your Firebase project with this name templateId = "input-system-instructions", templateKey = "customerName" ), From c645fcad64b1e2462daf4c1a35e2e9b78a4341e8 Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Thu, 13 Nov 2025 17:21:04 -0700 Subject: [PATCH 07/11] Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rosário P. Fernandes --- .../java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index 84a04c66e..2dfa5afd7 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -340,7 +340,7 @@ val FIREBASE_AI_SAMPLES = listOf( }, ), Sample( - title = "Imagen 3 - Server Template Generation", + title = "Server Prompt Template - Imagen", description = "Generate an image using a server prompt template.", navRoute = "imagen", categories = listOf(Category.IMAGE), From 9aedc10513d0dbe34b4050ee3f8180ba032eff5a Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Thu, 13 Nov 2025 17:21:27 -0700 Subject: [PATCH 08/11] Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rosário P. Fernandes --- .../java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index 2dfa5afd7..74247b7db 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -341,7 +341,7 @@ val FIREBASE_AI_SAMPLES = listOf( ), Sample( title = "Server Prompt Template - Imagen", - description = "Generate an image using a server prompt template.", + description = "Generate an image using a server prompt template. Note that you need to setup the template in the Firebase console before running this demo.", navRoute = "imagen", categories = listOf(Category.IMAGE), initialPrompt = content { text("List of things that should be in the image") }, From cd496b198731a7e0aba6434e0ab07d3e48b22502 Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Thu, 13 Nov 2025 17:21:36 -0700 Subject: [PATCH 09/11] Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rosário P. Fernandes --- .../java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index 74247b7db..d8f6b0ec8 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -352,7 +352,7 @@ val FIREBASE_AI_SAMPLES = listOf( templateKey = "prompt" ), Sample( - title = "Server Prompt Templates", + title = "Server Prompt Templates - Gemini", description = "Generate an invoice using server prompt templates. Note that you need to setup the template in the Firebase console before running this demo.", navRoute = "text", categories = listOf(Category.TEXT), From 4e599170ebe54bc92b66696e8e85058e1d4556fe Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Thu, 13 Nov 2025 17:21:46 -0700 Subject: [PATCH 10/11] Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rosário P. Fernandes --- .../java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index d8f6b0ec8..e9d987fb8 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -356,7 +356,7 @@ val FIREBASE_AI_SAMPLES = listOf( description = "Generate an invoice using server prompt templates. Note that you need to setup the template in the Firebase console before running this demo.", navRoute = "text", categories = listOf(Category.TEXT), - initialPrompt = content { text("Customer Name") }, + initialPrompt = content { text("Jane Doe") }, allowEmptyPrompt = false, // To make this work, create an `Input + System Instructions` template in your Firebase project with this name templateId = "input-system-instructions", From f2b49f9da2ab5da3c4112b89768c1f4733d0315e Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Thu, 13 Nov 2025 17:21:58 -0700 Subject: [PATCH 11/11] Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rosário P. Fernandes --- .../java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index e9d987fb8..c13edfe1b 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -347,7 +347,7 @@ val FIREBASE_AI_SAMPLES = listOf( initialPrompt = content { text("List of things that should be in the image") }, allowEmptyPrompt = false, editingMode = EditingMode.TEMPLATE, - // To make this work on your project, create an `Imagen (Basic)` template in your project with this name + // To make this work, create an "Imagen (Basic)" server prompt template in your Firebase project with this name templateId = "imagen-basic", templateKey = "prompt" ),