diff --git a/firebase-ai/README.md b/firebase-ai/README.md index e35339307..5ae40900b 100644 --- a/firebase-ai/README.md +++ b/firebase-ai/README.md @@ -18,7 +18,7 @@ To try out this sample app, you need to use latest stable version of Android Stu * [Set up your Android app for Firebase][setup-android] * Use the package name `com.google.firebase.quickstart.ai` -* [Set up Firebase AI Logic][setup-ai-logic] +* [Set up Firebase AI Logic][setup-ai-logic] * Run the app on an Android device or emulator. ## Features @@ -34,13 +34,7 @@ You can find the implementation for each feature by clicking on the links below: - [Server Prompt Templates - Gemini](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ServerPromptTemplateViewModel.kt): Generate an invoice using server prompt templates. ### Image analysis / generation -- [Imagen 4 - image generation](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt): Generate images using Imagen 4 -- [Imagen 3 - Inpainting (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt): Replace part of an image using Imagen 3 -- [Imagen 3 - Outpainting (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt): Expand an image by drawing in more background -- [Imagen 3 - Subject Reference (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt): Generate an image using a referenced subject (must be an animal) -- [Imagen 3 - Style Transfer (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt): Change the art style of a cat picture using a reference - [Gemini 2.5 Flash Image (aka nanobanana)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageGenerationViewModel.kt): Generate and/or edit images using Gemini 2.5 Flash Image aka nanobanana -- [Server Prompt Template - Imagen](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt): Generate an image using a server prompt template. - [SVG Generator](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/SvgViewModel.kt): Use Gemini 3 Flash preview to create SVG illustrations - [Blog post creator (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageBlogCreatorViewModel.kt): Create a blog post from an image file stored in Cloud Storage. 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 51ed6ce39..8cbafed1f 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 @@ -28,12 +28,10 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.google.firebase.quickstart.ai.feature.live.BidiViewModel import com.google.firebase.quickstart.ai.feature.hybrid.HybridInferenceViewModel -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenViewModel import com.google.firebase.quickstart.ai.feature.text.ChatViewModel import com.google.firebase.quickstart.ai.feature.text.ServerPromptTemplateViewModel import com.google.firebase.quickstart.ai.feature.text.SvgViewModel import com.google.firebase.quickstart.ai.ui.ChatScreen -import com.google.firebase.quickstart.ai.ui.ImagenScreen import com.google.firebase.quickstart.ai.ui.ServerPromptScreen import com.google.firebase.quickstart.ai.ui.StreamRealtimeScreen import com.google.firebase.quickstart.ai.ui.StreamRealtimeVideoScreen @@ -100,10 +98,6 @@ class MainActivity : ComponentActivity() { (vm as? ChatViewModel)?.let { ChatScreen(it) } } - ScreenType.IMAGEN -> { - (vm as? ImagenViewModel)?.let { ImagenScreen(it) } - } - ScreenType.SVG -> { (vm as? SvgViewModel)?.let { SvgScreen(it) } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt deleted file mode 100644 index 1bcd6fb86..000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.google.firebase.quickstart.ai.feature.media.imagen - -import android.graphics.Bitmap -import com.google.firebase.Firebase -import com.google.firebase.ai.ImagenModel -import com.google.firebase.ai.ai -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.ImagenGenerationResponse -import com.google.firebase.ai.type.ImagenImageFormat -import com.google.firebase.ai.type.ImagenInlineImage -import com.google.firebase.ai.type.ImagenPersonFilterLevel -import com.google.firebase.ai.type.ImagenSafetyFilterLevel -import com.google.firebase.ai.type.ImagenSafetySettings -import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.ai.type.imagenGenerationConfig -import com.google.firebase.quickstart.ai.ui.ImagenUiState -import kotlinx.serialization.Serializable - -@Serializable -object ImagenGenerationRoute - -@OptIn(PublicPreviewAPI::class) -class ImagenGenerationViewModel : ImagenViewModel() { - override val initialPrompt: String = "" - override val includeAttach: Boolean = false - override val selectionOptions: List = emptyList() - override val allowEmptyPrompt: Boolean = false - override val additionalImage: Bitmap? = null - override val imageLabels: List = emptyList() - - private val imagenModel: ImagenModel - - init { - imagenModel = Firebase.ai( - backend = GenerativeBackend.googleAI() - ).imagenModel( - modelName = "imagen-4.0-generate-001", - generationConfig = imagenGenerationConfig { - numberOfImages = 4 - imageFormat = ImagenImageFormat.png() - }, - safetySettings = ImagenSafetySettings( - safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, - personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL - ) - ) - } - - override suspend fun performGeneration( - inputText: String, - currentState: ImagenUiState.Success - ): ImagenGenerationResponse { - return imagenModel.generateImages(inputText) - } -} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt deleted file mode 100644 index 40e249e31..000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.google.firebase.quickstart.ai.feature.media.imagen - -import android.graphics.Bitmap -import com.google.firebase.Firebase -import com.google.firebase.ai.ImagenModel -import com.google.firebase.ai.ai -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.ImagenBackgroundMask -import com.google.firebase.ai.type.ImagenEditMode -import com.google.firebase.ai.type.ImagenEditingConfig -import com.google.firebase.ai.type.ImagenForegroundMask -import com.google.firebase.ai.type.ImagenGenerationResponse -import com.google.firebase.ai.type.ImagenImageFormat -import com.google.firebase.ai.type.ImagenInlineImage -import com.google.firebase.ai.type.ImagenPersonFilterLevel -import com.google.firebase.ai.type.ImagenRawImage -import com.google.firebase.ai.type.ImagenSafetyFilterLevel -import com.google.firebase.ai.type.ImagenSafetySettings -import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.ai.type.imagenGenerationConfig -import com.google.firebase.ai.type.toImagenInlineImage -import com.google.firebase.quickstart.ai.ui.ImagenUiState -import kotlinx.serialization.Serializable - -@Serializable -object ImagenInpaintingRoute - -@OptIn(PublicPreviewAPI::class) -class ImagenInpaintingViewModel : ImagenViewModel() { - override val initialPrompt: String = "A sunny beach" - override val includeAttach: Boolean = true - override val selectionOptions: List = listOf("Mask", "Background", "Foreground") - override val allowEmptyPrompt: Boolean = true - override val additionalImage: Bitmap? = null - override val imageLabels: List = emptyList() - - private val imagenModel: ImagenModel - - init { - val config = imagenGenerationConfig { - numberOfImages = 4 - imageFormat = ImagenImageFormat.png() - } - val settings = ImagenSafetySettings( - safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, - personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL - ) - imagenModel = Firebase.ai( - backend = GenerativeBackend.vertexAI() - ).imagenModel( - modelName = "imagen-3.0-capability-001", - generationConfig = config, - safetySettings = settings - ) - } - - override suspend fun performGeneration( - inputText: String, - currentState: ImagenUiState.Success - ): ImagenGenerationResponse { - val bitmap = currentState.attachedImage!! - val mask = when (currentState.selectedOption) { - "Foreground" -> ImagenForegroundMask() - else -> ImagenBackgroundMask() - } - return imagenModel.editImage( - listOfNotNull(ImagenRawImage(bitmap.toImagenInlineImage()), mask), - inputText, - ImagenEditingConfig(ImagenEditMode.INPAINT_INSERTION) - ) - } -} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt deleted file mode 100644 index 1f8533363..000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.google.firebase.quickstart.ai.feature.media.imagen - -import android.graphics.Bitmap -import com.google.firebase.Firebase -import com.google.firebase.ai.ImagenModel -import com.google.firebase.ai.ai -import com.google.firebase.ai.type.Dimensions -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.ImagenEditMode -import com.google.firebase.ai.type.ImagenEditingConfig -import com.google.firebase.ai.type.ImagenGenerationResponse -import com.google.firebase.ai.type.ImagenImageFormat -import com.google.firebase.ai.type.ImagenImagePlacement -import com.google.firebase.ai.type.ImagenInlineImage -import com.google.firebase.ai.type.ImagenMaskReference -import com.google.firebase.ai.type.ImagenPersonFilterLevel -import com.google.firebase.ai.type.ImagenRawMask -import com.google.firebase.ai.type.ImagenSafetyFilterLevel -import com.google.firebase.ai.type.ImagenSafetySettings -import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.ai.type.imagenGenerationConfig -import com.google.firebase.ai.type.toImagenInlineImage -import com.google.firebase.quickstart.ai.ui.ImagenUiState -import kotlinx.serialization.Serializable - -@Serializable -object ImagenOutpaintingRoute - -@OptIn(PublicPreviewAPI::class) -class ImagenOutpaintingViewModel : ImagenViewModel() { - override val initialPrompt: String = "" - override val includeAttach: Boolean = true - override val selectionOptions: List = listOf("Image Alignment", "Center", "Top", "Bottom", "Left", "Right") - override val allowEmptyPrompt: Boolean = true - override val additionalImage: Bitmap? = null - override val imageLabels: List = emptyList() - - private val imagenModel: ImagenModel - - init { - val config = imagenGenerationConfig { - numberOfImages = 4 - imageFormat = ImagenImageFormat.png() - } - val settings = ImagenSafetySettings( - safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, - personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL - ) - imagenModel = Firebase.ai( - backend = GenerativeBackend.vertexAI() - ).imagenModel( - modelName = "imagen-3.0-capability-001", - generationConfig = config, - safetySettings = settings - ) - } - - override suspend fun performGeneration( - inputText: String, - currentState: ImagenUiState.Success - ): ImagenGenerationResponse { - val bitmap = currentState.attachedImage!! - val position = when (currentState.selectedOption) { - "Top" -> ImagenImagePlacement.TOP_CENTER - "Bottom" -> ImagenImagePlacement.BOTTOM_CENTER - "Left" -> ImagenImagePlacement.LEFT_CENTER - "Right" -> ImagenImagePlacement.RIGHT_CENTER - else -> ImagenImagePlacement.CENTER - } - val dimensions = Dimensions(bitmap.width * 2, bitmap.height * 2) - val (sourceImage, mask) = ImagenMaskReference.generateMaskAndPadForOutpainting( - bitmap.toImagenInlineImage(), - dimensions, - position - ) - return imagenModel.editImage( - listOf(sourceImage, ImagenRawMask(mask.image!!, 0.05)), - inputText, - ImagenEditingConfig(ImagenEditMode.OUTPAINT) - ) - } -} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt deleted file mode 100644 index 8186811eb..000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.google.firebase.quickstart.ai.feature.media.imagen - -import android.graphics.Bitmap -import com.google.firebase.Firebase -import com.google.firebase.ai.ImagenModel -import com.google.firebase.ai.ai -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.ImagenGenerationResponse -import com.google.firebase.ai.type.ImagenImageFormat -import com.google.firebase.ai.type.ImagenInlineImage -import com.google.firebase.ai.type.ImagenPersonFilterLevel -import com.google.firebase.ai.type.ImagenRawImage -import com.google.firebase.ai.type.ImagenSafetyFilterLevel -import com.google.firebase.ai.type.ImagenSafetySettings -import com.google.firebase.ai.type.ImagenStyleReference -import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.ai.type.imagenGenerationConfig -import com.google.firebase.ai.type.toImagenInlineImage -import com.google.firebase.quickstart.ai.MainActivity -import com.google.firebase.quickstart.ai.ui.ImagenUiState -import kotlinx.serialization.Serializable - -@Serializable -object ImagenStyleTransferRoute - -@OptIn(PublicPreviewAPI::class) -class ImagenStyleTransferViewModel : ImagenViewModel() { - override val initialPrompt: String = "A picture of a cat" - override val includeAttach: Boolean = true - override val selectionOptions: List = emptyList() - override val allowEmptyPrompt: Boolean = true - override val additionalImage: Bitmap = MainActivity.catImage - override val imageLabels: List = listOf("Style Target", "Style Source") - - private val imagenModel: ImagenModel - - init { - val config = imagenGenerationConfig { - numberOfImages = 4 - imageFormat = ImagenImageFormat.png() - } - val settings = ImagenSafetySettings( - safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, - personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL - ) - imagenModel = Firebase.ai( - backend = GenerativeBackend.vertexAI() - ).imagenModel( - modelName = "imagen-3.0-capability-001", - generationConfig = config, - safetySettings = settings - ) - } - - override suspend fun performGeneration( - inputText: String, - currentState: ImagenUiState.Success - ): ImagenGenerationResponse { - val attachedImage = currentState.attachedImage!! - return imagenModel.editImage( - listOf( - ImagenRawImage(MainActivity.catImage.toImagenInlineImage()), - ImagenStyleReference(attachedImage.toImagenInlineImage(), 1, "an art style") - ), - "Generate an image in an art style [1] based on the following caption: $inputText", - ) - } -} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt deleted file mode 100644 index a0a6ef8f0..000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.google.firebase.quickstart.ai.feature.media.imagen - -import android.graphics.Bitmap -import com.google.firebase.Firebase -import com.google.firebase.ai.ImagenModel -import com.google.firebase.ai.ai -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.ImagenGenerationResponse -import com.google.firebase.ai.type.ImagenImageFormat -import com.google.firebase.ai.type.ImagenInlineImage -import com.google.firebase.ai.type.ImagenPersonFilterLevel -import com.google.firebase.ai.type.ImagenSafetyFilterLevel -import com.google.firebase.ai.type.ImagenSafetySettings -import com.google.firebase.ai.type.ImagenSubjectReference -import com.google.firebase.ai.type.ImagenSubjectReferenceType -import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.ai.type.imagenGenerationConfig -import com.google.firebase.ai.type.toImagenInlineImage -import com.google.firebase.quickstart.ai.ui.ImagenUiState -import kotlinx.serialization.Serializable - -@Serializable -object ImagenSubjectReferenceRoute - -@OptIn(PublicPreviewAPI::class) -class ImagenSubjectReferenceViewModel : ImagenViewModel() { - override val initialPrompt: String = " flying through space" - override val includeAttach: Boolean = true - override val selectionOptions: List = emptyList() - override val allowEmptyPrompt: Boolean = false - override val additionalImage: Bitmap? = null - override val imageLabels: List = emptyList() - - private val imagenModel: ImagenModel - - init { - val config = imagenGenerationConfig { - numberOfImages = 4 - imageFormat = ImagenImageFormat.png() - } - val settings = ImagenSafetySettings( - safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, - personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL - ) - imagenModel = Firebase.ai( - backend = GenerativeBackend.vertexAI() - ).imagenModel( - modelName = "imagen-3.0-capability-001", - generationConfig = config, - safetySettings = settings - ) - } - - override suspend fun performGeneration( - inputText: String, - currentState: ImagenUiState.Success - ): ImagenGenerationResponse { - val attachedImage = currentState.attachedImage!! - return imagenModel.editImage( - listOf( - ImagenSubjectReference( - referenceId = 1, - image = attachedImage.toImagenInlineImage(), - subjectType = ImagenSubjectReferenceType.ANIMAL, - description = "An animal" - ) - ), - "Create an image about An animal [1] to match the description: " + - inputText.replace("", "An animal [1]"), - ) - } -} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt deleted file mode 100644 index 2e882faa1..000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.google.firebase.quickstart.ai.feature.media.imagen - -import android.graphics.Bitmap -import com.google.firebase.Firebase -import com.google.firebase.ai.TemplateImagenModel -import com.google.firebase.ai.ai -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.ImagenGenerationResponse -import com.google.firebase.ai.type.ImagenInlineImage -import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.quickstart.ai.ui.ImagenUiState -import kotlinx.serialization.Serializable - -@Serializable -object ImagenTemplateRoute - -@OptIn(PublicPreviewAPI::class) -class ImagenTemplateViewModel : ImagenViewModel() { - override val initialPrompt: String = "List of things that should be in the image" - override val includeAttach: Boolean = false - override val selectionOptions: List = emptyList() - override val allowEmptyPrompt: Boolean = false - override val additionalImage: Bitmap? = null - override val imageLabels: List = emptyList() - - private var templateImagenModel: TemplateImagenModel - - init { - templateImagenModel = Firebase.ai( - backend = GenerativeBackend.googleAI() - ).templateImagenModel() - } - - override suspend fun performGeneration( - inputText: String, - currentState: ImagenUiState.Success - ): ImagenGenerationResponse { - return try { - templateImagenModel.generateImages("imagen-basic", mapOf("prompt" to inputText)) - } catch (e: Exception) { - if (e.localizedMessage?.contains("not found") == true) { - throw Exception( - """ - Template was not found, please verify that your project contains a template named "imagen-basic". - """.trimIndent() - ) - } else { - throw e - } - } - } -} 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 deleted file mode 100644 index a10d91e33..000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.google.firebase.quickstart.ai.feature.media.imagen - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import androidx.core.graphics.scale -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.google.firebase.ai.type.ImagenGenerationResponse -import com.google.firebase.ai.type.ImagenInlineImage -import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.quickstart.ai.ui.ImagenUiState -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch - -@OptIn(PublicPreviewAPI::class) -abstract class ImagenViewModel : ViewModel() { - - abstract val initialPrompt: String - abstract val includeAttach: Boolean - abstract val selectionOptions: List - abstract val allowEmptyPrompt: Boolean - abstract val additionalImage: Bitmap? - abstract val imageLabels: List - - private val _uiState = MutableStateFlow(ImagenUiState.Success()) - val uiState: StateFlow = _uiState.asStateFlow() - - protected abstract suspend fun performGeneration( - inputText: String, - currentState: ImagenUiState.Success - ): ImagenGenerationResponse - - fun generateImages(inputText: String) { - val currentState = (_uiState.value as? ImagenUiState.Success) ?: ImagenUiState.Success() - - viewModelScope.launch { - _uiState.value = ImagenUiState.Loading - try { - val imageResponse = performGeneration(inputText, currentState) - _uiState.value = currentState.copy(images = imageResponse.images.map { it.asBitmap() }) - } catch (e: Exception) { - _uiState.value = ImagenUiState.Error(e.localizedMessage ?: "Unknown error") - } - } - } - - suspend fun attachImage( - fileInBytes: ByteArray, - ) { - val originalBitmap = BitmapFactory.decodeByteArray(fileInBytes, 0, fileInBytes.size) - val resizedBitmap = originalBitmap.scale( - 512, - (originalBitmap.height * (512.0 / originalBitmap.width)).toInt() - ) - val currentState = (_uiState.value as? ImagenUiState.Success) ?: ImagenUiState.Success() - _uiState.value = currentState.copy(attachedImage = resizedBitmap) - } - - fun selectOption(selection: String) { - val currentState = (_uiState.value as? ImagenUiState.Success) ?: ImagenUiState.Success() - _uiState.value = currentState.copy(selectedOption = selection) - } -} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenScreen.kt deleted file mode 100644 index 418d2a5ce..000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenScreen.kt +++ /dev/null @@ -1,254 +0,0 @@ -package com.google.firebase.quickstart.ai.ui - -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 com.google.firebase.quickstart.ai.R -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenViewModel -import kotlinx.coroutines.launch - - -@Composable -fun ImagenScreen( - imagenViewModel: ImagenViewModel -) { - val uiState by imagenViewModel.uiState.collectAsStateWithLifecycle() - val successState = uiState as? ImagenUiState.Success - val attachedImage = successState?.attachedImage - val generatedImages = successState?.images ?: emptyList() - - var imagenPrompt by rememberSaveable { mutableStateOf(imagenViewModel.initialPrompt) } - - val context = LocalContext.current - val contentResolver = context.contentResolver - val scope = rememberCoroutineScope() - val openDocument = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { optionalUri: Uri? -> - optionalUri?.let { uri -> - var fileName: String? = null - // Fetch file name and size - contentResolver.query(uri, null, null, null, null)?.use { cursor -> - val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) - val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE) - cursor.moveToFirst() - val humanReadableSize = Formatter.formatShortFileSize( - context, cursor.getLong(sizeIndex) - ) - fileName = "${cursor.getString(nameIndex)} ($humanReadableSize)" - } - - contentResolver.openInputStream(uri)?.use { stream -> - val bytes = stream.readBytes() - scope.launch { - imagenViewModel.attachImage(bytes) - } - } - } - } - - Column( - modifier = Modifier.verticalScroll(rememberScrollState()) - ) { - ElevatedCard( - modifier = Modifier - .padding(all = 16.dp) - .fillMaxWidth(), - shape = MaterialTheme.shapes.large - ) { - OutlinedTextField( - value = imagenPrompt, - label = { Text("Prompt") }, - placeholder = { Text("Enter text to generate image") }, - onValueChange = { imagenPrompt = it }, - modifier = Modifier - .padding(16.dp) - .fillMaxWidth() - ) - if (imagenViewModel.selectionOptions.isNotEmpty()) { - DropDownMenu(imagenViewModel.selectionOptions) { imagenViewModel.selectOption(it) } - } - val attachmentsList = buildList { - if (imagenViewModel.additionalImage != null) { - add( - Attachment( - imagenViewModel.imageLabels.getOrElse(0) { "" }, - imagenViewModel.additionalImage - ) - ) - } - if (attachedImage != null) { - add(Attachment(imagenViewModel.imageLabels.getOrElse(1) { "" }, attachedImage)) - } - } - - if (imagenViewModel.includeAttach && attachmentsList.isNotEmpty()) { - AttachmentsList(attachmentsList) - } - Row() { - if (imagenViewModel.includeAttach) { - TextButton( - onClick = { - openDocument.launch(arrayOf("image/*")) - }, - modifier = Modifier - .padding(end = 16.dp, bottom = 16.dp) - ) { Text("Attach") } - } - TextButton( - onClick = { - if (imagenViewModel.allowEmptyPrompt || imagenPrompt.isNotBlank()) { - imagenViewModel.generateImages(imagenPrompt) - } - }, - modifier = Modifier - .padding(end = 16.dp, bottom = 16.dp) - ) { - Text("Generate") - } - } - - } - - if (uiState is ImagenUiState.Loading) { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .padding(all = 8.dp) - .align(Alignment.CenterHorizontally) - ) { - CircularProgressIndicator() - } - } - (uiState as? ImagenUiState.Error)?.let { - Card( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - shape = MaterialTheme.shapes.large, - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.errorContainer - ) - ) { - Text( - text = it.message, - color = MaterialTheme.colorScheme.error, - modifier = Modifier.padding(all = 16.dp) - ) - } - } - LazyHorizontalGrid( - rows = GridCells.Fixed(2), - modifier = Modifier - .padding(16.dp) - .height(500.dp) - ) { - items(generatedImages) { image -> - Card( - modifier = Modifier - .padding(8.dp) - .fillMaxWidth(), - shape = MaterialTheme.shapes.large, - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.onSecondaryContainer - ) - ) { - Image(bitmap = image.asImageBitmap(), "Generated image") - } - } - } - } -} - -@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/ui/ImagenUiState.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenUiState.kt deleted file mode 100644 index 907cabd13..000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenUiState.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.google.firebase.quickstart.ai.ui - -import android.graphics.Bitmap - -sealed interface ImagenUiState { - data object Idle : ImagenUiState - data object Loading : ImagenUiState - data class Success( - val images: List = emptyList(), - val attachedImage: Bitmap? = null, - val selectedOption: String? = null - ) : ImagenUiState - data class Error(val message: String) : ImagenUiState -} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt index a72196be2..af633b814 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt @@ -6,18 +6,6 @@ import com.google.firebase.quickstart.ai.feature.hybrid.HybridInferenceRoute import com.google.firebase.quickstart.ai.feature.hybrid.HybridInferenceViewModel import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeAudioRoute import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoRoute -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenGenerationRoute -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenGenerationViewModel -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenInpaintingRoute -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenInpaintingViewModel -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenOutpaintingRoute -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenOutpaintingViewModel -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenStyleTransferRoute -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenStyleTransferViewModel -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenSubjectReferenceRoute -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenSubjectReferenceViewModel -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenTemplateRoute -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenTemplateViewModel import com.google.firebase.quickstart.ai.feature.text.AudioSummarizationRoute import com.google.firebase.quickstart.ai.feature.text.AudioSummarizationViewModel import com.google.firebase.quickstart.ai.feature.text.AudioTranslationRoute @@ -99,46 +87,6 @@ val FIREBASE_AI_SAMPLES = listOf( viewModelClass = ImageBlogCreatorViewModel::class, categories = listOf(Category.IMAGE) ), - Sample( - title = "Imagen 4 - image generation", - description = "Generate images using Imagen 4", - route = ImagenGenerationRoute, - screenType = ScreenType.IMAGEN, - viewModelClass = ImagenGenerationViewModel::class, - categories = listOf(Category.IMAGE) - ), - Sample( - title = "Imagen 3 - Inpainting (Vertex AI)", - description = "Replace part of an image using Imagen 3", - route = ImagenInpaintingRoute, - screenType = ScreenType.IMAGEN, - viewModelClass = ImagenInpaintingViewModel::class, - categories = listOf(Category.IMAGE) - ), - Sample( - title = "Imagen 3 - Outpainting (Vertex AI)", - description = "Expand an image by drawing in more background", - route = ImagenOutpaintingRoute, - screenType = ScreenType.IMAGEN, - viewModelClass = ImagenOutpaintingViewModel::class, - categories = listOf(Category.IMAGE) - ), - Sample( - title = "Imagen 3 - Subject Reference (Vertex AI)", - description = "Generate an image using a referenced subject (must be an animal)", - route = ImagenSubjectReferenceRoute, - screenType = ScreenType.IMAGEN, - viewModelClass = ImagenSubjectReferenceViewModel::class, - categories = listOf(Category.IMAGE) - ), - Sample( - title = "Imagen 3 - Style Transfer (Vertex AI)", - description = "Change the art style of a cat picture using a reference", - route = ImagenStyleTransferRoute, - screenType = ScreenType.IMAGEN, - viewModelClass = ImagenStyleTransferViewModel::class, - categories = listOf(Category.IMAGE) - ), Sample( title = "Gemini 2.5 Flash Image (aka nanobanana)", description = "Generate and/or edit images using Gemini 2.5 Flash Image aka nanobanana", @@ -208,15 +156,6 @@ val FIREBASE_AI_SAMPLES = listOf( viewModelClass = GoogleSearchGroundingViewModel::class, categories = listOf(Category.TEXT) ), - Sample( - title = "Server Prompt Template - Imagen", - 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.", - route = ImagenTemplateRoute, - screenType = ScreenType.IMAGEN, - viewModelClass = ImagenTemplateViewModel::class, - categories = listOf(Category.IMAGE) - ), Sample( title = "Server Prompt Templates - Gemini", description = "Generate an invoice using server prompt templates. Note that you need to setup the template" + 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 a51b56315..af631a417 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 @@ -18,7 +18,6 @@ enum class Category( enum class ScreenType { CHAT, - IMAGEN, SVG, SERVER_PROMPT, BIDI,