Skip to content

Commit 4018b71

Browse files
davidmotsonDavid Motsonashvilithatfiredevdpebot
authored
Server Prompt Templates (#2732)
* Server Prompt Templates * fixes for comments * clean up samples * Update FirebaseAISamples.kt * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes <rosariopf@google.com> * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes <rosariopf@google.com> * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes <rosariopf@google.com> * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes <rosariopf@google.com> * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes <rosariopf@google.com> * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes <rosariopf@google.com> * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes <rosariopf@google.com> * update BoM version * format * Auto-update dependencies. (#2728) * refactor(ai-logic): upgrade to Imagen 4 (#2730) * Auto-update dependencies. (#2731) --------- Co-authored-by: David Motsonashvili <davidmotson@google.com> Co-authored-by: Rosário P. Fernandes <rosariopf@google.com> Co-authored-by: DPEBot <dpebot@google.com>
1 parent 21aee0d commit 4018b71

File tree

7 files changed

+291
-3
lines changed

7 files changed

+291
-3
lines changed

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,8 @@ val FIREBASE_AI_SAMPLES = listOf(
328328
),
329329
Sample(
330330
title = "Grounding with Google Search",
331-
description = "Use Grounding with Google Search to get responses based on up-to-date information from the web.",
331+
description = "Use Grounding with Google Search to get responses based on up-to-date information from the" +
332+
" web.",
332333
navRoute = "chat",
333334
categories = listOf(Category.TEXT, Category.DOCUMENT),
334335
modelName = "gemini-2.5-flash",
@@ -339,4 +340,30 @@ val FIREBASE_AI_SAMPLES = listOf(
339340
)
340341
},
341342
),
343+
Sample(
344+
title = "Server Prompt Template - Imagen",
345+
description = "Generate an image using a server prompt template. Note that you need to setup the template in" +
346+
"the Firebase console before running this demo.",
347+
navRoute = "imagen",
348+
categories = listOf(Category.IMAGE),
349+
initialPrompt = content { text("List of things that should be in the image") },
350+
allowEmptyPrompt = false,
351+
editingMode = EditingMode.TEMPLATE,
352+
// To make this work, create an "Imagen (Basic)" server prompt template in your Firebase project with this name
353+
templateId = "imagen-basic",
354+
templateKey = "prompt"
355+
),
356+
Sample(
357+
title = "Server Prompt Templates - Gemini",
358+
description = "Generate an invoice using server prompt templates. Note that you need to setup the template" +
359+
" in the Firebase console before running this demo.",
360+
navRoute = "text",
361+
categories = listOf(Category.TEXT),
362+
initialPrompt = content { text("Jane Doe") },
363+
allowEmptyPrompt = false,
364+
// To make this work, create an `Input + System Instructions` template in your Firebase project with this name
365+
templateId = "input-system-instructions",
366+
templateKey = "customerName"
367+
),
368+
342369
)

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenRoute
3535
import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenScreen
3636
import com.google.firebase.quickstart.ai.feature.text.ChatRoute
3737
import com.google.firebase.quickstart.ai.feature.text.ChatScreen
38+
import com.google.firebase.quickstart.ai.feature.text.TextGenRoute
39+
import com.google.firebase.quickstart.ai.feature.text.TextGenScreen
3840
import com.google.firebase.quickstart.ai.ui.navigation.MainMenuScreen
3941
import com.google.firebase.quickstart.ai.ui.theme.FirebaseAILogicTheme
4042

@@ -90,6 +92,9 @@ class MainActivity : ComponentActivity() {
9092
"stream" -> {
9193
navController.navigate(StreamRealtimeRoute(it.id))
9294
}
95+
"text" -> {
96+
navController.navigate(TextGenRoute(it.id))
97+
}
9398
}
9499
}
95100
)
@@ -106,6 +111,9 @@ class MainActivity : ComponentActivity() {
106111
composable<StreamRealtimeRoute> {
107112
StreamRealtimeScreen()
108113
}
114+
composable<TextGenRoute> {
115+
TextGenScreen()
116+
}
109117
}
110118
}
111119
}

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ enum class EditingMode {
66
OUTPAINTING,
77
SUBJECT_REFERENCE,
88
STYLE_TRANSFER,
9+
TEMPLATE,
910
}

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.StateFlow
2222
import kotlinx.coroutines.flow.first
2323
import kotlinx.coroutines.launch
2424
import androidx.core.graphics.scale
25+
import com.google.firebase.ai.TemplateImagenModel
2526
import com.google.firebase.ai.type.Dimensions
2627
import com.google.firebase.ai.type.ImagenBackgroundMask
2728
import com.google.firebase.ai.type.ImagenEditMode
@@ -67,6 +68,10 @@ class ImagenViewModel(
6768

6869
val additionalImage = sample.additionalImage
6970

71+
val templateId = sample.templateId
72+
73+
val templateKey = sample.templateKey
74+
7075
private val _attachedImage = MutableStateFlow<Bitmap?>(null)
7176
val attachedImage: StateFlow<Bitmap?> = _attachedImage
7277

@@ -75,6 +80,7 @@ class ImagenViewModel(
7580

7681
// Firebase AI Logic
7782
private val imagenModel: ImagenModel
83+
private val templateImagenModel: TemplateImagenModel
7884

7985
init {
8086
val config = imagenGenerationConfig {
@@ -92,23 +98,34 @@ class ImagenViewModel(
9298
generationConfig = config,
9399
safetySettings = settings
94100
)
101+
templateImagenModel = Firebase.ai.templateImagenModel()
95102
}
96103

97104
fun generateImages(inputText: String) {
98105
viewModelScope.launch {
99106
_isLoading.value = true
107+
_errorMessage.value = null // clear error message
100108
try {
101109
val imageResponse = when(sample.editingMode) {
102110
EditingMode.INPAINTING -> inpaint(imagenModel, inputText)
103111
EditingMode.OUTPAINTING -> outpaint(imagenModel, inputText)
104112
EditingMode.SUBJECT_REFERENCE -> drawReferenceSubject(imagenModel, inputText)
105113
EditingMode.STYLE_TRANSFER -> transferStyle(imagenModel, inputText)
114+
EditingMode.TEMPLATE ->
115+
generateWithTemplate(templateImagenModel, templateId!!, mapOf(templateKey!! to inputText))
106116
else -> generate(imagenModel, inputText)
107117
}
108118
_generatedBitmaps.value = imageResponse.images.map { it.asBitmap() }
109-
_errorMessage.value = null // clear error message
110119
} catch (e: Exception) {
111-
_errorMessage.value = e.localizedMessage
120+
val errorMessage =
121+
if ((e.localizedMessage?.contains("not found") == true) &&
122+
sample.editingMode == EditingMode.TEMPLATE) {
123+
"Template was not found, please verify that your project contains a" +
124+
" template named \"$templateId\"."
125+
} else {
126+
e.localizedMessage
127+
}
128+
_errorMessage.value = errorMessage
112129
} finally {
113130
_isLoading.value = false
114131
}
@@ -212,4 +229,12 @@ class ImagenViewModel(
212229
inputText
213230
)
214231
}
232+
233+
suspend fun generateWithTemplate(
234+
model: TemplateImagenModel,
235+
templateId: String,
236+
inputMap: Map<String, String>
237+
): ImagenGenerationResponse<ImagenInlineImage> {
238+
return model.generateImages(templateId, inputMap)
239+
}
215240
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package com.google.firebase.quickstart.ai.feature.text
2+
3+
import android.net.Uri
4+
import android.provider.OpenableColumns
5+
import android.text.format.Formatter
6+
import androidx.activity.compose.rememberLauncherForActivityResult
7+
import androidx.activity.result.contract.ActivityResultContracts
8+
import androidx.compose.foundation.Image
9+
import androidx.compose.foundation.clickable
10+
import androidx.compose.foundation.layout.Arrangement
11+
import androidx.compose.foundation.layout.Box
12+
import androidx.compose.foundation.layout.Column
13+
import androidx.compose.foundation.layout.Row
14+
import androidx.compose.foundation.layout.fillMaxWidth
15+
import androidx.compose.foundation.layout.height
16+
import androidx.compose.foundation.layout.padding
17+
import androidx.compose.foundation.lazy.grid.GridCells
18+
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
19+
import androidx.compose.foundation.lazy.grid.items
20+
import androidx.compose.foundation.rememberScrollState
21+
import androidx.compose.foundation.verticalScroll
22+
import androidx.compose.material3.Card
23+
import androidx.compose.material3.CardDefaults
24+
import androidx.compose.material3.CircularProgressIndicator
25+
import androidx.compose.material3.DropdownMenu
26+
import androidx.compose.material3.DropdownMenuItem
27+
import androidx.compose.material3.ElevatedCard
28+
import androidx.compose.material3.MaterialTheme
29+
import androidx.compose.material3.OutlinedTextField
30+
import androidx.compose.material3.Text
31+
import androidx.compose.material3.TextButton
32+
import androidx.compose.runtime.Composable
33+
import androidx.compose.runtime.getValue
34+
import androidx.compose.runtime.mutableIntStateOf
35+
import androidx.compose.runtime.mutableStateOf
36+
import androidx.compose.runtime.remember
37+
import androidx.compose.runtime.rememberCoroutineScope
38+
import androidx.compose.runtime.saveable.rememberSaveable
39+
import androidx.compose.runtime.setValue
40+
import androidx.compose.ui.Alignment
41+
import androidx.compose.ui.Modifier
42+
import androidx.compose.ui.graphics.asImageBitmap
43+
import androidx.compose.ui.platform.LocalContext
44+
import androidx.compose.ui.res.painterResource
45+
import androidx.compose.ui.unit.dp
46+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
47+
import androidx.lifecycle.viewmodel.compose.viewModel
48+
import com.google.firebase.quickstart.ai.R
49+
import kotlinx.coroutines.launch
50+
import kotlinx.serialization.Serializable
51+
52+
@Serializable
53+
class TextGenRoute(val sampleId: String)
54+
55+
@Composable
56+
fun TextGenScreen(
57+
textGenViewModel: TextGenViewModel = viewModel<TextGenViewModel>()
58+
) {
59+
var textPrompt by rememberSaveable { mutableStateOf(textGenViewModel.initialPrompt) }
60+
val errorMessage by textGenViewModel.errorMessage.collectAsStateWithLifecycle()
61+
val isLoading by textGenViewModel.isLoading.collectAsStateWithLifecycle()
62+
val generatedText by textGenViewModel.generatedText.collectAsStateWithLifecycle()
63+
64+
Column(
65+
modifier = Modifier.verticalScroll(rememberScrollState())
66+
) {
67+
ElevatedCard(
68+
modifier = Modifier
69+
.padding(all = 16.dp)
70+
.fillMaxWidth(),
71+
shape = MaterialTheme.shapes.large
72+
) {
73+
OutlinedTextField(
74+
value = textPrompt,
75+
label = { Text("Prompt") },
76+
placeholder = { Text("Enter text to generate") },
77+
onValueChange = { textPrompt = it },
78+
modifier = Modifier
79+
.padding(16.dp)
80+
.fillMaxWidth()
81+
)
82+
Row() {
83+
TextButton(
84+
onClick = {
85+
if (textGenViewModel.allowEmptyPrompt || textPrompt.isNotBlank()) {
86+
textGenViewModel.generate(textPrompt)
87+
}
88+
},
89+
modifier = Modifier.padding(end = 16.dp, bottom = 16.dp)
90+
) {
91+
Text("Generate")
92+
}
93+
}
94+
95+
}
96+
97+
if (isLoading) {
98+
Box(
99+
contentAlignment = Alignment.Center,
100+
modifier = Modifier
101+
.padding(all = 8.dp)
102+
.align(Alignment.CenterHorizontally)
103+
) {
104+
CircularProgressIndicator()
105+
}
106+
}
107+
errorMessage?.let {
108+
Card(
109+
modifier = Modifier
110+
.padding(horizontal = 16.dp)
111+
.fillMaxWidth(),
112+
shape = MaterialTheme.shapes.large,
113+
colors = CardDefaults.cardColors(
114+
containerColor = MaterialTheme.colorScheme.errorContainer
115+
)
116+
) {
117+
Text(
118+
text = it,
119+
color = MaterialTheme.colorScheme.error,
120+
modifier = Modifier.padding(all = 16.dp)
121+
)
122+
}
123+
}
124+
generatedText?.let {
125+
Card(
126+
modifier = Modifier
127+
.padding(horizontal = 16.dp)
128+
.fillMaxWidth(),
129+
shape = MaterialTheme.shapes.large,
130+
colors = CardDefaults.cardColors(
131+
containerColor = MaterialTheme.colorScheme.primaryContainer
132+
)
133+
) {
134+
Text(
135+
text = it,
136+
color = MaterialTheme.colorScheme.primary,
137+
modifier = Modifier.padding(all = 16.dp)
138+
)
139+
}
140+
}
141+
}
142+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.google.firebase.quickstart.ai.feature.text
2+
3+
import androidx.lifecycle.SavedStateHandle
4+
import androidx.lifecycle.ViewModel
5+
import androidx.lifecycle.viewModelScope
6+
import androidx.navigation.toRoute
7+
import com.google.firebase.Firebase
8+
import com.google.firebase.ai.ai
9+
import com.google.firebase.ai.type.PublicPreviewAPI
10+
import com.google.firebase.ai.type.asTextOrNull
11+
import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES
12+
import kotlinx.coroutines.flow.MutableStateFlow
13+
import kotlinx.coroutines.flow.StateFlow
14+
import kotlinx.coroutines.launch
15+
import com.google.firebase.ai.GenerativeModel
16+
import com.google.firebase.ai.TemplateGenerativeModel
17+
18+
@OptIn(PublicPreviewAPI::class)
19+
class TextGenViewModel(
20+
savedStateHandle: SavedStateHandle
21+
) : ViewModel() {
22+
private val sampleId = savedStateHandle.toRoute<TextGenRoute>().sampleId
23+
private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId }
24+
val initialPrompt = sample.initialPrompt?.parts?.first()?.asTextOrNull().orEmpty()
25+
26+
private val _errorMessage: MutableStateFlow<String?> = MutableStateFlow(null)
27+
val errorMessage: StateFlow<String?> = _errorMessage
28+
29+
private val _isLoading = MutableStateFlow(false)
30+
val isLoading: StateFlow<Boolean> = _isLoading
31+
32+
val allowEmptyPrompt = sample.allowEmptyPrompt
33+
34+
val templateId = sample.templateId
35+
36+
val templateKey = sample.templateKey
37+
38+
private val _generatedText = MutableStateFlow<String?>(null)
39+
val generatedText: StateFlow<String?> = _generatedText
40+
41+
// Firebase AI Logic
42+
private val generativeModel: GenerativeModel
43+
private val templateGenerativeModel: TemplateGenerativeModel
44+
45+
init {
46+
generativeModel = Firebase.ai(
47+
backend = sample.backend // GenerativeBackend.googleAI() by default
48+
).generativeModel(
49+
modelName = sample.modelName ?: "gemini-2.5-flash",
50+
systemInstruction = sample.systemInstructions,
51+
generationConfig = sample.generationConfig,
52+
tools = sample.tools
53+
)
54+
templateGenerativeModel = Firebase.ai.templateGenerativeModel()
55+
}
56+
57+
fun generate(inputText: String) {
58+
viewModelScope.launch {
59+
_isLoading.value = true
60+
_errorMessage.value = null // clear error message
61+
try {
62+
val generativeResponse = if (templateId != null) {
63+
templateGenerativeModel
64+
.generateContent(templateId, mapOf(templateKey!! to inputText))
65+
} else {
66+
generativeModel.generateContent(inputText)
67+
}
68+
_generatedText.value = generativeResponse.text
69+
} catch (e: Exception) {
70+
val errorMessage =
71+
if ((e.localizedMessage?.contains("not found") == true) && (templateId != null)) {
72+
"Template was not found, please verify that your project contains a" +
73+
" template named \"$templateId\"."
74+
} else {
75+
e.localizedMessage
76+
}
77+
_errorMessage.value = errorMessage
78+
} finally {
79+
_isLoading.value = false
80+
}
81+
}
82+
}
83+
}

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ data class Sample(
4545
val imageLabels: List<String> = emptyList(),
4646
val selectionOptions: List<String> = emptyList(),
4747
val editingMode: EditingMode? = null,
48+
val templateId: String? = null,
49+
val templateKey: String? = null,
4850
)

0 commit comments

Comments
 (0)