diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 475b410..0b33e97 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -227,6 +227,10 @@ dependencies { // About implementation(libs.about.lib.core) implementation(libs.about.lib.compose.ui) + + // AI + implementation(libs.gemini) + implementation(libs.openai) } kapt { diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/data/ai/OutfitRecommendationRecommenderGemini.kt b/app/src/main/java/com/github/odaridavid/weatherapp/data/ai/OutfitRecommendationRecommenderGemini.kt new file mode 100644 index 0000000..b6572b6 --- /dev/null +++ b/app/src/main/java/com/github/odaridavid/weatherapp/data/ai/OutfitRecommendationRecommenderGemini.kt @@ -0,0 +1,26 @@ +package com.github.odaridavid.weatherapp.data.ai + +import com.github.odaridavid.weatherapp.BuildConfig +import com.google.ai.client.generativeai.GenerativeModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import javax.inject.Inject + +class OutfitRecommendationRecommenderGemini @Inject constructor() { + + private val outfitsModel by lazy { + GenerativeModel( + modelName = "gemini-1.0-pro", + apiKey = BuildConfig.VERTEX_AI_API_KEY + ) + } + + suspend fun generateOutfitRecommendation(temperature: String): Flow { + // TODO implement for language changes + val prompt = "What would be a nice outfit for $temperature weather?" + return flowOf(outfitsModel.generateContent(prompt).text) + } + + +} + diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/data/ai/OutfitRecommendationRecommenderOpenAI.kt b/app/src/main/java/com/github/odaridavid/weatherapp/data/ai/OutfitRecommendationRecommenderOpenAI.kt new file mode 100644 index 0000000..ee5af9f --- /dev/null +++ b/app/src/main/java/com/github/odaridavid/weatherapp/data/ai/OutfitRecommendationRecommenderOpenAI.kt @@ -0,0 +1,38 @@ +package com.github.odaridavid.weatherapp.data.ai + + +import com.aallam.openai.api.chat.ChatCompletionRequest +import com.aallam.openai.api.chat.ChatMessage +import com.aallam.openai.api.chat.ChatRole +import com.aallam.openai.api.model.ModelId +import com.aallam.openai.client.OpenAI +import com.github.odaridavid.weatherapp.BuildConfig +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import javax.inject.Inject + +class OutfitRecommendationRecommenderOpenAI @Inject constructor() { + + private val openAi by lazy { + OpenAI(token = BuildConfig.OPEN_AI_KEY) + } + + suspend fun generateOutfitRecommendation(temperature: String): Flow { + // TODO implement for language changes + val prompt = "What would be a nice outfit for $temperature weather?" + val chatCompletionRequest = ChatCompletionRequest( + model = ModelId("gpt-3.5-turbo"), + messages = listOf( + ChatMessage( + role = ChatRole.Assistant, + content = prompt + ) + ) + ) + val completion = openAi.chatCompletion(chatCompletionRequest) + + val response = completion.choices.first().message.content + return flowOf(response) + } + +} diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeScreen.kt b/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeScreen.kt index 012f76f..ccf1d09 100644 --- a/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeScreen.kt @@ -59,6 +59,15 @@ fun HomeScreen( if (state.errorMessageId != null) { ErrorScreen(state.errorMessageId, onTryAgainClicked) } else { + state.recommendation?.let { recommendation -> + MediumBody( + text = recommendation, + modifier = Modifier + .fillMaxWidth() + .padding(WeatherAppTheme.dimens.medium), + textAlign = TextAlign.Center + ) + } state.weather?.current?.let { currentWeather -> CurrentWeatherWidget(currentWeather = currentWeather) } ?: run { diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeViewModel.kt b/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeViewModel.kt index ed79788..004c899 100644 --- a/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.github.odaridavid.weatherapp.api.SettingsRepository import com.github.odaridavid.weatherapp.api.WeatherRepository +import com.github.odaridavid.weatherapp.data.ai.OutfitRecommendationRecommenderOpenAI import com.github.odaridavid.weatherapp.model.DefaultLocation import com.github.odaridavid.weatherapp.model.Result import com.github.odaridavid.weatherapp.model.SupportedLanguage @@ -14,6 +15,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import javax.inject.Inject @@ -21,7 +23,8 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( private val weatherRepository: WeatherRepository, - private val settingsRepository: SettingsRepository + private val settingsRepository: SettingsRepository, + private val outfitRecommendationRecommenderOpenAI: OutfitRecommendationRecommenderOpenAI, ) : ViewModel() { private val _state = MutableStateFlow(HomeScreenViewState(isLoading = true)) @@ -70,6 +73,19 @@ class HomeViewModel @Inject constructor( when (result) { is Result.Success -> { val weatherData = result.data + viewModelScope.launch { + // TODO Do this someplace else? + val temperature = weatherData.current?.temperature.toString() + outfitRecommendationRecommenderOpenAI + .generateOutfitRecommendation(temperature = temperature) + .catch { e -> + e.printStackTrace() + } + .collect { recommendation -> + setState { copy(recommendation = recommendation) } + } + + } setState { copy( weather = weatherData, @@ -104,5 +120,6 @@ data class HomeScreenViewState( val language: SupportedLanguage = SupportedLanguage.ENGLISH, val weather: Weather? = null, val isLoading: Boolean = false, + val recommendation: String? = null, @StringRes val errorMessageId: Int? = null ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5c33a1..86391c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,6 +39,8 @@ androidx-test-runner = "1.5.2" androidx-test-rules = "1.5.0" core-ktx = "1.5.0" coroutines = "1.8.0" +gemini = "0.2.2" +openai = "3.7.0" [libraries] activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } @@ -89,6 +91,8 @@ about-lib-compose-ui = { module = "com.mikepenz:aboutlibraries-compose-m3", vers android-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } android-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test-rules" } test-core-ktx = { group = "androidx.test", name = "core-ktx", version.ref = "core-ktx" } +gemini = { module = "com.google.ai.client.generativeai:generativeai", version.ref = "gemini" } +openai = { module = "com.aallam.openai:openai-client", version.ref = "openai" } [plugins] com-android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }