From 52fcf6896abb914ce383fa6380e4e9d1b967ff00 Mon Sep 17 00:00:00 2001 From: Renato Date: Wed, 8 Mar 2023 18:48:39 -0300 Subject: [PATCH 01/12] feat: Add ImageGenerations API --- .../co/yml/ychat/android/MainViewModel.kt | 21 ++++++++ .../yml/ychat/android/ui/SendMessageLayout.kt | 3 +- .../commonMain/kotlin/co/yml/ychat/YChat.kt | 3 ++ .../co/yml/ychat/data/api/ChatGptApi.kt | 4 ++ .../yml/ychat/data/api/impl/ChatGptApiImpl.kt | 10 ++++ .../yml/ychat/data/dto/ImageGenerationsDto.kt | 13 +++++ .../data/dto/ImageGenerationsParamsDto.kt | 18 +++++++ .../co/yml/ychat/di/module/LibraryModule.kt | 5 ++ .../domain/mapper/ImageGenerationsMapper.kt | 23 ++++++++ .../yml/ychat/domain/model/ImageGenerated.kt | 16 ++++++ .../domain/model/ImageGenerationsParams.kt | 9 ++++ .../usecases/ImageGenerationsUseCase.kt | 16 ++++++ .../entrypoint/features/ImageGenerations.kt | 20 +++++++ .../entrypoint/impl/ImageGenerationsImpl.kt | 52 +++++++++++++++++++ .../co/yml/ychat/entrypoint/impl/YChatImpl.kt | 5 ++ 15 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt create mode 100644 ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsParamsDto.kt create mode 100644 ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt create mode 100644 ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerated.kt create mode 100644 ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerationsParams.kt create mode 100644 ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt create mode 100644 ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt create mode 100644 ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt diff --git a/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt b/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt index 90daeda..0a0927b 100644 --- a/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt +++ b/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt @@ -1,11 +1,13 @@ package co.yml.ychat.android +import android.util.Log import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import co.yml.ychat.YChat +import co.yml.ychat.YChat.Callback import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -20,6 +22,11 @@ class MainViewModel(private val chatGpt: YChat) : ViewModel() { ) } + private val imageGenerations by lazy { + chatGpt.imageGenerations() + .setResults(2) + } + private val _items = mutableStateListOf() val items = _items @@ -41,6 +48,20 @@ class MainViewModel(private val chatGpt: YChat) : ViewModel() { } } + fun onImageRequest(prompt: String) { + viewModelScope.launch { + imageGenerations.execute(prompt, object: Callback> { + override fun onSuccess(result: List) { + Log.d("callback", "onSuccess: $result") + } + + override fun onError(throwable: Throwable) { + Log.d("callback", "onError: $throwable") + } + }) + } + } + private suspend fun showTypingAnimation(message: String) { items.add(MessageItem(message = message, isOut = true)) delay((1000..2000).random().toLong()) diff --git a/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt b/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt index f0d26ee..6b257b6 100644 --- a/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt +++ b/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt @@ -91,7 +91,8 @@ fun SendMessageLayout() { .background(if (textFieldState.isNotEmpty() && isLoading.not()) colorResource(id = R.color.softBlue) else colorResource(id = R.color.opaqueWhite)), onClick = { scope.launch { - viewModel.onSendMessage(textFieldState, typingString) + //viewModel.onSendMessage(textFieldState, typingString) + viewModel.onImageRequest(textFieldState) textFieldState = "" } }, diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt index 4bca17e..6c977b3 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt @@ -2,6 +2,7 @@ package co.yml.ychat import co.yml.ychat.entrypoint.features.ChatCompletions import co.yml.ychat.entrypoint.features.Completion +import co.yml.ychat.entrypoint.features.ImageGenerations import co.yml.ychat.entrypoint.impl.YChatImpl import kotlin.jvm.JvmStatic import kotlin.jvm.Volatile @@ -77,6 +78,8 @@ interface YChat { */ fun chatCompletions(): ChatCompletions + fun imageGenerations(): ImageGenerations + /** * Callback is an interface used for handling the results of an operation. * It provides two methods, `onSuccess` and `onError`, for handling the success diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/ChatGptApi.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/ChatGptApi.kt index 4996e4c..a7e3a9f 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/ChatGptApi.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/ChatGptApi.kt @@ -4,6 +4,8 @@ import co.yml.ychat.data.dto.ChatCompletionParamsDto import co.yml.ychat.data.dto.ChatCompletionsDto import co.yml.ychat.data.dto.CompletionDto import co.yml.ychat.data.dto.CompletionParamsDto +import co.yml.ychat.data.dto.ImageGenerationsDto +import co.yml.ychat.data.dto.ImageGenerationsParamsDto import co.yml.ychat.data.infrastructure.ApiResult internal interface ChatGptApi { @@ -11,4 +13,6 @@ internal interface ChatGptApi { suspend fun completion(paramsDto: CompletionParamsDto): ApiResult suspend fun chatCompletions(paramsDto: ChatCompletionParamsDto): ApiResult + + suspend fun imageGenerations(paramsDto: ImageGenerationsParamsDto): ApiResult } diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/impl/ChatGptApiImpl.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/impl/ChatGptApiImpl.kt index 1135ed1..791c8c2 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/impl/ChatGptApiImpl.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/impl/ChatGptApiImpl.kt @@ -5,6 +5,8 @@ import co.yml.ychat.data.dto.ChatCompletionParamsDto import co.yml.ychat.data.dto.ChatCompletionsDto import co.yml.ychat.data.dto.CompletionDto import co.yml.ychat.data.dto.CompletionParamsDto +import co.yml.ychat.data.dto.ImageGenerationsDto +import co.yml.ychat.data.dto.ImageGenerationsParamsDto import co.yml.ychat.data.infrastructure.ApiExecutor import co.yml.ychat.data.infrastructure.ApiResult import io.ktor.http.HttpMethod @@ -26,4 +28,12 @@ internal class ChatGptApiImpl(private val apiExecutor: ApiExecutor) : ChatGptApi .setBody(paramsDto) .execute() } + + override suspend fun imageGenerations(paramsDto: ImageGenerationsParamsDto): ApiResult { + return apiExecutor + .setEndpoint("v1/images/generations") + .setHttpMethod(HttpMethod.Post) + .setBody(paramsDto) + .execute() + } } diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt new file mode 100644 index 0000000..d02b87c --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt @@ -0,0 +1,13 @@ +package co.yml.ychat.data.dto + +import co.yml.ychat.domain.model.ImageGenerated +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +internal data class ImageGenerationsDto( + @SerialName("created") + val created: Long, + @SerialName("data") + val data: List, +) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsParamsDto.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsParamsDto.kt new file mode 100644 index 0000000..62ab7c3 --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsParamsDto.kt @@ -0,0 +1,18 @@ +package co.yml.ychat.data.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +internal data class ImageGenerationsParamsDto( + @SerialName("prompt") + val prompt: String, + @SerialName("n") + val results: Int, + @SerialName("size") + val size: String, + @SerialName("response_format") + val responseFormat: String, + @SerialName("user") + val user: String, +) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/di/module/LibraryModule.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/di/module/LibraryModule.kt index 4106d79..7f1b5b7 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/di/module/LibraryModule.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/di/module/LibraryModule.kt @@ -7,10 +7,13 @@ import co.yml.ychat.data.storage.ChatLogStorage import co.yml.ychat.di.provider.NetworkProvider import co.yml.ychat.domain.usecases.ChatCompletionsUseCase import co.yml.ychat.domain.usecases.CompletionUseCase +import co.yml.ychat.domain.usecases.ImageGenerationsUseCase import co.yml.ychat.entrypoint.features.ChatCompletions import co.yml.ychat.entrypoint.features.Completion +import co.yml.ychat.entrypoint.features.ImageGenerations import co.yml.ychat.entrypoint.impl.ChatCompletionsImpl import co.yml.ychat.entrypoint.impl.CompletionImpl +import co.yml.ychat.entrypoint.impl.ImageGenerationsImpl import kotlinx.coroutines.Dispatchers import org.koin.core.module.Module import org.koin.dsl.module @@ -23,11 +26,13 @@ internal class LibraryModule(private val apiKey: String) { private val entrypointModule = module { factory { CompletionImpl(Dispatchers.Default, get()) } factory { ChatCompletionsImpl(Dispatchers.Default, get()) } + factory { ImageGenerationsImpl(Dispatchers.Default, get()) } } private val domainModule = module { factory { CompletionUseCase(get(), get()) } factory { ChatCompletionsUseCase(get()) } + factory { ImageGenerationsUseCase(get()) } } private val dataModule = module { diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt new file mode 100644 index 0000000..2db037a --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt @@ -0,0 +1,23 @@ +package co.yml.ychat.domain.mapper + +import co.yml.ychat.data.dto.ImageGenerationsDto +import co.yml.ychat.data.dto.ImageGenerationsParamsDto +import co.yml.ychat.domain.model.ImageGenerated +import co.yml.ychat.domain.model.ImageGenerationsParams + +internal fun ImageGenerationsDto.toImageGenerated(): List { + return this.data.map { + ImageGenerated(it.url) + } +} + + +internal fun ImageGenerationsParams.toImageGenerationsParamsDto(): ImageGenerationsParamsDto { + return ImageGenerationsParamsDto( + prompt = this.prompt, + results = this.results, + size = this.size, + responseFormat = this.responseFormat, + user = this.user + ) +} diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerated.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerated.kt new file mode 100644 index 0000000..439d332 --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerated.kt @@ -0,0 +1,16 @@ +package co.yml.ychat.domain.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents a message in a conversation, consisting of a [role] indicating the speaker + * (e.g., “system”, “user” or “assistant”), and the [content] of the message sent by the speaker. + * @property role The role of the speaker who sends the message. + * @property content The content of the message sent by the speaker. + */ +@Serializable +data class ImageGenerated( + @SerialName("url") + val url: String, +) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerationsParams.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerationsParams.kt new file mode 100644 index 0000000..aee1239 --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerationsParams.kt @@ -0,0 +1,9 @@ +package co.yml.ychat.domain.model + +internal data class ImageGenerationsParams( + var prompt: String = "", + var results: Int = 2, + var size: String = "256x256", + var responseFormat: String = "url", + var user: String = "", +) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt new file mode 100644 index 0000000..b9da7a4 --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt @@ -0,0 +1,16 @@ +package co.yml.ychat.domain.usecases + +import co.yml.ychat.data.api.ChatGptApi +import co.yml.ychat.domain.mapper.toImageGenerated +import co.yml.ychat.domain.mapper.toImageGenerationsParamsDto +import co.yml.ychat.domain.model.ImageGenerated +import co.yml.ychat.domain.model.ImageGenerationsParams + +internal data class ImageGenerationsUseCase(private val chatGptApi: ChatGptApi) { + + suspend fun requestImageGenerations(params: ImageGenerationsParams): List { + val requestDto = params.toImageGenerationsParamsDto() + val response = chatGptApi.imageGenerations(requestDto) + return response.getBodyOrThrow().toImageGenerated() + } +} diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt new file mode 100644 index 0000000..fad69e6 --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt @@ -0,0 +1,20 @@ +package co.yml.ychat.entrypoint.features + +import co.yml.ychat.YChat +import co.yml.ychat.data.exception.ChatGptException +import co.yml.ychat.domain.model.ImageGenerated +import kotlin.coroutines.cancellation.CancellationException + +interface ImageGenerations { + + fun setResults(results: Int): ImageGenerations + + fun setSize(size: String): ImageGenerations + + fun setResponseFormat(responseFormat: String): ImageGenerations + + @Throws(CancellationException::class, ChatGptException::class) + suspend fun execute(prompt: String): List + + fun execute(prompt: String, callback: YChat.Callback>) +} diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt new file mode 100644 index 0000000..5f0caf0 --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt @@ -0,0 +1,52 @@ +package co.yml.ychat.entrypoint.impl + +import co.yml.ychat.YChat +import co.yml.ychat.domain.model.ChatCompletionsParams +import co.yml.ychat.domain.model.ImageGenerated +import co.yml.ychat.domain.model.ImageGenerationsParams +import co.yml.ychat.domain.usecases.ImageGenerationsUseCase +import co.yml.ychat.entrypoint.features.ImageGenerations +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch + +internal class ImageGenerationsImpl( + private val dispatcher: CoroutineDispatcher, + private val imageGenerationsUseCase: ImageGenerationsUseCase, +) : ImageGenerations { + + private val scope by lazy { CoroutineScope(SupervisorJob() + dispatcher) } + + private var params: ImageGenerationsParams = ImageGenerationsParams() + + override fun setResults(results: Int): ImageGenerations { + params.results = results + return this + } + + override fun setSize(size: String): ImageGenerations { + params.size = size + return this + } + + override fun setResponseFormat(responseFormat: String): ImageGenerations { + params.responseFormat = responseFormat + return this + } + + override suspend fun execute(prompt: String): List { + params.prompt = prompt + return imageGenerationsUseCase.requestImageGenerations(params) + } + + override fun execute(prompt: String, callback: YChat.Callback>) { + scope.launch { + kotlin.runCatching { execute(prompt) } + .onSuccess { it -> callback.onSuccess(it.map { it.url }) } // fix here + .onFailure { callback.onError(it) } + } + } + + +} diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/YChatImpl.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/YChatImpl.kt index 324a95f..c29f71e 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/YChatImpl.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/YChatImpl.kt @@ -4,6 +4,7 @@ import co.yml.ychat.YChat import co.yml.ychat.di.module.LibraryModule import co.yml.ychat.entrypoint.features.ChatCompletions import co.yml.ychat.entrypoint.features.Completion +import co.yml.ychat.entrypoint.features.ImageGenerations import org.koin.core.KoinApplication internal class YChatImpl(apiKey: String) : YChat { @@ -22,4 +23,8 @@ internal class YChatImpl(apiKey: String) : YChat { override fun chatCompletions(): ChatCompletions { return koinApp.koin.get() } + + override fun imageGenerations(): ImageGenerations { + return koinApp.koin.get() + } } From 9310e86f34c805a712559150a708690c2aacd6b5 Mon Sep 17 00:00:00 2001 From: Renato Date: Mon, 13 Mar 2023 08:01:49 -0300 Subject: [PATCH 02/12] feat: Android sample integration --- buildSrc/src/main/kotlin/Dependencies.kt | 2 + sample/android/build.gradle.kts | 1 + .../co/yml/ychat/android/MainViewModel.kt | 22 +++-- .../java/co/yml/ychat/android/MessageItem.kt | 1 + .../co/yml/ychat/android/ui/ChatLayout.kt | 12 ++- .../yml/ychat/android/ui/ImageItemLayout.kt | 87 +++++++++++++++++++ .../yml/ychat/android/ui/SendMessageLayout.kt | 9 +- 7 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 sample/android/src/main/java/co/yml/ychat/android/ui/ImageItemLayout.kt diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index ff2abe0..864e7e8 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -11,6 +11,7 @@ object Versions { const val COMPOSE_ACTIVITY = "1.6.1" const val COMPOSE_NAVIGATION = "2.5.3" const val COMPOSE_LIVEDATA = "1.3.3" + const val COIL = "2.2.2" const val KTOR = "2.2.2" const val KOIN = "3.2.0" const val MATERIAL_DESIGN = "1.6.1" @@ -52,6 +53,7 @@ object Dependencies { const val COMPOSE_ACTIVITY = "androidx.activity:activity-compose:${Versions.COMPOSE_ACTIVITY}" const val COMPOSE_NAVIGATION = "androidx.navigation:navigation-compose:${Versions.COMPOSE_NAVIGATION}" const val COMPOSE_LIVEDATA = "androidx.compose.runtime:runtime-livedata:${Versions.COMPOSE_LIVEDATA}" + const val COIL = "io.coil-kt:coil-compose:${Versions.COIL}" } object Test { diff --git a/sample/android/build.gradle.kts b/sample/android/build.gradle.kts index 76481a2..6e73bef 100644 --- a/sample/android/build.gradle.kts +++ b/sample/android/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { implementation(Dependencies.UI.COMPOSE_ACTIVITY) implementation(Dependencies.UI.COMPOSE_NAVIGATION) implementation(Dependencies.UI.COMPOSE_LIVEDATA) + implementation(Dependencies.UI.COIL) implementation(Dependencies.DI.KOIN_CORE) implementation(Dependencies.DI.KOIN_ANDROID) implementation(Dependencies.DI.KOIN_COMPOSE) diff --git a/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt b/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt index 0a0927b..46cb6da 100644 --- a/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt +++ b/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt @@ -1,6 +1,5 @@ package co.yml.ychat.android -import android.util.Log import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.MutableLiveData @@ -37,7 +36,7 @@ class MainViewModel(private val chatGpt: YChat) : ViewModel() { private var typingItem = mutableStateOf(MessageItem(message = typingTxt.value, isOut = false)) private fun setLoading(isLoading: Boolean) { - _isLoading.value = isLoading + _isLoading.postValue(isLoading) } fun onSendMessage(message: String, typingStr: String) { @@ -48,15 +47,17 @@ class MainViewModel(private val chatGpt: YChat) : ViewModel() { } } - fun onImageRequest(prompt: String) { + fun onImageRequest(prompt: String, typingStr: String) { + updateTypingMessage(typingStr) viewModelScope.launch { - imageGenerations.execute(prompt, object: Callback> { + showTypingAnimation(prompt) + imageGenerations.execute(prompt, object : Callback> { override fun onSuccess(result: List) { - Log.d("callback", "onSuccess: $result") + showImages(result) } override fun onError(throwable: Throwable) { - Log.d("callback", "onError: $throwable") + writeResponse(ERROR) } }) } @@ -75,6 +76,14 @@ class MainViewModel(private val chatGpt: YChat) : ViewModel() { setLoading(false) } + private fun showImages(result: List) { + items.remove(items[items.lastIndex]) + result.forEach { + items.add(MessageItem(message = IMAGE, isOut = false, url = it)) + } + setLoading(false) + } + private suspend fun requestCompletion(message: String): String { return try { chatCompletions.execute(message).last().content @@ -98,5 +107,6 @@ class MainViewModel(private val chatGpt: YChat) : ViewModel() { companion object { private const val ERROR = "Error" private const val MAX_TOKENS = 1024 + private const val IMAGE = "image" } } \ No newline at end of file diff --git a/sample/android/src/main/java/co/yml/ychat/android/MessageItem.kt b/sample/android/src/main/java/co/yml/ychat/android/MessageItem.kt index cd1cf5d..80f9ff9 100644 --- a/sample/android/src/main/java/co/yml/ychat/android/MessageItem.kt +++ b/sample/android/src/main/java/co/yml/ychat/android/MessageItem.kt @@ -3,4 +3,5 @@ package co.yml.ychat.android data class MessageItem( val message: String, val isOut: Boolean, + val url: String? = null ) \ No newline at end of file diff --git a/sample/android/src/main/java/co/yml/ychat/android/ui/ChatLayout.kt b/sample/android/src/main/java/co/yml/ychat/android/ui/ChatLayout.kt index d93c86c..ee43cca 100644 --- a/sample/android/src/main/java/co/yml/ychat/android/ui/ChatLayout.kt +++ b/sample/android/src/main/java/co/yml/ychat/android/ui/ChatLayout.kt @@ -77,9 +77,15 @@ fun ChatLayout( .padding(spaceMedium), ) { items(messages) { message -> - MessageItemLayout( - messageText = message.message, isOut = message.isOut - ) + message.url?.let { + ImageItemLayout( + messageText = message.url, isOut = message.isOut + ) + } ?: run { + MessageItemLayout( + messageText = message.message, isOut = message.isOut + ) + } } coroutineScope.launch { listState.animateScrollToItem(messages.size) diff --git a/sample/android/src/main/java/co/yml/ychat/android/ui/ImageItemLayout.kt b/sample/android/src/main/java/co/yml/ychat/android/ui/ImageItemLayout.kt new file mode 100644 index 0000000..9b382ff --- /dev/null +++ b/sample/android/src/main/java/co/yml/ychat/android/ui/ImageItemLayout.kt @@ -0,0 +1,87 @@ +package co.yml.ychat.android.ui + +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import co.yml.ychat.android.R +import co.yml.ychat.android.ui.Dimensions.default +import co.yml.ychat.android.ui.Dimensions.robotMessageIconSize +import co.yml.ychat.android.ui.Dimensions.robotMessagePaddingSize +import co.yml.ychat.android.ui.Dimensions.spaceExtraSmall +import co.yml.ychat.android.ui.Dimensions.spaceMedium +import co.yml.ychat.android.ui.Dimensions.spaceSmall +import coil.compose.AsyncImage + +@Composable +fun ImageItemLayout( + messageText: String, + isOut: Boolean +) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = if (isOut) Alignment.End else Alignment.Start + ) { + Row( + modifier = Modifier.padding(top = spaceMedium), + verticalAlignment = Alignment.Bottom + ) { + if (isOut.not()) { + Image( + painterResource(R.drawable.ic_robot), + contentDescription = "", + modifier = Modifier + .width(robotMessageIconSize) + .height(robotMessageIconSize) + .clip(shape = CircleShape) + .background(colorResource(id = R.color.softGreen)) + .padding(robotMessagePaddingSize), + ) + Spacer(modifier = Modifier.padding(spaceExtraSmall)) + } + Box( + modifier = Modifier + .clip( + shape = RoundedCornerShape( + topStart = spaceMedium, + topEnd = spaceMedium, + bottomEnd = if (isOut) default else spaceMedium, + bottomStart = if (isOut) spaceMedium else default + ) + ) + .background(if (isOut) colorResource(id = R.color.softBlue) else colorResource(id = R.color.opaqueWhite)) + .padding(spaceSmall) + ) { + AsyncImage( + modifier = Modifier.clip(RoundedCornerShape(8.dp)), + model = messageText, + contentDescription = messageText, + placeholder = painterResource(R.drawable.ic_robot), + ) + } + } + } +} + +@Preview(uiMode = UI_MODE_NIGHT_YES) +@Composable +fun PreviewImageItemLayout() { + MessageItemLayout(messageText = "Message", isOut = false) +} \ No newline at end of file diff --git a/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt b/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt index 6b257b6..e8673b0 100644 --- a/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt +++ b/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt @@ -52,6 +52,8 @@ fun SendMessageLayout() { val scope = rememberCoroutineScope() val viewModel = koinViewModel() val isLoading: Boolean by viewModel.isLoading.observeAsState(initial = false) + + Row( modifier = Modifier .background(color = MaterialTheme.colors.background) @@ -91,8 +93,11 @@ fun SendMessageLayout() { .background(if (textFieldState.isNotEmpty() && isLoading.not()) colorResource(id = R.color.softBlue) else colorResource(id = R.color.opaqueWhite)), onClick = { scope.launch { - //viewModel.onSendMessage(textFieldState, typingString) - viewModel.onImageRequest(textFieldState) + if (textFieldState.startsWith("/image ")) { + viewModel.onImageRequest(textFieldState, typingString) + } else { + viewModel.onSendMessage(textFieldState, typingString) + } textFieldState = "" } }, From 0acc62b45c45c35b4f55fec206eb98e767fca9e7 Mon Sep 17 00:00:00 2001 From: Renato Date: Tue, 14 Mar 2023 09:12:13 -0300 Subject: [PATCH 03/12] feat: iOS sample integration --- .../co/yml/ychat/android/MainViewModel.kt | 1 - .../yml/ychat/android/ui/SendMessageLayout.kt | 1 - .../Features/Completion/CompletionView.swift | 33 +++++++++- .../Completion/Model/ChatMessage.swift | 1 + .../ViewModel/CompletionViewModel.swift | 25 ++++++- .../ychat/jvm/controller/YChatController.java | 8 +++ .../yml/ychat/jvm/services/YChatService.java | 8 +++ .../domain/mapper/ImageGenerationsMapper.kt | 1 - .../yml/ychat/domain/model/ImageGenerated.kt | 6 +- .../domain/model/ImageGenerationsParams.kt | 15 ++++- .../entrypoint/impl/ImageGenerationsImpl.kt | 5 +- .../co/yml/ychat/di/LibraryModuleTest.kt | 4 ++ .../mapper/ChatCompletionsMapperTest.kt | 40 ++++++++++++ .../mapper/ImageGenerationsMapperTest.kt | 30 +++++++++ .../model/ImageGenerationsParamsTest.kt | 20 ++++++ .../usecases/ImageGenerationsUseCaseTest.kt | 65 +++++++++++++++++++ 16 files changed, 245 insertions(+), 18 deletions(-) create mode 100644 ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ChatCompletionsMapperTest.kt create mode 100644 ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt create mode 100644 ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/ImageGenerationsParamsTest.kt create mode 100644 ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt diff --git a/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt b/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt index 46cb6da..e37e388 100644 --- a/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt +++ b/sample/android/src/main/java/co/yml/ychat/android/MainViewModel.kt @@ -23,7 +23,6 @@ class MainViewModel(private val chatGpt: YChat) : ViewModel() { private val imageGenerations by lazy { chatGpt.imageGenerations() - .setResults(2) } private val _items = mutableStateListOf() diff --git a/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt b/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt index e8673b0..8be8698 100644 --- a/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt +++ b/sample/android/src/main/java/co/yml/ychat/android/ui/SendMessageLayout.kt @@ -53,7 +53,6 @@ fun SendMessageLayout() { val viewModel = koinViewModel() val isLoading: Boolean by viewModel.isLoading.observeAsState(initial = false) - Row( modifier = Modifier .background(color = MaterialTheme.colors.background) diff --git a/sample/ios/YChatApp/Features/Completion/CompletionView.swift b/sample/ios/YChatApp/Features/Completion/CompletionView.swift index a4d44d0..108ee50 100644 --- a/sample/ios/YChatApp/Features/Completion/CompletionView.swift +++ b/sample/ios/YChatApp/Features/Completion/CompletionView.swift @@ -72,9 +72,14 @@ private extension CompletionView { } case .bot: HStack { - botChatBubble(message: chatMessage.message) - Spacer().frame(width: 60) - Spacer() + if let imageUrl = chatMessage.url { + botImageBubble(imageUrl) + Spacer() + } else { + botChatBubble(message: chatMessage.message) + Spacer().frame(width: 60) + Spacer() + } } case .loading: HStack { @@ -120,6 +125,28 @@ private extension CompletionView { .cornerRadius(16, corners: [.bottomLeft, .bottomLeft, .topRight]) } } + + @ViewBuilder + private func botImageBubble(_ url: String) -> some View { + HStack(alignment: .top, spacing: 4) { + Circle() + .fill(.green) + .frame(width: 40, height: 40) + .overlay { + Image(uiImage: Icon.bot.uiImage) + .renderingMode(.template) + .foregroundColor(.white) + } + ZStack { + AsyncImage(url: URL(string: url)) + .foregroundColor(.grayDark) + } + .padding(.horizontal, 16) + .padding(.vertical, 8) + .background(Color.grayLight) + .cornerRadius(16, corners: [.bottomLeft, .bottomLeft, .topRight]) + } + } @ViewBuilder private func sendMessageSection() -> some View { diff --git a/sample/ios/YChatApp/Features/Completion/Model/ChatMessage.swift b/sample/ios/YChatApp/Features/Completion/Model/ChatMessage.swift index 04306aa..1219bb2 100644 --- a/sample/ios/YChatApp/Features/Completion/Model/ChatMessage.swift +++ b/sample/ios/YChatApp/Features/Completion/Model/ChatMessage.swift @@ -12,6 +12,7 @@ struct ChatMessage: Identifiable, Equatable { let id: String var message: String = "" var type: MessageType = .human(error: false) + var url: String? enum MessageType: Equatable { case human(error: Bool), bot, loading diff --git a/sample/ios/YChatApp/Features/Completion/ViewModel/CompletionViewModel.swift b/sample/ios/YChatApp/Features/Completion/ViewModel/CompletionViewModel.swift index f13f22f..90ef52e 100644 --- a/sample/ios/YChatApp/Features/Completion/ViewModel/CompletionViewModel.swift +++ b/sample/ios/YChatApp/Features/Completion/ViewModel/CompletionViewModel.swift @@ -19,6 +19,10 @@ internal final class CompletionViewModel: ObservableObject { content: "You are a helpful assistant." ) + private var imageGenerations: ImageGenerations = + YChatCompanion.shared.create(apiKey: Config.apiKey) + .imageGenerations() + @Published var message: String = "" @@ -37,9 +41,15 @@ internal final class CompletionViewModel: ObservableObject { cleanLastMessage() addLoading() do { - let result = try await chatCompletions.execute(content: input)[0].content - removeLoading() - addAIMessage(message: result) + if input.contains("/image ") { + let result = try await imageGenerations.execute(prompt: input)[0].url + removeLoading() + addAIImage(url: result) + } else { + let result = try await chatCompletions.execute(content: input)[0].content + removeLoading() + addAIMessage(message: result) + } } catch { removeLoading() setError() @@ -64,6 +74,15 @@ internal final class CompletionViewModel: ObservableObject { ) chatMessageList.append(chatMessage) } + + private func addAIImage(url: String) { + let chatMessage = ChatMessage( + id: UUID().uuidString, + type: .bot, + url: url + ) + chatMessageList.append(chatMessage) + } private func addLoading() { let chatMessage = ChatMessage( diff --git a/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java b/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java index 4e1b1ac..6d6d2e1 100644 --- a/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java +++ b/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java @@ -32,6 +32,14 @@ public ResponseEntity chatCompletions( return ResponseEntity.ok(result); } + @GetMapping("generations") + public ResponseEntity imageGenerations( + @RequestParam(value = "prompt", defaultValue = Defaults.CHAT_COMPLETION_INPUT) String input + ) throws Exception { + String result = YChatService.getImageGenerationsAnswer(input); + return ResponseEntity.ok(result); + } + private static class Defaults { static final String COMPLETION_INPUT = "Say this is a test."; static final String CHAT_COMPLETION_INPUT = "Tell me one strength exercise"; diff --git a/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java b/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java index a6aaa34..3cb1f56 100644 --- a/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java +++ b/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Service; import java.util.concurrent.CompletableFuture; import co.yml.ychat.YChat; +import co.yml.ychat.domain.model.ImageGenerated; @Service public class YChatService { @@ -35,6 +36,13 @@ public String getChatCompletionsAnswer(String input, String topic) throws Except return future.get().get(0).getContent(); } + public String getImageGenerationsAnswer(String prompt) throws Exception { + final CompletableFuture> future = new CompletableFuture<>(); + ychat.imageGenerations() + .execute(prompt, new CompletionCallbackResult<>(future)); + return future.get().get(0); + } + private static class CompletionCallbackResult implements YChat.Callback { private final CompletableFuture future; diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt index 2db037a..9a280bc 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt @@ -11,7 +11,6 @@ internal fun ImageGenerationsDto.toImageGenerated(): List { } } - internal fun ImageGenerationsParams.toImageGenerationsParamsDto(): ImageGenerationsParamsDto { return ImageGenerationsParamsDto( prompt = this.prompt, diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerated.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerated.kt index 439d332..9e6ba6f 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerated.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerated.kt @@ -4,10 +4,8 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Represents a message in a conversation, consisting of a [role] indicating the speaker - * (e.g., “system”, “user” or “assistant”), and the [content] of the message sent by the speaker. - * @property role The role of the speaker who sends the message. - * @property content The content of the message sent by the speaker. + * Represents a image generated by a prompt, consisting of a [url] generated by the system (AI). + * @property url The url of the image generated by the input provided. */ @Serializable data class ImageGenerated( diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerationsParams.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerationsParams.kt index aee1239..47c91f2 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerationsParams.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerationsParams.kt @@ -1,8 +1,21 @@ package co.yml.ychat.domain.model +/** + * Parameters to configure the Image Generations API. + * + * @param prompt The prompt(s) to generate images for. + * + * @param results: Quantity of images to be generated. + * + * @param size: The size of the images generated (squared). Ex. 256x256, 512x512, 1024x1024 + * + * @param responseFormat: The format in which the generated images are returned. Must be one of url or b64_json. + * + * @param user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. + */ internal data class ImageGenerationsParams( var prompt: String = "", - var results: Int = 2, + var results: Int = 1, var size: String = "256x256", var responseFormat: String = "url", var user: String = "", diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt index 5f0caf0..070fd4a 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt @@ -1,7 +1,6 @@ package co.yml.ychat.entrypoint.impl import co.yml.ychat.YChat -import co.yml.ychat.domain.model.ChatCompletionsParams import co.yml.ychat.domain.model.ImageGenerated import co.yml.ychat.domain.model.ImageGenerationsParams import co.yml.ychat.domain.usecases.ImageGenerationsUseCase @@ -43,10 +42,8 @@ internal class ImageGenerationsImpl( override fun execute(prompt: String, callback: YChat.Callback>) { scope.launch { kotlin.runCatching { execute(prompt) } - .onSuccess { it -> callback.onSuccess(it.map { it.url }) } // fix here + .onSuccess { callback.onSuccess(it.map { it.url }) } .onFailure { callback.onError(it) } } } - - } diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/di/LibraryModuleTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/di/LibraryModuleTest.kt index fef41e9..214b693 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/di/LibraryModuleTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/di/LibraryModuleTest.kt @@ -6,8 +6,10 @@ import co.yml.ychat.data.storage.ChatLogStorage import co.yml.ychat.di.module.LibraryModule import co.yml.ychat.domain.usecases.ChatCompletionsUseCase import co.yml.ychat.domain.usecases.CompletionUseCase +import co.yml.ychat.domain.usecases.ImageGenerationsUseCase import co.yml.ychat.entrypoint.features.ChatCompletions import co.yml.ychat.entrypoint.features.Completion +import co.yml.ychat.entrypoint.features.ImageGenerations import io.ktor.client.HttpClient import kotlin.test.AfterTest import kotlin.test.BeforeTest @@ -39,5 +41,7 @@ class LibraryModuleTest : KoinTest { get() get() get() + get() + get() } } diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ChatCompletionsMapperTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ChatCompletionsMapperTest.kt new file mode 100644 index 0000000..49f6582 --- /dev/null +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ChatCompletionsMapperTest.kt @@ -0,0 +1,40 @@ +package co.yml.ychat.domain.mapper + +import co.yml.ychat.data.dto.ChatCompletionsChoiceDto +import co.yml.ychat.data.dto.ChatCompletionsDto +import co.yml.ychat.data.dto.ChatMessageDto +import co.yml.ychat.data.dto.UsageDto +import co.yml.ychat.domain.model.ChatCompletionsParams +import co.yml.ychat.domain.model.ChatMessage +import kotlin.test.Test +import kotlin.test.assertEquals + +class ChatCompletionsMapperTest { + + @Test + fun `on convert ChatCompletionsDto to ChatMessages`() { + val listOfChatMessages = listOf(ChatMessage("user", "message 1"), ChatMessage("user", "message 2")) + val chatCompletionsDto = ChatCompletionsDto( + choices = listOf( + ChatCompletionsChoiceDto(1, ChatMessageDto("user", "message 1"), null), + ChatCompletionsChoiceDto(1, ChatMessageDto("user", "message 2"), null) + ), + id = "1", + model = "", + usage = UsageDto(1, 1, 1) + ) + assertEquals(listOfChatMessages, chatCompletionsDto.toChatMessages()) + } + + @Test + fun `on convert ChatCompletionsParams to ChatCompletionParamsDto`() { + val messages = arrayListOf(ChatMessage("user", "message 1"), ChatMessage("user", "message 2")) + val chatCompletionsParams = ChatCompletionsParams(messages) + assertEquals(messages.map { ChatMessageDto(it.role, it.content) }, chatCompletionsParams.toChatCompletionParamsDto().messages, "") + assertEquals("gpt-3.5-turbo", chatCompletionsParams.toChatCompletionParamsDto().model) + assertEquals(1, chatCompletionsParams.toChatCompletionParamsDto().maxResults) + assertEquals(4096, chatCompletionsParams.toChatCompletionParamsDto().maxTokens) + assertEquals(1.0, chatCompletionsParams.toChatCompletionParamsDto().temperature) + assertEquals(1.0, chatCompletionsParams.toChatCompletionParamsDto().topP) + } +} diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt new file mode 100644 index 0000000..2f949ca --- /dev/null +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt @@ -0,0 +1,30 @@ +package co.yml.ychat.domain.mapper + +import co.yml.ychat.data.dto.ImageGenerationsDto +import co.yml.ychat.domain.model.ImageGenerated +import co.yml.ychat.domain.model.ImageGenerationsParams +import kotlin.test.Test +import kotlin.test.assertEquals + +class ImageGenerationsMapperTest { + + @Test + fun `on convert ImageGenerationsDto to ImageGenerated`() { + val listOfImageGenerated = listOf(ImageGenerated("http://url1.test"), ImageGenerated("http://url2.test")) + val imageGenerationsDto = ImageGenerationsDto( + created = 12345, + data = listOfImageGenerated + ) + assertEquals(listOfImageGenerated, imageGenerationsDto.toImageGenerated()) + } + + @Test + fun `on convert ImageGenerationsParams to ImageGenerationsDto`() { + val imageGenerationsParams = ImageGenerationsParams(prompt = "/image test") + assertEquals("/image test", imageGenerationsParams.toImageGenerationsParamsDto().prompt) + assertEquals("url", imageGenerationsParams.toImageGenerationsParamsDto().responseFormat) + assertEquals("256x256", imageGenerationsParams.toImageGenerationsParamsDto().size) + assertEquals("", imageGenerationsParams.toImageGenerationsParamsDto().user) + assertEquals(1, imageGenerationsParams.toImageGenerationsParamsDto().results) + } +} diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/ImageGenerationsParamsTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/ImageGenerationsParamsTest.kt new file mode 100644 index 0000000..0c237cf --- /dev/null +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/ImageGenerationsParamsTest.kt @@ -0,0 +1,20 @@ +package co.yml.ychat.domain.model + +import kotlin.test.Test +import kotlin.test.assertEquals + +class ImageGenerationsParamsTest { + + @Test + fun `on ChatCompletionsParams verify default values`() { + // arrange + val params = ImageGenerationsParams() + + // assert + assertEquals(true, params.prompt.isEmpty()) + assertEquals(1, params.results) + assertEquals("256x256", params.size) + assertEquals("url", params.responseFormat) + assertEquals("", params.user) + } +} diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt new file mode 100644 index 0000000..c5e8628 --- /dev/null +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt @@ -0,0 +1,65 @@ +package co.yml.ychat.domain.usecases + +import co.yml.ychat.data.api.ChatGptApi +import co.yml.ychat.data.dto.ImageGenerationsDto +import co.yml.ychat.data.exception.ChatGptException +import co.yml.ychat.data.infrastructure.ApiResult +import co.yml.ychat.domain.model.ImageGenerated +import co.yml.ychat.domain.model.ImageGenerationsParams +import io.mockk.coEvery +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class ImageGenerationsUseCaseTest { + + private lateinit var imageGenerationsUseCase: ImageGenerationsUseCase + + private val chatGptApiMock = mockk() + + @BeforeTest + fun setup() { + imageGenerationsUseCase = ImageGenerationsUseCase(chatGptApiMock) + } + + @Test + fun `on requestImageGenerations when request succeed then should return formatted result`() { + // arrange + val prompt = "/image test" + val imageGenerationsDto = buildImageGenerationsDto("https://image-generated.test") + val params = ImageGenerationsParams(prompt = prompt) + val apiResult = ApiResult(body = imageGenerationsDto) + coEvery { chatGptApiMock.imageGenerations(any()) } returns apiResult + + // act + val result = runBlocking { imageGenerationsUseCase.requestImageGenerations(params) } + + // assert + assertEquals("https://image-generated.test", result.last().url) + } + + @Test + fun `on requestChatCompletions when not request succeed then should throw an exception`() { + // arrange + val prompt = "/image test" + val params = ImageGenerationsParams(prompt = prompt) + val apiResult = ApiResult(exception = ChatGptException()) + coEvery { chatGptApiMock.imageGenerations(any()) } returns apiResult + + // act + val result = + runCatching { runBlocking { imageGenerationsUseCase.requestImageGenerations(params) } } + + // assert + assertEquals(true, result.exceptionOrNull() is ChatGptException) + } + + private fun buildImageGenerationsDto(url: String): ImageGenerationsDto { + return ImageGenerationsDto( + created = 12345, + data = listOf(ImageGenerated(url)) + ) + } +} From 8ffd0cb872db9e71286e7c3b7d381207895b97b0 Mon Sep 17 00:00:00 2001 From: Renato Date: Tue, 14 Mar 2023 18:48:17 -0300 Subject: [PATCH 04/12] refactor: Replace List of ImageGenerated by String in ImageGenerations --- .../co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt | 4 ++-- .../co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt | 2 +- .../co/yml/ychat/entrypoint/features/ImageGenerations.kt | 2 +- .../co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt index 9a280bc..038195e 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt @@ -5,9 +5,9 @@ import co.yml.ychat.data.dto.ImageGenerationsParamsDto import co.yml.ychat.domain.model.ImageGenerated import co.yml.ychat.domain.model.ImageGenerationsParams -internal fun ImageGenerationsDto.toImageGenerated(): List { +internal fun ImageGenerationsDto.toImageGenerated(): List { return this.data.map { - ImageGenerated(it.url) + it.url } } diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt index b9da7a4..401b4c5 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt @@ -8,7 +8,7 @@ import co.yml.ychat.domain.model.ImageGenerationsParams internal data class ImageGenerationsUseCase(private val chatGptApi: ChatGptApi) { - suspend fun requestImageGenerations(params: ImageGenerationsParams): List { + suspend fun requestImageGenerations(params: ImageGenerationsParams): List { val requestDto = params.toImageGenerationsParamsDto() val response = chatGptApi.imageGenerations(requestDto) return response.getBodyOrThrow().toImageGenerated() diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt index fad69e6..a3e8fb9 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt @@ -14,7 +14,7 @@ interface ImageGenerations { fun setResponseFormat(responseFormat: String): ImageGenerations @Throws(CancellationException::class, ChatGptException::class) - suspend fun execute(prompt: String): List + suspend fun execute(prompt: String): List fun execute(prompt: String, callback: YChat.Callback>) } diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt index 070fd4a..2924917 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt @@ -34,7 +34,7 @@ internal class ImageGenerationsImpl( return this } - override suspend fun execute(prompt: String): List { + override suspend fun execute(prompt: String): List { params.prompt = prompt return imageGenerationsUseCase.requestImageGenerations(params) } @@ -42,7 +42,7 @@ internal class ImageGenerationsImpl( override fun execute(prompt: String, callback: YChat.Callback>) { scope.launch { kotlin.runCatching { execute(prompt) } - .onSuccess { callback.onSuccess(it.map { it.url }) } + .onSuccess { callback.onSuccess(it) } .onFailure { callback.onError(it) } } } From f9989aa4b29810a56725da97de8b58a3cb369aae Mon Sep 17 00:00:00 2001 From: Renato Date: Tue, 14 Mar 2023 18:49:36 -0300 Subject: [PATCH 05/12] refactor: remove "kotlin" prefix in ImageGenerationsImpl --- .../kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt index 2924917..00a3061 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt @@ -41,7 +41,7 @@ internal class ImageGenerationsImpl( override fun execute(prompt: String, callback: YChat.Callback>) { scope.launch { - kotlin.runCatching { execute(prompt) } + runCatching { execute(prompt) } .onSuccess { callback.onSuccess(it) } .onFailure { callback.onError(it) } } From 7ca3eac9b43cfcd3209833736863a6a601c598c8 Mon Sep 17 00:00:00 2001 From: Renato Date: Tue, 14 Mar 2023 18:56:10 -0300 Subject: [PATCH 06/12] refactor:rename ImageGenerated to ImageGeneratedDto,remove unused import --- .../main/java/co/yml/ychat/jvm/services/YChatService.java | 1 - .../kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt | 4 ++-- .../co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt | 1 - .../model/{ImageGenerated.kt => ImageGeneratedDto.kt} | 2 +- .../yml/ychat/domain/usecases/ImageGenerationsUseCase.kt | 1 - .../co/yml/ychat/entrypoint/features/ImageGenerations.kt | 1 - .../co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt | 1 - .../yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt | 8 ++++---- .../ychat/domain/usecases/ImageGenerationsUseCaseTest.kt | 4 ++-- 9 files changed, 9 insertions(+), 14 deletions(-) rename ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/{ImageGenerated.kt => ImageGeneratedDto.kt} (90%) diff --git a/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java b/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java index 3cb1f56..6fa70fb 100644 --- a/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java +++ b/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java @@ -7,7 +7,6 @@ import org.springframework.stereotype.Service; import java.util.concurrent.CompletableFuture; import co.yml.ychat.YChat; -import co.yml.ychat.domain.model.ImageGenerated; @Service public class YChatService { diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt index d02b87c..7f2d2e2 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt @@ -1,6 +1,6 @@ package co.yml.ychat.data.dto -import co.yml.ychat.domain.model.ImageGenerated +import co.yml.ychat.domain.model.ImageGeneratedDto import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -9,5 +9,5 @@ internal data class ImageGenerationsDto( @SerialName("created") val created: Long, @SerialName("data") - val data: List, + val data: List, ) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt index 038195e..ea109b7 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapper.kt @@ -2,7 +2,6 @@ package co.yml.ychat.domain.mapper import co.yml.ychat.data.dto.ImageGenerationsDto import co.yml.ychat.data.dto.ImageGenerationsParamsDto -import co.yml.ychat.domain.model.ImageGenerated import co.yml.ychat.domain.model.ImageGenerationsParams internal fun ImageGenerationsDto.toImageGenerated(): List { diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerated.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGeneratedDto.kt similarity index 90% rename from ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerated.kt rename to ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGeneratedDto.kt index 9e6ba6f..055f950 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGenerated.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGeneratedDto.kt @@ -8,7 +8,7 @@ import kotlinx.serialization.Serializable * @property url The url of the image generated by the input provided. */ @Serializable -data class ImageGenerated( +internal data class ImageGeneratedDto( @SerialName("url") val url: String, ) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt index 401b4c5..f0aa77f 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCase.kt @@ -3,7 +3,6 @@ package co.yml.ychat.domain.usecases import co.yml.ychat.data.api.ChatGptApi import co.yml.ychat.domain.mapper.toImageGenerated import co.yml.ychat.domain.mapper.toImageGenerationsParamsDto -import co.yml.ychat.domain.model.ImageGenerated import co.yml.ychat.domain.model.ImageGenerationsParams internal data class ImageGenerationsUseCase(private val chatGptApi: ChatGptApi) { diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt index a3e8fb9..864041e 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/ImageGenerations.kt @@ -2,7 +2,6 @@ package co.yml.ychat.entrypoint.features import co.yml.ychat.YChat import co.yml.ychat.data.exception.ChatGptException -import co.yml.ychat.domain.model.ImageGenerated import kotlin.coroutines.cancellation.CancellationException interface ImageGenerations { diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt index 00a3061..1f03284 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/ImageGenerationsImpl.kt @@ -1,7 +1,6 @@ package co.yml.ychat.entrypoint.impl import co.yml.ychat.YChat -import co.yml.ychat.domain.model.ImageGenerated import co.yml.ychat.domain.model.ImageGenerationsParams import co.yml.ychat.domain.usecases.ImageGenerationsUseCase import co.yml.ychat.entrypoint.features.ImageGenerations diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt index 2f949ca..f7f128c 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt @@ -1,7 +1,7 @@ package co.yml.ychat.domain.mapper import co.yml.ychat.data.dto.ImageGenerationsDto -import co.yml.ychat.domain.model.ImageGenerated +import co.yml.ychat.domain.model.ImageGeneratedDto import co.yml.ychat.domain.model.ImageGenerationsParams import kotlin.test.Test import kotlin.test.assertEquals @@ -10,12 +10,12 @@ class ImageGenerationsMapperTest { @Test fun `on convert ImageGenerationsDto to ImageGenerated`() { - val listOfImageGenerated = listOf(ImageGenerated("http://url1.test"), ImageGenerated("http://url2.test")) + val listOfImageGeneratedDto = listOf(ImageGeneratedDto("http://url1.test"), ImageGeneratedDto("http://url2.test")) val imageGenerationsDto = ImageGenerationsDto( created = 12345, - data = listOfImageGenerated + data = listOfImageGeneratedDto ) - assertEquals(listOfImageGenerated, imageGenerationsDto.toImageGenerated()) + assertEquals(listOfImageGeneratedDto, imageGenerationsDto.toImageGenerated()) } @Test diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt index c5e8628..6ae49af 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt @@ -4,7 +4,7 @@ import co.yml.ychat.data.api.ChatGptApi import co.yml.ychat.data.dto.ImageGenerationsDto import co.yml.ychat.data.exception.ChatGptException import co.yml.ychat.data.infrastructure.ApiResult -import co.yml.ychat.domain.model.ImageGenerated +import co.yml.ychat.domain.model.ImageGeneratedDto import co.yml.ychat.domain.model.ImageGenerationsParams import io.mockk.coEvery import io.mockk.mockk @@ -59,7 +59,7 @@ class ImageGenerationsUseCaseTest { private fun buildImageGenerationsDto(url: String): ImageGenerationsDto { return ImageGenerationsDto( created = 12345, - data = listOf(ImageGenerated(url)) + data = listOf(ImageGeneratedDto(url)) ) } } From ccff2bbe2343a86e6003727a6694e3b320ac09d3 Mon Sep 17 00:00:00 2001 From: Renato Date: Tue, 14 Mar 2023 18:59:04 -0300 Subject: [PATCH 07/12] refactor: create IMAGE_GENERATION_TOPIC default value --- .../main/java/co/yml/ychat/jvm/controller/YChatController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java b/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java index 6d6d2e1..05a3290 100644 --- a/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java +++ b/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java @@ -34,7 +34,7 @@ public ResponseEntity chatCompletions( @GetMapping("generations") public ResponseEntity imageGenerations( - @RequestParam(value = "prompt", defaultValue = Defaults.CHAT_COMPLETION_INPUT) String input + @RequestParam(value = "prompt", defaultValue = Defaults.IMAGE_GENERATION_TOPIC) String input ) throws Exception { String result = YChatService.getImageGenerationsAnswer(input); return ResponseEntity.ok(result); @@ -44,5 +44,6 @@ private static class Defaults { static final String COMPLETION_INPUT = "Say this is a test."; static final String CHAT_COMPLETION_INPUT = "Tell me one strength exercise"; static final String CHAT_COMPLETION_TOPIC = "fitness"; + static final String IMAGE_GENERATION_TOPIC = "ocean"; } } From 30d15827c92932a110295533589304a4fd58774d Mon Sep 17 00:00:00 2001 From: Renato Date: Tue, 14 Mar 2023 19:05:02 -0300 Subject: [PATCH 08/12] refactor: add JavaDoc for imageGenerations() --- ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt index 6c977b3..c6e2286 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt @@ -78,6 +78,19 @@ interface YChat { */ fun chatCompletions(): ChatCompletions + /** + * The image generations api is used to generate images based on a prompt. You input some text as a + * prompt, and the model will generate one or more images. + * + * You can configure the parameters of the completion before executing it. Example: + * ``` + * val result = YChat.create(apiKey).imageGenerations() + * .setResults(2) + * .setSize(1024x1024) + * .set... + * .execute("/image ocean") + * ``` + */ fun imageGenerations(): ImageGenerations /** From 8cd7d2985bdbd8d6a9ffbf1b4cd41a8ee1ed278b Mon Sep 17 00:00:00 2001 From: Renato Date: Tue, 14 Mar 2023 19:16:52 -0300 Subject: [PATCH 09/12] refactor:add integration test for the imageGenerations() entry point --- .../mapper/ImageGenerationsMapperTest.kt | 2 +- .../usecases/ImageGenerationsUseCaseTest.kt | 2 +- .../co/yml/ychat/entrypoint/YChatTest.kt | 20 +++++++++++++++++++ .../kotlin/infrastructure/MockStorage.kt | 2 ++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt index f7f128c..0ce66ff 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt @@ -15,7 +15,7 @@ class ImageGenerationsMapperTest { created = 12345, data = listOfImageGeneratedDto ) - assertEquals(listOfImageGeneratedDto, imageGenerationsDto.toImageGenerated()) + assertEquals(listOfImageGeneratedDto.map { it.url }, imageGenerationsDto.toImageGenerated()) } @Test diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt index 6ae49af..ad1ffe7 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt @@ -37,7 +37,7 @@ class ImageGenerationsUseCaseTest { val result = runBlocking { imageGenerationsUseCase.requestImageGenerations(params) } // assert - assertEquals("https://image-generated.test", result.last().url) + assertEquals("https://image-generated.test", result.last()) } @Test diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt index c0e264c..e61ab05 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt @@ -73,6 +73,26 @@ class YChatTest { assertEquals("This in indeed a test", result) } + @Test + fun `on imageGenerations execute method should return result successfully`() { + // arrange + val textResult = "This in indeed a test" + val imageGenerationsSuccessResult = MockStorage.imageGenerationsSuccessResult(textResult) + mockHttpEngine(imageGenerationsSuccessResult) + + // act + val result = runBlocking { + yChat.imageGenerations() + .setResults(1) + .setSize("256x256") + .setResponseFormat("url") + .execute("/image ocean") + } + + // assert + assertEquals("https://testlink.com/image-test.jps", result.first()) + } + private fun mockHttpEngine(result: String) { val httpEngine = MockEngine { respond( diff --git a/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt b/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt index 8c81b1c..6e31f9c 100644 --- a/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt +++ b/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt @@ -13,4 +13,6 @@ object MockStorage { "\"usage\":{\"prompt_tokens\":13,\"completion_tokens\":12,\"total_tokens\":25}," + "\"choices\":[{\"message\":{\"role\":\"assistant\",\"content\":\"$text\"}," + "\"finish_reason\":\"stop\",\"index\":0}]}" + + fun imageGenerationsSuccessResult(text: String) ="{\"created\":1678805561,\"data\":[{\"url\":\"https://testlink.com/image-test.jps\"}]}" } From 80a6938d235dcde92564c47ecb7059b6d1fb7844 Mon Sep 17 00:00:00 2001 From: Renato Date: Tue, 14 Mar 2023 19:23:01 -0300 Subject: [PATCH 10/12] refactor: README file of the JVM sample --- sample/jvm/README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sample/jvm/README.md b/sample/jvm/README.md index e870312..fbe18d4 100644 --- a/sample/jvm/README.md +++ b/sample/jvm/README.md @@ -42,4 +42,18 @@ This endpoint generates text based on the provided prompt and a specified topic. ##### Example: -`GET http://localhost:8080/api/ychat/chat-completions?input="Tell me an exercise plan"&topic=fitness` \ No newline at end of file +`GET http://localhost:8080/api/ychat/chat-completions?input="Tell me an exercise plan"&topic=fitness` + +### Image Generations Endpoint + +This endpoint generates images based on the provided prompt. + +##### Endpoint: http://localhost:[port_number]/api/ychat/generations + +##### Parameters: + +- `prompt`: The prompt for generating images. + +##### Example: + +`GET http://localhost:8080/api/ychat/generations?prompt="ocean" \ No newline at end of file From 895f0aa89c769a799c6d375ee3cd89b50a38dc7d Mon Sep 17 00:00:00 2001 From: Renato Date: Tue, 14 Mar 2023 19:26:26 -0300 Subject: [PATCH 11/12] refactor: adjust code formatting --- ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt b/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt index 6e31f9c..07950a1 100644 --- a/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt +++ b/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt @@ -14,5 +14,5 @@ object MockStorage { "\"choices\":[{\"message\":{\"role\":\"assistant\",\"content\":\"$text\"}," + "\"finish_reason\":\"stop\",\"index\":0}]}" - fun imageGenerationsSuccessResult(text: String) ="{\"created\":1678805561,\"data\":[{\"url\":\"https://testlink.com/image-test.jps\"}]}" + fun imageGenerationsSuccessResult(text: String) = "{\"created\":1678805561,\"data\":[{\"url\":\"https://testlink.com/image-test.jps\"}]}" } From 1f69fb9f23199a109580103e87385ebb481eca87 Mon Sep 17 00:00:00 2001 From: Koji Osugi Date: Wed, 15 Mar 2023 11:50:58 -0300 Subject: [PATCH 12/12] refactor: test adjustment --- .../commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt | 6 +++--- ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt index e61ab05..0b5be31 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt @@ -76,8 +76,8 @@ class YChatTest { @Test fun `on imageGenerations execute method should return result successfully`() { // arrange - val textResult = "This in indeed a test" - val imageGenerationsSuccessResult = MockStorage.imageGenerationsSuccessResult(textResult) + val imageUrl = "https://testlink.com/image-test.jpg" + val imageGenerationsSuccessResult = MockStorage.imageGenerationsSuccessResult(imageUrl) mockHttpEngine(imageGenerationsSuccessResult) // act @@ -90,7 +90,7 @@ class YChatTest { } // assert - assertEquals("https://testlink.com/image-test.jps", result.first()) + assertEquals("https://testlink.com/image-test.jpg", result.first()) } private fun mockHttpEngine(result: String) { diff --git a/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt b/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt index 07950a1..95d843f 100644 --- a/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt +++ b/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt @@ -14,5 +14,6 @@ object MockStorage { "\"choices\":[{\"message\":{\"role\":\"assistant\",\"content\":\"$text\"}," + "\"finish_reason\":\"stop\",\"index\":0}]}" - fun imageGenerationsSuccessResult(text: String) = "{\"created\":1678805561,\"data\":[{\"url\":\"https://testlink.com/image-test.jps\"}]}" + fun imageGenerationsSuccessResult(text: String) = + "{\"created\":1678805561,\"data\":[{\"url\":\"$text\"}]}" }