diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..222fddd5c --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,39 @@ +[versions] +agp = "8.4.0" +guava = "32.0.1-android" +kotlin = "1.9.23" +coreKtx = "1.13.1" +junit = "4.13.2" +junitVersion = "1.1.5" +espressoCore = "3.5.1" +lifecycleRuntimeKtx = "2.7.0" +activityCompose = "1.9.0" +composeBom = "2024.05.00" +reactiveStreams = "1.0.4" +vertexAI = "16.0.0-alpha03" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } + +# Vertex AI SDKs in Firebase +firebase-vertex-ai ={ group = "com.google.firebase", name = "firebase-vertexai", version.ref = "vertexAI" } +reactive-streams = { module = "org.reactivestreams:reactive-streams", version.ref = "reactiveStreams" } + +[plugins] +androidApplication = { id = "com.android.application", version.ref = "agp" } +jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } + diff --git a/settings.gradle.kts b/settings.gradle.kts index 6ac3b56bd..0a024c635 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,6 +24,7 @@ include(":auth:app", ":perf:app", ":test-lab:app", ":analytics:app", - ":installations:app" + ":installations:app", + ":vertexai:app" ) diff --git a/vertexai/.gitignore b/vertexai/.gitignore new file mode 100644 index 000000000..aa724b770 --- /dev/null +++ b/vertexai/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/vertexai/app/.gitignore b/vertexai/app/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/vertexai/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/vertexai/app/build.gradle.kts b/vertexai/app/build.gradle.kts new file mode 100644 index 000000000..4b50b6a0f --- /dev/null +++ b/vertexai/app/build.gradle.kts @@ -0,0 +1,78 @@ +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.jetbrainsKotlinAndroid) +} + +android { + namespace = "com.google.firebase.example.vertexai" + compileSdk = 34 + + defaultConfig { + applicationId = "com.google.firebase.example.vertexai" + minSdk = 21 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.13" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + + implementation(libs.firebase.vertex.ai) + + // Required for one-shot operations (to use `ListenableFuture` from Reactive Streams) + implementation(libs.reactive.streams) + + // Required for streaming operations (to use `Publisher` from Guava Android) + implementation(libs.guava) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} \ No newline at end of file diff --git a/vertexai/app/proguard-rules.pro b/vertexai/app/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/vertexai/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/vertexai/app/src/androidTest/java/com/google/firebase/example/vertexai/ExampleInstrumentedTest.kt b/vertexai/app/src/androidTest/java/com/google/firebase/example/vertexai/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..c404b6bbe --- /dev/null +++ b/vertexai/app/src/androidTest/java/com/google/firebase/example/vertexai/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.google.firebase.example.vertexai + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.assertEquals + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.google.firebase.example.vertexai", appContext.packageName) + } +} \ No newline at end of file diff --git a/vertexai/app/src/main/AndroidManifest.xml b/vertexai/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..c8970bf08 --- /dev/null +++ b/vertexai/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/MainActivity.kt b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/MainActivity.kt new file mode 100644 index 000000000..357ad8259 --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/MainActivity.kt @@ -0,0 +1,46 @@ +package com.google.firebase.example.vertexai + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.google.firebase.example.vertexai.ui.theme.VertexAIInFirebaseTheme + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + VertexAIInFirebaseTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + Greeting("Android") + } + } + } + } +} + +@Composable +fun Greeting(name: String, modifier: Modifier = Modifier) { + Text( + text = "Hello $name!", + modifier = modifier + ) +} + +@Preview(showBackground = true) +@Composable +fun GreetingPreview() { + VertexAIInFirebaseTheme { + Greeting("Android") + } +} \ No newline at end of file diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/ChatViewModel.java b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/ChatViewModel.java new file mode 100644 index 000000000..b5e15c798 --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/ChatViewModel.java @@ -0,0 +1,179 @@ +package com.google.firebase.example.vertexai.java; + +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModel; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.firebase.vertexai.Chat; +import com.google.firebase.vertexai.FirebaseVertexAI; +import com.google.firebase.vertexai.GenerativeModel; +import com.google.firebase.vertexai.java.ChatFutures; +import com.google.firebase.vertexai.java.GenerativeModelFutures; +import com.google.firebase.vertexai.type.Content; +import com.google.firebase.vertexai.type.CountTokensResponse; +import com.google.firebase.vertexai.type.GenerateContentResponse; +import com.google.firebase.vertexai.type.RequestOptions; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; + +public class ChatViewModel extends ViewModel { + private GenerativeModelFutures model; + + void startChatSendMessageStream() { + // [START vertexai_send_message_stream] + // (optional) Create previous chat history for context + Content.Builder userContentBuilder = new Content.Builder(); + userContentBuilder.setRole("user"); + userContentBuilder.addText("Hello, I have 2 dogs in my house."); + Content userContent = userContentBuilder.build(); + + Content.Builder modelContentBuilder = new Content.Builder(); + modelContentBuilder.setRole("model"); + modelContentBuilder.addText("Great to meet you. What would you like to know?"); + Content modelContent = userContentBuilder.build(); + + List history = Arrays.asList(userContent, modelContent); + + // Initialize the chat + ChatFutures chat = model.startChat(history); + + // Create a new user message + Content.Builder messageBuilder = new Content.Builder(); + messageBuilder.setRole("user"); + messageBuilder.addText("How many paws are in my house?"); + + Content message = messageBuilder.build(); + + // Send the message + Publisher streamingResponse = + chat.sendMessageStream(message); + + final String[] fullResponse = {""}; + + streamingResponse.subscribe(new Subscriber() { + @Override + public void onNext(GenerateContentResponse generateContentResponse) { + String chunk = generateContentResponse.getText(); + fullResponse[0] += chunk; + } + + @Override + public void onComplete() { + System.out.println(fullResponse[0]); + } + + // ... other methods omitted for brevity + // [START_EXCLUDE] + @Override + public void onSubscribe(Subscription s) { + + } + + @Override + public void onError(Throwable t) { + + } + // [END_EXCLUDE] + }); + // [END vertexai_send_message_stream] + } + + void startChatSendMessage(Executor executor) { + // [START vertexai_send_message] + // (optional) Create previous chat history for context + Content.Builder userContentBuilder = new Content.Builder(); + userContentBuilder.setRole("user"); + userContentBuilder.addText("Hello, I have 2 dogs in my house."); + Content userContent = userContentBuilder.build(); + + Content.Builder modelContentBuilder = new Content.Builder(); + modelContentBuilder.setRole("model"); + modelContentBuilder.addText("Great to meet you. What would you like to know?"); + Content modelContent = userContentBuilder.build(); + + List history = Arrays.asList(userContent, modelContent); + + // Initialize the chat + ChatFutures chat = model.startChat(history); + + // Create a new user message + Content.Builder messageBuilder = new Content.Builder(); + messageBuilder.setRole("user"); + messageBuilder.addText("How many paws are in my house?"); + + Content message = messageBuilder.build(); + + // Send the message + ListenableFuture response = chat.sendMessage(message); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String resultText = result.getText(); + System.out.println(resultText); + } + + @Override + public void onFailure(@NonNull Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END vertexai_send_message] + } + + void countTokensChat(Executor executor) { + ChatFutures chat = model.startChat(); + // [START vertexai_count_tokens_chat] + List history = chat.getChat().getHistory(); + + Content messageContent = new Content.Builder() + .addText("This is the message I intend to send") + .build(); + + Collections.addAll(history, messageContent); + + ListenableFuture countTokensResponse = + model.countTokens(history.toArray(new Content[0])); + Futures.addCallback(countTokensResponse, new FutureCallback<>() { + @Override + public void onSuccess(CountTokensResponse result) { + int totalTokens = result.getTotalTokens(); + int totalBillableTokens = result.getTotalBillableCharacters(); + System.out.println("totalTokens = " + totalTokens + + "totalBillableTokens = " + totalBillableTokens); + } + + @Override + public void onFailure(@NonNull Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END vertexai_count_tokens_chat] + } + + void systemInstructionsText() { + // [START vertexai_si_text] + Content systemInstruction = new Content.Builder() + .addText("You are a cat. Your name is Neko.") + .build(); + GenerativeModel model = FirebaseVertexAI.getInstance() + .generativeModel( + /* modelName */ "gemini-1.5-pro-preview-0409", + /* generationConfig (optional) */ null, + /* safetySettings (optional) */ null, + /* requestOptions (optional) */ new RequestOptions(), + /* tools (optional) */ null, + /* toolsConfig (optional) */ null, + /* systemInstruction (optional) */ systemInstruction + ); + // [END vertexai_si_text] + } +} diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/ConfigurationViewModel.java b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/ConfigurationViewModel.java new file mode 100644 index 000000000..8d393b2fe --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/ConfigurationViewModel.java @@ -0,0 +1,68 @@ +package com.google.firebase.example.vertexai.java; + +import androidx.lifecycle.ViewModel; + +import com.google.firebase.vertexai.FirebaseVertexAI; +import com.google.firebase.vertexai.GenerativeModel; +import com.google.firebase.vertexai.java.GenerativeModelFutures; +import com.google.firebase.vertexai.type.BlockThreshold; +import com.google.firebase.vertexai.type.GenerationConfig; +import com.google.firebase.vertexai.type.HarmCategory; +import com.google.firebase.vertexai.type.RequestOptions; +import com.google.firebase.vertexai.type.SafetySetting; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class ConfigurationViewModel extends ViewModel { + + void configModelParams() { + // [START vertexai_model_params] + GenerationConfig.Builder configBuilder = new GenerationConfig.Builder(); + configBuilder.temperature = 0.9f; + configBuilder.topK = 16; + configBuilder.topP = 0.1f; + configBuilder.maxOutputTokens = 200; + configBuilder.stopSequences = List.of("red"); + + GenerationConfig generationConfig = configBuilder.build(); + + GenerativeModel gm = FirebaseVertexAI.Companion.getInstance().generativeModel( + "MODEL_NAME", + generationConfig + ); + + GenerativeModelFutures model = GenerativeModelFutures.from(gm); + // [END vertexai_model_params] + } + + void configSafetySettings() { + SafetySetting harassmentSafety1 = new SafetySetting(HarmCategory.HARASSMENT, + BlockThreshold.ONLY_HIGH); + + GenerativeModel gm1 = FirebaseVertexAI.Companion.getInstance().generativeModel( + "MODEL_NAME", + /* generationConfig is optional */ null, + Collections.singletonList(harassmentSafety1) + ); + + GenerativeModelFutures model1 = GenerativeModelFutures.from(gm1); + + // [START vertexai_safety_settings] + SafetySetting harassmentSafety = new SafetySetting(HarmCategory.HARASSMENT, + BlockThreshold.ONLY_HIGH); + + SafetySetting hateSpeechSafety = new SafetySetting(HarmCategory.HATE_SPEECH, + BlockThreshold.MEDIUM_AND_ABOVE); + + GenerativeModel gm = FirebaseVertexAI.Companion.getInstance().generativeModel( + "MODEL_NAME", + /* generationConfig is optional */ null, + List.of(harassmentSafety, hateSpeechSafety) + ); + + GenerativeModelFutures model = GenerativeModelFutures.from(gm); + // [END vertexai_safety_settings] + } +} diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/FunctionCallViewModel.java b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/FunctionCallViewModel.java new file mode 100644 index 000000000..5719033d6 --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/FunctionCallViewModel.java @@ -0,0 +1,26 @@ +package com.google.firebase.example.vertexai.java; + +import androidx.lifecycle.ViewModel; + +public class FunctionCallViewModel extends ViewModel { + + // [START vertexai_fc_create_function] + // Function calling isn't yet supported for Java on Android. + // But it's coming soon! + // [END vertexai_fc_create_function] + + // [START vertexai_fc_func_declaration] + // Function calling isn't yet supported for Java on Android. + // But it's coming soon! + // [END vertexai_fc_func_declaration] + + // [START vertexai_fc_init] + // Function calling isn't yet supported for Java on Android. + // But it's coming soon! + // [END vertexai_fc_init] + + // [START vertexai_fc_generate] + // Function calling isn't yet supported for Java on Android. + // But it's coming soon! + // [END vertexai_fc_generate] +} diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/GenerateContentViewModel.java b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/GenerateContentViewModel.java new file mode 100644 index 000000000..e5f0f15fa --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/java/GenerateContentViewModel.java @@ -0,0 +1,391 @@ +package com.google.firebase.example.vertexai.java; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; + +import androidx.lifecycle.ViewModel; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.firebase.example.vertexai.R; +import com.google.firebase.vertexai.FirebaseVertexAI; +import com.google.firebase.vertexai.GenerativeModel; +import com.google.firebase.vertexai.java.GenerativeModelFutures; +import com.google.firebase.vertexai.type.Content; +import com.google.firebase.vertexai.type.CountTokensResponse; +import com.google.firebase.vertexai.type.GenerateContentResponse; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.Executor; + +public class GenerateContentViewModel extends ViewModel { + private GenerativeModelFutures model; + + // Only meant to separate the scope of the initialization snippet + // so that it doesn't cause a naming clash with the top level declaration + static class InitializationSnippet { + // [START vertexai_init] + GenerativeModel gm = FirebaseVertexAI.getInstance() + .generativeModel("gemini-1.5-pro-preview-0409"); + + GenerativeModelFutures model = GenerativeModelFutures.from(gm); + // [END vertexai_init] + } + + void generateContentStream() { + // [START vertexai_textonly_stream] + Content prompt = new Content.Builder() + .addText("Write a story about a magic backpack.") + .build(); + + Publisher streamingResponse = + model.generateContentStream(prompt); + + final String[] fullResponse = {""}; + + streamingResponse.subscribe(new Subscriber() { + @Override + public void onNext(GenerateContentResponse generateContentResponse) { + String chunk = generateContentResponse.getText(); + fullResponse[0] += chunk; + } + + @Override + public void onComplete() { + System.out.println(fullResponse[0]); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + } + + @Override + public void onSubscribe(Subscription s) { + } + }); + // [END vertexai_textonly_stream] + } + + void generateContent(Executor executor) { + // [START vertexai_textonly] + // Provide a prompt that contains text + Content prompt = new Content.Builder() + .addText("Write a story about a magic backpack.") + .build(); + + // To generate text output, call generateContent with the text input + ListenableFuture response = model.generateContent(prompt); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String resultText = result.getText(); + System.out.println(resultText); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END vertexai_textonly] + } + + // Fake implementation to exemplify Activity.getResources() + Resources getResources() { + return null; + } + + // Fake implementation to exemplify Activity.getApplicationContext() + Context getApplicationContext() { + return null; + } + + void generateContentWithImageStream() { + // [START vertexai_text_and_image_stream] + Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sparky); + + Content prompt = new Content.Builder() + .addImage(bitmap) + .addText("Write a story about a magic backpack.") + .build(); + + Publisher streamingResponse = + model.generateContentStream(prompt); + + final String[] fullResponse = {""}; + + streamingResponse.subscribe(new Subscriber() { + @Override + public void onNext(GenerateContentResponse generateContentResponse) { + String chunk = generateContentResponse.getText(); + fullResponse[0] += chunk; + } + + @Override + public void onComplete() { + System.out.println(fullResponse[0]); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + } + + @Override + public void onSubscribe(Subscription s) { + } + }); + // [END vertexai_text_and_image_stream] + } + + void generateContentWithImage(Executor executor) { + // [START vertexai_text_and_image] + Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sparky); + + Content content = new Content.Builder() + .addImage(bitmap) + .addText("What developer tool is this mascot from?") + .build(); + + ListenableFuture response = model.generateContent(content); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String resultText = result.getText(); + System.out.println(resultText); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END vertexai_text_and_image] + } + + void generateContentWithMultipleImagesStream() { + // [START vertexai_text_and_images_stream] + Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.sparky); + Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.sparky_eats_pizza); + + // Provide a prompt that includes the images specifed above and text + Content prompt = new Content.Builder() + .addImage(bitmap1) + .addImage(bitmap2) + .addText("What's different between these pictures?") + .build(); + + Publisher streamingResponse = + model.generateContentStream(prompt); + + final String[] fullResponse = {""}; + + streamingResponse.subscribe(new Subscriber() { + @Override + public void onNext(GenerateContentResponse generateContentResponse) { + String chunk = generateContentResponse.getText(); + fullResponse[0] += chunk; + } + + @Override + public void onComplete() { + System.out.println(fullResponse[0]); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + } + + @Override + public void onSubscribe(Subscription s) { + } + }); + // [END vertexai_text_and_images_stream] + } + + void generateContentWithMultipleImages(Executor executor) { + // [START vertexai_text_and_images] + Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.sparky); + Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.sparky_eats_pizza); + + // Provide a prompt that includes the images specifed above and text + Content prompt = new Content.Builder() + .addImage(bitmap1) + .addImage(bitmap2) + .addText("What's different between these pictures?") + .build(); + + ListenableFuture response = model.generateContent(prompt); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String resultText = result.getText(); + System.out.println(resultText); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END vertexai_text_and_images] + } + + void generateContentWithVideo(Executor executor, Uri videoUri) { + // [START vertexai_text_and_video] + ContentResolver resolver = getApplicationContext().getContentResolver(); + try (InputStream stream = resolver.openInputStream(videoUri)) { + File videoFile = new File(new URI(videoUri.toString())); + int videoSize = (int) videoFile.length(); + byte[] videoBytes = new byte[videoSize]; + if (stream != null) { + stream.read(videoBytes, 0, videoBytes.length); + stream.close(); + + Content prompt = new Content.Builder() + .addBlob("video/mp4", videoBytes) + .addText("What is in the video?") + .build(); + + ListenableFuture response = model.generateContent(prompt); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String resultText = result.getText(); + System.out.println(resultText); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + // [END vertexai_text_and_video] + } + + void generateContentWithVideoStream( + Uri videoUri + ) { + // [START vertexai_text_and_video_stream] + ContentResolver resolver = getApplicationContext().getContentResolver(); + try (InputStream stream = resolver.openInputStream(videoUri)) { + File videoFile = new File(new URI(videoUri.toString())); + int videoSize = (int) videoFile.length(); + byte[] videoBytes = new byte[videoSize]; + if (stream != null) { + stream.read(videoBytes, 0, videoBytes.length); + stream.close(); + + Content prompt = new Content.Builder() + .addBlob("video/mp4", videoBytes) + .addText("What is in the video?") + .build(); + + Publisher streamingResponse = + model.generateContentStream(prompt); + + final String[] fullResponse = {""}; + + streamingResponse.subscribe(new Subscriber() { + @Override + public void onNext(GenerateContentResponse generateContentResponse) { + String chunk = generateContentResponse.getText(); + fullResponse[0] += chunk; + } + + @Override + public void onComplete() { + System.out.println(fullResponse[0]); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + } + + @Override + public void onSubscribe(Subscription s) { + } + }); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + // [END vertexai_text_and_video_stream] + } + + void countTokensText(Executor executor) { + // [START vertexai_count_tokens_text] + Content text = new Content.Builder() + .addText("Write a story about a magic backpack.") + .build(); + + ListenableFuture countTokensResponse = model.countTokens(text); + + Futures.addCallback(countTokensResponse, new FutureCallback() { + @Override + public void onSuccess(CountTokensResponse result) { + int totalTokens = result.getTotalTokens(); + int totalBillableTokens = result.getTotalBillableCharacters(); + System.out.println("totalTokens = " + totalTokens + + "totalBillableTokens = " + totalBillableTokens); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END vertexai_count_tokens_text] + } + + void countTokensMultimodal(Executor executor, Bitmap bitmap) { + // [START vertexai_count_tokens_multimodal] + Content text = new Content.Builder() + .addImage(bitmap) + .addText("Where can I buy this") + .build(); + + // For text-only input + ListenableFuture countTokensResponse = model.countTokens(text); + + Futures.addCallback(countTokensResponse, new FutureCallback() { + @Override + public void onSuccess(CountTokensResponse result) { + int totalTokens = result.getTotalTokens(); + int totalBillableTokens = result.getTotalBillableCharacters(); + System.out.println("totalTokens = " + totalTokens + + "totalBillableTokens = " + totalBillableTokens); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END vertexai_count_tokens_multimodal] + } +} diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/ChatViewModel.kt b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/ChatViewModel.kt new file mode 100644 index 000000000..abe65c958 --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/ChatViewModel.kt @@ -0,0 +1,75 @@ +package com.google.firebase.example.vertexai.kotlin + +import android.content.res.Resources +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.Firebase +import com.google.firebase.vertexai.GenerativeModel +import com.google.firebase.vertexai.type.content +import com.google.firebase.vertexai.vertexAI +import kotlinx.coroutines.launch + +@Suppress("JoinDeclarationAndAssignment") // for the generativeModel var +class ChatViewModel : ViewModel() { + private val TAG = "ChatViewModel" + private var generativeModel: GenerativeModel + + init { + generativeModel = Firebase.vertexAI.generativeModel("gemini-1.5-pro-preview-0409") + } + + fun startChatSendMessageStream() { + viewModelScope.launch { + // [START vertexai_send_message_stream] + val chat = generativeModel.startChat( + history = listOf( + content(role = "user") { text("Hello, I have 2 dogs in my house.") }, + content(role = "model") { text("Great to meet you. What would you like to know?") } + ) + ) + + chat.sendMessageStream("How many paws are in my house?").collect { chunk -> + Log.d(TAG, chunk.text ?: "") + } + // [END vertexai_send_message_stream] + } + } + + fun startChatSendMessage() { + viewModelScope.launch { + // [START vertexai_send_message] + val chat = generativeModel.startChat( + history = listOf( + content(role = "user") { text("Hello, I have 2 dogs in my house.") }, + content(role = "model") { text("Great to meet you. What would you like to know?") } + ) + ) + + val response = chat.sendMessage("How many paws are in my house?") + Log.d(TAG, response.text ?: "") + // [END vertexai_send_message] + } + } + + fun countTokensChat() { + viewModelScope.launch { + val chat = generativeModel.startChat() + // [START vertexai_count_tokens_chat] + // Count tokens for a chat prompt + val history = chat.history + val messageContent = content { text("This is the message I intend to send") } + val (tokens, billableChars) = generativeModel.countTokens(*history.toTypedArray(), messageContent) + // [END vertexai_count_tokens_chat] + } + } + + fun systemInstructionsText() { + // [START vertexai_si_text] + val generativeModel = Firebase.vertexAI.generativeModel( + modelName = "gemini-1.5-pro-preview-0409", + systemInstruction = content { text("You are a cat. Your name is Neko.") }, + ) + // [END vertexai_si_text] + } +} \ No newline at end of file diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/ConfigurationViewModel.kt b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/ConfigurationViewModel.kt new file mode 100644 index 000000000..7476c9d79 --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/ConfigurationViewModel.kt @@ -0,0 +1,47 @@ +package com.google.firebase.example.vertexai.kotlin + +import androidx.lifecycle.ViewModel +import com.google.firebase.Firebase +import com.google.firebase.vertexai.type.BlockThreshold +import com.google.firebase.vertexai.type.HarmCategory +import com.google.firebase.vertexai.type.SafetySetting +import com.google.firebase.vertexai.type.generationConfig +import com.google.firebase.vertexai.vertexAI + +class ConfigurationViewModel : ViewModel() { + + fun configModelParams() { + // [START vertexai_model_params] + val config = generationConfig { + temperature = 0.9f + topK = 16 + topP = 0.1f + maxOutputTokens = 200 + stopSequences = listOf("red") + } + val generativeModel = Firebase.vertexAI.generativeModel( + modelName = "gemini-1.5-pro-preview-0409", + generationConfig = config + ) + // [END vertexai_model_params] + } + + fun configSafetySettings() { + val generativeModel1 = Firebase.vertexAI.generativeModel( + modelName = "MODEL_NAME", + safetySettings = listOf( + SafetySetting(HarmCategory.HARASSMENT, BlockThreshold.ONLY_HIGH) + ) + ) + + // [START vertexai_safety_settings] + val harassmentSafety = SafetySetting(HarmCategory.HARASSMENT, BlockThreshold.ONLY_HIGH) + val hateSpeechSafety = SafetySetting(HarmCategory.HATE_SPEECH, BlockThreshold.MEDIUM_AND_ABOVE) + + val generativeModel = Firebase.vertexAI.generativeModel( + modelName = "MODEL_NAME", + safetySettings = listOf(harassmentSafety, hateSpeechSafety) + ) + // [END vertexai_safety_settings] + } +} \ No newline at end of file diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/FunctionCallViewModel.kt b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/FunctionCallViewModel.kt new file mode 100644 index 000000000..5dac6234e --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/FunctionCallViewModel.kt @@ -0,0 +1,92 @@ +package com.google.firebase.example.vertexai.kotlin + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.Firebase +import com.google.firebase.vertexai.GenerativeModel +import com.google.firebase.vertexai.type.FunctionResponsePart +import com.google.firebase.vertexai.type.InvalidStateException +import com.google.firebase.vertexai.type.Schema +import com.google.firebase.vertexai.type.Tool +import com.google.firebase.vertexai.type.content +import com.google.firebase.vertexai.type.defineFunction +import com.google.firebase.vertexai.vertexAI +import kotlinx.coroutines.launch +import org.json.JSONObject + +class FunctionCallViewModel : ViewModel() { + + // [START vertexai_fc_create_function] + suspend fun makeApiRequest( + currencyFrom: String, + currencyTo: String + ): JSONObject { + // This hypothetical API returns a JSON such as: + // {"base":"USD","rates":{"SEK": 10.99}} + return JSONObject().apply { + put("base", currencyFrom) + put("rates", hashMapOf(currencyTo to 10.99)) + } + } + // [END vertexai_fc_create_function] + + // [START vertexai_fc_func_declaration] + val getExchangeRate = defineFunction( + name = "getExchangeRate", + description = "Get the exchange rate for currencies between countries", + Schema.str("currencyFrom", "The currency to convert from."), + Schema.str("currencyTo", "The currency to convert to.") + ) { from, to -> + // Call the function that you declared above + makeApiRequest(from, to) + } + // [END vertexai_fc_func_declaration] + + // [START vertexai_fc_init] + // Initialize the Vertex AI service and the generative model + // Use a model that supports function calling, like Gemini 1.0 Pro. + val generativeModel = Firebase.vertexAI.generativeModel( + modelName = "gemini-1.0-pro", + // Specify the function declaration. + tools = listOf(Tool(listOf(getExchangeRate))) + ) + // [END vertexai_fc_init] + + // [START vertexai_fc_generate] + fun generateFunctionCall() { + viewModelScope.launch { + val chat = generativeModel.startChat() + + val prompt = "How much is 50 US dollars worth in Swedish krona?" + + // Send the message to the generative model + var response = chat.sendMessage(prompt) + + // Check if the model responded with a function call + response.functionCall?.let { functionCall -> + // Try to retrieve the stored lambda from the model's tools and + // throw an exception if the returned function was not declared + val matchedFunction = generativeModel.tools?.flatMap { it.functionDeclarations } + ?.first { it.name == functionCall.name } + ?: throw InvalidStateException("Function not found: ${functionCall.name}") + + // Call the lambda retrieved above + val apiResponse: JSONObject = matchedFunction.execute(functionCall) + + // Send the API response back to the generative model + // so that it generates a text response that can be displayed to the user + response = chat.sendMessage( + content(role = "function") { + part(FunctionResponsePart(functionCall.name, apiResponse)) + } + ) + } + + // Whenever the model responds with text, show it in the UI + response.text?.let { modelResponse -> + println(modelResponse) + } + } + } + // [END vertexai_fc_generate] +} \ No newline at end of file diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/GenerateContentViewModel.kt b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/GenerateContentViewModel.kt new file mode 100644 index 000000000..adb613463 --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/kotlin/GenerateContentViewModel.kt @@ -0,0 +1,215 @@ +package com.google.firebase.example.vertexai.kotlin + +import android.content.Context +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.Firebase +import com.google.firebase.example.vertexai.R +import com.google.firebase.vertexai.GenerativeModel +import com.google.firebase.vertexai.type.content +import com.google.firebase.vertexai.vertexAI +import kotlinx.coroutines.launch + +@Suppress("JoinDeclarationAndAssignment") // for the generativeModel var +class GenerateContentViewModel : ViewModel() { + private val TAG = "ContentViewModel" + private var generativeModel: GenerativeModel + + // Only meant to separate the scope of the initialization snippet + // so that it doesn't cause a naming clash with the top level generativeModel + fun initialize() { + // [START vertexai_init] + val generativeModel = Firebase.vertexAI.generativeModel( + // Specify a model that supports your use case + // Gemini 1.5 Pro is versatile and can accept both text-only and multimodal prompt inputs + modelName = "gemini-1.5-pro-preview-0409" + ) + // [END vertexai_init] + } + + init { + generativeModel = Firebase.vertexAI.generativeModel("gemini-1.5-pro-preview-0409") + } + + fun generateContentStream() { + viewModelScope.launch { + // [START vertexai_textonly_stream] + // Provide a prompt that includes only text + val prompt = "Write a story about a magic backpack." + // To stream generated text output, call generateContentStream and pass in the prompt + var fullResponse = "" + generativeModel.generateContentStream(prompt).collect { chunk -> + Log.d(TAG, chunk.text ?: "") + fullResponse += chunk.text + } + // [END vertexai_textonly_stream] + } + } + + fun generateContent() { + viewModelScope.launch { + // [START vertexai_textonly] + // Provide a prompt that includes only text + val prompt = "Write a story about a magic backpack." + + // To generate text output, call generateContent and pass in the prompt + val response = generativeModel.generateContent(prompt) + Log.d(TAG, response.text ?: "") + // [END vertexai_textonly] + } + } + + fun generateContentWithImageStream(resources: Resources) { + viewModelScope.launch { + // [START vertexai_text_and_image_stream] + // Loads an image from the app/res/drawable/ directory + val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.sparky) + + val prompt = content { + image(bitmap) + text("What developer tool is this mascot from?") + } + + var fullResponse = "" + generativeModel.generateContentStream(prompt).collect { chunk -> + Log.d(TAG, chunk.text ?: "") + fullResponse += chunk.text + } + // [END vertexai_text_and_image_stream] + } + } + + fun generateContentWithImage(resources: Resources) { + viewModelScope.launch { + // [START vertexai_text_and_image] + // Loads an image from the app/res/drawable/ directory + val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.sparky) + + val prompt = content { + image(bitmap) + text("What developer tool is this mascot from?") + } + + val response = generativeModel.generateContent(prompt) + Log.d(TAG, response.text ?: "") + // [END vertexai_text_and_image] + } + } + + fun generateContentWithMultipleImagesStream(resources: Resources) { + viewModelScope.launch { + // [START vertexai_text_and_images_stream] + // Loads an image from the app/res/drawable/ directory + val bitmap1: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.sparky) + val bitmap2: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.sparky_eats_pizza) + + val prompt = content { + image(bitmap1) + image(bitmap2) + text("What is different between these pictures?") + } + + var fullResponse = "" + generativeModel.generateContentStream(prompt).collect { chunk -> + Log.d(TAG, chunk.text ?: "") + fullResponse += chunk.text + } + // [END vertexai_text_and_images_stream] + } + } + + fun generateContentWithMultipleImages(resources: Resources) { + viewModelScope.launch { + // [START vertexai_text_and_images] + // Loads an image from the app/res/drawable/ directory + val bitmap1: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.sparky) + val bitmap2: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.sparky_eats_pizza) + + val prompt = content { + image(bitmap1) + image(bitmap2) + text("What is different between these pictures?") + } + + val response = generativeModel.generateContent(prompt) + Log.d(TAG, response.text ?: "") + // [END vertexai_text_and_images] + } + } + + fun generateContentWithVideoStream( + applicationContext: Context, + videoUri: Uri + ) { + viewModelScope.launch { + // [START vertexai_text_and_video_stream] + val contentResolver = applicationContext.contentResolver + contentResolver.openInputStream(videoUri).use { stream -> + stream?.let { + val bytes = stream.readBytes() + + val prompt = content { + blob("video/mp4", bytes) + text("What is in the video?") + } + + var fullResponse = "" + generativeModel.generateContentStream(prompt).collect { chunk -> + Log.d(TAG, chunk.text ?: "") + fullResponse += chunk.text + } + } + } + // [END vertexai_text_and_video_stream] + } + } + + fun generateContentWithVideo( + applicationContext: Context, + videoUri: Uri + ) { + viewModelScope.launch { + // [START vertexai_text_and_video] + val contentResolver = applicationContext.contentResolver + contentResolver.openInputStream(videoUri).use { stream -> + stream?.let { + val bytes = stream.readBytes() + + val prompt = content { + blob("video/mp4", bytes) + text("What is in the video?") + } + + val response = generativeModel.generateContent(prompt) + Log.d(TAG, response.text ?: "") + } + } + // [END vertexai_text_and_video] + } + } + + fun countTokensText() { + viewModelScope.launch { + // [START vertexai_count_tokens_text] + val (tokens, billableChars) = generativeModel.countTokens("Write a story about a magic backpack.") + // [END vertexai_count_tokens_text] + } + } + + fun countTokensMultimodal(bitmap: Bitmap) { + viewModelScope.launch { + // [START vertexai_count_tokens_multimodal] + val prompt = content { + image(bitmap) + text("Where can I buy this?") + } + val (tokens, billableChars) = generativeModel.countTokens(prompt) + // [END vertexai_count_tokens_multimodal] + } + } +} \ No newline at end of file diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/ui/theme/Color.kt b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/ui/theme/Color.kt new file mode 100644 index 000000000..9888e94fd --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.google.firebase.example.vertexai.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/ui/theme/Theme.kt b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/ui/theme/Theme.kt new file mode 100644 index 000000000..b02d7202c --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/ui/theme/Theme.kt @@ -0,0 +1,70 @@ +package com.google.firebase.example.vertexai.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun VertexAIInFirebaseTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/vertexai/app/src/main/java/com/google/firebase/example/vertexai/ui/theme/Type.kt b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/ui/theme/Type.kt new file mode 100644 index 000000000..f0c66d39d --- /dev/null +++ b/vertexai/app/src/main/java/com/google/firebase/example/vertexai/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.google.firebase.example.vertexai.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/vertexai/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/vertexai/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..2b068d114 --- /dev/null +++ b/vertexai/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/vertexai/app/src/main/res/drawable/ic_launcher_background.xml b/vertexai/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..07d5da9cb --- /dev/null +++ b/vertexai/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vertexai/app/src/main/res/drawable/sparky.png b/vertexai/app/src/main/res/drawable/sparky.png new file mode 100644 index 000000000..8f95ac093 Binary files /dev/null and b/vertexai/app/src/main/res/drawable/sparky.png differ diff --git a/vertexai/app/src/main/res/drawable/sparky_eats_pizza.png b/vertexai/app/src/main/res/drawable/sparky_eats_pizza.png new file mode 100644 index 000000000..8f95ac093 Binary files /dev/null and b/vertexai/app/src/main/res/drawable/sparky_eats_pizza.png differ diff --git a/vertexai/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/vertexai/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..6f3b755bf --- /dev/null +++ b/vertexai/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/vertexai/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/vertexai/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..6f3b755bf --- /dev/null +++ b/vertexai/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/vertexai/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/vertexai/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 000000000..c209e78ec Binary files /dev/null and b/vertexai/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/vertexai/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/vertexai/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 000000000..b2dfe3d1b Binary files /dev/null and b/vertexai/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/vertexai/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/vertexai/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 000000000..4f0f1d64e Binary files /dev/null and b/vertexai/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/vertexai/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/vertexai/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 000000000..62b611da0 Binary files /dev/null and b/vertexai/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/vertexai/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/vertexai/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 000000000..948a3070f Binary files /dev/null and b/vertexai/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/vertexai/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/vertexai/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..1b9a6956b Binary files /dev/null and b/vertexai/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/vertexai/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/vertexai/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 000000000..28d4b77f9 Binary files /dev/null and b/vertexai/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/vertexai/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/vertexai/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..9287f5083 Binary files /dev/null and b/vertexai/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/vertexai/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/vertexai/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 000000000..aa7d6427e Binary files /dev/null and b/vertexai/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/vertexai/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/vertexai/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..9126ae37c Binary files /dev/null and b/vertexai/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/vertexai/app/src/main/res/values/colors.xml b/vertexai/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..f8c6127d3 --- /dev/null +++ b/vertexai/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/vertexai/app/src/main/res/values/strings.xml b/vertexai/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..9ef4a1c66 --- /dev/null +++ b/vertexai/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Vertex AI in Firebase + \ No newline at end of file diff --git a/vertexai/app/src/main/res/values/themes.xml b/vertexai/app/src/main/res/values/themes.xml new file mode 100644 index 000000000..3567a967d --- /dev/null +++ b/vertexai/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +