Skip to content

Commit

Permalink
add Imagen screen to quickstart
Browse files Browse the repository at this point in the history
  • Loading branch information
David Motsonashvili committed Feb 26, 2025
1 parent ad17c02 commit 61886b9
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.google.firebase.Firebase
import com.google.firebase.quickstart.vertexai.feature.audio.AudioViewModel
import com.google.firebase.quickstart.vertexai.feature.chat.ChatViewModel
import com.google.firebase.quickstart.vertexai.feature.functioncalling.FunctionsChatViewModel
import com.google.firebase.quickstart.vertexai.feature.image.ImagenViewModel
import com.google.firebase.quickstart.vertexai.feature.multimodal.PhotoReasoningViewModel
import com.google.firebase.quickstart.vertexai.feature.text.SummarizeViewModel
import com.google.firebase.vertexai.type.Schema
Expand Down Expand Up @@ -106,6 +107,12 @@ val GenerativeViewModelFactory = object : ViewModelProvider.Factory {
AudioViewModel(generativeModel)
}

isAssignableFrom(ImagenViewModel::class.java) -> {
val imagenModel = Firebase.vertexAI.imagenModel(
"imagen-3.0-fast-generate-001")
ImagenViewModel(imagenModel)
}

else ->
throw IllegalArgumentException("Unknown ViewModel class: ${viewModelClass.name}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.navigation.compose.rememberNavController
import com.google.firebase.quickstart.vertexai.feature.audio.AudioRoute
import com.google.firebase.quickstart.vertexai.feature.chat.ChatRoute
import com.google.firebase.quickstart.vertexai.feature.functioncalling.FunctionsChatRoute
import com.google.firebase.quickstart.vertexai.feature.image.ImagenRoute
import com.google.firebase.quickstart.vertexai.feature.multimodal.PhotoReasoningRoute
import com.google.firebase.quickstart.vertexai.feature.text.SummarizeRoute
import com.google.firebase.quickstart.vertexai.ui.theme.GenerativeAISample
Expand Down Expand Up @@ -68,6 +69,9 @@ class MainActivity : ComponentActivity() {
composable("audio") {
AudioRoute()
}
composable("images") {
ImagenRoute()
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ fun MenuScreen(
MenuItem("photo_reasoning", R.string.menu_reason_title, R.string.menu_reason_description),
MenuItem("chat", R.string.menu_chat_title, R.string.menu_chat_description),
MenuItem("functions_chat", R.string.menu_functions_title, R.string.menu_functions_description),
MenuItem("audio", R.string.menu_audio_title, R.string.menu_audio_description)
MenuItem("audio", R.string.menu_audio_title, R.string.menu_audio_description),
MenuItem("images", R.string.menu_imagen_title, R.string.menu_imagen_description)
)

LazyColumn(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.quickstart.vertexai.feature.image

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.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.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.firebase.quickstart.vertexai.GenerativeViewModelFactory
import com.google.firebase.quickstart.vertexai.R
import com.google.firebase.quickstart.vertexai.ui.theme.GenerativeAISample

@Composable
internal fun ImagenRoute(
imagenViewModel: ImagenViewModel = viewModel(factory = GenerativeViewModelFactory)
) {
val imagenUiState by imagenViewModel.uiState.collectAsState()

ImagenScreen(imagenUiState, onImagenClicked = { inputText ->
imagenViewModel.generateImage(inputText)
})
}

@Composable
fun ImagenScreen(
uiState: ImagenUiState = ImagenUiState.Loading,
onImagenClicked: (String) -> Unit = {}
) {
var imagenPrompt by rememberSaveable { mutableStateOf("") }

Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
ElevatedCard(
modifier = Modifier
.padding(all = 16.dp)
.fillMaxWidth(),
shape = MaterialTheme.shapes.large
) {
OutlinedTextField(
value = imagenPrompt,
label = { Text(stringResource(R.string.imagen_label)) },
placeholder = { Text(stringResource(R.string.imagen_hint)) },
onValueChange = { imagenPrompt = it },
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
)
TextButton(
onClick = {
if (imagenPrompt.isNotBlank()) {
onImagenClicked(imagenPrompt)
}
},
modifier = Modifier
.padding(end = 16.dp, bottom = 16.dp)
.align(Alignment.End)
) {
Text(stringResource(R.string.action_go))
}
}

when (uiState) {
ImagenUiState.Initial -> {
// Nothing is shown
}

ImagenUiState.Loading -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.padding(all = 8.dp)
.align(Alignment.CenterHorizontally)
) {
CircularProgressIndicator()
}
}

is ImagenUiState.Success -> {
Card(
modifier = Modifier
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
.fillMaxWidth(),
shape = MaterialTheme.shapes.large,
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.onSecondaryContainer
)
) {
Image(bitmap = uiState.image.asImageBitmap(), "")
}
}

is ImagenUiState.Error -> {
Card(
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
shape = MaterialTheme.shapes.large,
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
)
) {
Text(
text = uiState.errorMessage,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(all = 16.dp)
)
}
}
}
}
}

@Composable
@Preview(showSystemUi = true)
fun ImagenScreenPreview() {
GenerativeAISample(darkTheme = true) {
ImagenScreen()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.quickstart.vertexai.feature.image

import android.graphics.Bitmap

/**
* A sealed hierarchy describing the state of the text generation.
*/
sealed interface ImagenUiState {

/**
* Empty state when the screen is first shown
*/
data object Initial : ImagenUiState

/**
* Still loading
*/
data object Loading : ImagenUiState

/**
* Text has been generated
*/
data class Success(
val image: Bitmap
) : ImagenUiState

/**
* There was an error generating an image
*/
data class Error(
val errorMessage: String
) : ImagenUiState
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.quickstart.vertexai.feature.image

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.firebase.vertexai.ImagenModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class ImagenViewModel(
private val imageModel: ImagenModel
) : ViewModel() {

private val _uiState: MutableStateFlow<ImagenUiState> =
MutableStateFlow(ImagenUiState.Initial)
val uiState: StateFlow<ImagenUiState> = _uiState.asStateFlow()

fun generateImage(inputText: String) {
_uiState.value = ImagenUiState.Loading

viewModelScope.launch {
// Non-streaming
try {
val imageResponse = imageModel.generateImages(
inputText
)
_uiState.value =
ImagenUiState.Success(imageResponse.images.first().asBitmap())
} catch (e: Exception) {
_uiState.value = ImagenUiState.Error(e.localizedMessage ?: "")
}
}
}
}
6 changes: 6 additions & 0 deletions vertexai/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@
<string name="menu_chat_description">Sample app demonstrating a conversational UI</string>
<string name="menu_audio_title">Generate text from text-and-audio input</string>
<string name="menu_audio_description">Sample app for recording audio and generating text from it</string>
<string name="menu_imagen_title">Generate images from a text prompt</string>
<string name="menu_imagen_description">Sample app demonstrating Imagen</string>

<!-- Summarize sample strings -->
<string name="summarize_label">Text</string>
<string name="summarize_hint">Enter text to summarize</string>

<!-- Generate Image String -->
<string name="imagen_label">Prompt</string>
<string name="imagen_hint">Enter text to generate an image</string>

<!-- Photo Reasoning sample strings -->
<string name="reason_label">Question</string>
<string name="reason_hint">Upload an image and then ask a question</string>
Expand Down
2 changes: 1 addition & 1 deletion vertexai/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.8.1" apply false
id("com.android.application") version "8.6.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.10" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.1.10" apply false
id("com.google.gms.google-services") version "4.4.2" apply false
Expand Down

0 comments on commit 61886b9

Please sign in to comment.