Skip to content

Commit c7b4cdc

Browse files
committed
feat: Add Edits API
1 parent 9b6440b commit c7b4cdc

File tree

18 files changed

+337
-1
lines changed

18 files changed

+337
-1
lines changed

ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package co.yml.ychat
22

33
import co.yml.ychat.entrypoint.features.ChatCompletions
44
import co.yml.ychat.entrypoint.features.Completion
5+
import co.yml.ychat.entrypoint.features.Edits
56
import co.yml.ychat.entrypoint.features.ImageGenerations
67
import co.yml.ychat.entrypoint.impl.YChatImpl
78
import kotlin.jvm.JvmStatic
@@ -93,6 +94,9 @@ interface YChat {
9394
*/
9495
fun imageGenerations(): ImageGenerations
9596

97+
98+
fun edits(): Edits
99+
96100
/**
97101
* Callback is an interface used for handling the results of an operation.
98102
* It provides two methods, `onSuccess` and `onError`, for handling the success

ychat/src/commonMain/kotlin/co/yml/ychat/data/api/ChatGptApi.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import co.yml.ychat.data.dto.ChatCompletionParamsDto
44
import co.yml.ychat.data.dto.ChatCompletionsDto
55
import co.yml.ychat.data.dto.CompletionDto
66
import co.yml.ychat.data.dto.CompletionParamsDto
7+
import co.yml.ychat.data.dto.EditsDto
8+
import co.yml.ychat.data.dto.EditsParamsDto
79
import co.yml.ychat.data.dto.ImageGenerationsDto
810
import co.yml.ychat.data.dto.ImageGenerationsParamsDto
911
import co.yml.ychat.data.infrastructure.ApiResult
@@ -15,4 +17,6 @@ internal interface ChatGptApi {
1517
suspend fun chatCompletions(paramsDto: ChatCompletionParamsDto): ApiResult<ChatCompletionsDto>
1618

1719
suspend fun imageGenerations(paramsDto: ImageGenerationsParamsDto): ApiResult<ImageGenerationsDto>
20+
21+
suspend fun edits(paramsDto: EditsParamsDto): ApiResult<EditsDto>
1822
}

ychat/src/commonMain/kotlin/co/yml/ychat/data/api/impl/ChatGptApiImpl.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import co.yml.ychat.data.dto.ChatCompletionParamsDto
55
import co.yml.ychat.data.dto.ChatCompletionsDto
66
import co.yml.ychat.data.dto.CompletionDto
77
import co.yml.ychat.data.dto.CompletionParamsDto
8+
import co.yml.ychat.data.dto.EditsDto
9+
import co.yml.ychat.data.dto.EditsParamsDto
810
import co.yml.ychat.data.dto.ImageGenerationsDto
911
import co.yml.ychat.data.dto.ImageGenerationsParamsDto
1012
import co.yml.ychat.data.infrastructure.ApiExecutor
@@ -36,4 +38,12 @@ internal class ChatGptApiImpl(private val apiExecutor: ApiExecutor) : ChatGptApi
3638
.setBody(paramsDto)
3739
.execute()
3840
}
41+
42+
override suspend fun edits(paramsDto: EditsParamsDto): ApiResult<EditsDto> {
43+
return apiExecutor
44+
.setEndpoint("v1/edits")
45+
.setHttpMethod(HttpMethod.Post)
46+
.setBody(paramsDto)
47+
.execute()
48+
}
3949
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package co.yml.ychat.data.dto
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
internal data class EditsChoiceDto(
8+
@SerialName("text")
9+
val text: String,
10+
@SerialName("index")
11+
val index: Int
12+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package co.yml.ychat.data.dto
2+
3+
import co.yml.ychat.domain.model.ImageGeneratedDto
4+
import kotlinx.serialization.SerialName
5+
import kotlinx.serialization.Serializable
6+
7+
@Serializable
8+
internal data class EditsDto(
9+
@SerialName("object")
10+
val objectType: String,
11+
@SerialName("created")
12+
val created: Long,
13+
@SerialName("choices")
14+
val choices: List<EditsChoiceDto>,
15+
@SerialName("usage")
16+
val usage: UsageDto,
17+
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package co.yml.ychat.data.dto
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
internal data class EditsParamsDto(
8+
@SerialName("model")
9+
val model: String,
10+
@SerialName("input")
11+
val input: String,
12+
@SerialName("instruction")
13+
val instruction: String,
14+
@SerialName("n")
15+
val results: Int = 1,
16+
@SerialName("temperature")
17+
val temperature: Double,
18+
@SerialName("top_p")
19+
val topP: Double,
20+
)

ychat/src/commonMain/kotlin/co/yml/ychat/di/module/LibraryModule.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import co.yml.ychat.data.storage.ChatLogStorage
77
import co.yml.ychat.di.provider.NetworkProvider
88
import co.yml.ychat.domain.usecases.ChatCompletionsUseCase
99
import co.yml.ychat.domain.usecases.CompletionUseCase
10+
import co.yml.ychat.domain.usecases.EditsUseCase
1011
import co.yml.ychat.domain.usecases.ImageGenerationsUseCase
1112
import co.yml.ychat.entrypoint.features.ChatCompletions
1213
import co.yml.ychat.entrypoint.features.Completion
14+
import co.yml.ychat.entrypoint.features.Edits
1315
import co.yml.ychat.entrypoint.features.ImageGenerations
1416
import co.yml.ychat.entrypoint.impl.ChatCompletionsImpl
1517
import co.yml.ychat.entrypoint.impl.CompletionImpl
18+
import co.yml.ychat.entrypoint.impl.EditsImpl
1619
import co.yml.ychat.entrypoint.impl.ImageGenerationsImpl
1720
import kotlinx.coroutines.Dispatchers
1821
import org.koin.core.module.Module
@@ -27,12 +30,14 @@ internal class LibraryModule(private val apiKey: String) {
2730
factory<Completion> { CompletionImpl(Dispatchers.Default, get()) }
2831
factory<ChatCompletions> { ChatCompletionsImpl(Dispatchers.Default, get()) }
2932
factory<ImageGenerations> { ImageGenerationsImpl(Dispatchers.Default, get()) }
33+
factory<Edits> { EditsImpl(Dispatchers.Default, get()) }
3034
}
3135

3236
private val domainModule = module {
3337
factory { CompletionUseCase(get(), get()) }
3438
factory { ChatCompletionsUseCase(get()) }
3539
factory { ImageGenerationsUseCase(get()) }
40+
factory { EditsUseCase(get()) }
3641
}
3742

3843
private val dataModule = module {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package co.yml.ychat.domain.mapper
2+
3+
import co.yml.ychat.data.dto.EditsDto
4+
import co.yml.ychat.data.dto.EditsParamsDto
5+
import co.yml.ychat.domain.model.EditsParams
6+
7+
internal fun EditsDto.toEditsModel(): List<String> {
8+
return this.choices.map { it.text }
9+
}
10+
11+
internal fun EditsParams.toEditsParamsDto(): EditsParamsDto {
12+
return EditsParamsDto(
13+
model = this.model,
14+
input = this.input,
15+
instruction = this.instruction,
16+
results = this.results,
17+
temperature = this.temperature,
18+
topP = this.topP
19+
)
20+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package co.yml.ychat.domain.model
2+
3+
internal data class EditsParams(
4+
var model: String = "text-davinci-edit-001",
5+
var input: String = "",
6+
var instruction: String = "",
7+
var results: Int = 1,
8+
var temperature: Double = 1.0,
9+
var topP: Double = 1.0,
10+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package co.yml.ychat.domain.usecases
2+
3+
import co.yml.ychat.data.api.ChatGptApi
4+
import co.yml.ychat.domain.mapper.toEditsModel
5+
import co.yml.ychat.domain.mapper.toEditsParamsDto
6+
import co.yml.ychat.domain.mapper.toImageGenerated
7+
import co.yml.ychat.domain.mapper.toImageGenerationsParamsDto
8+
import co.yml.ychat.domain.model.EditsParams
9+
import co.yml.ychat.domain.model.ImageGenerationsParams
10+
11+
internal data class EditsUseCase(private val chatGptApi: ChatGptApi) {
12+
13+
suspend fun requestEdits(params: EditsParams): List<String> {
14+
val requestDto = params.toEditsParamsDto()
15+
val response = chatGptApi.edits(requestDto)
16+
return response.getBodyOrThrow().toEditsModel()
17+
}
18+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package co.yml.ychat.entrypoint.features
2+
3+
import co.yml.ychat.YChat
4+
import co.yml.ychat.data.exception.ChatGptException
5+
import kotlin.coroutines.cancellation.CancellationException
6+
7+
interface Edits {
8+
9+
fun setInput(input: String): Edits
10+
11+
fun setResults(results: Int): Edits
12+
13+
fun setModel(model: String): Edits
14+
15+
fun setTemperature(temperature: Double): Edits
16+
17+
fun setTopP(topP: Double): Edits
18+
19+
@Throws(CancellationException::class, ChatGptException::class)
20+
suspend fun execute(instruction: String): List<String>
21+
22+
fun execute(instruction: String, callback: YChat.Callback<List<String>>)
23+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package co.yml.ychat.entrypoint.impl
2+
3+
import co.yml.ychat.YChat
4+
import co.yml.ychat.domain.model.EditsParams
5+
import co.yml.ychat.domain.usecases.EditsUseCase
6+
import co.yml.ychat.entrypoint.features.Edits
7+
import kotlinx.coroutines.CoroutineDispatcher
8+
import kotlinx.coroutines.CoroutineScope
9+
import kotlinx.coroutines.SupervisorJob
10+
import kotlinx.coroutines.launch
11+
12+
internal class EditsImpl(
13+
private val dispatcher: CoroutineDispatcher,
14+
private val editsUseCase: EditsUseCase
15+
) : Edits {
16+
17+
private val scope by lazy { CoroutineScope(SupervisorJob() + dispatcher) }
18+
19+
private var params: EditsParams = EditsParams()
20+
21+
override fun setInput(input: String): Edits {
22+
params.input = input
23+
return this
24+
}
25+
26+
override fun setResults(results: Int): Edits {
27+
params.results = results
28+
return this
29+
}
30+
31+
override fun setModel(model: String): Edits {
32+
params.model = model
33+
return this
34+
}
35+
36+
override fun setTemperature(temperature: Double): Edits {
37+
params.temperature = temperature
38+
return this
39+
}
40+
41+
override fun setTopP(topP: Double): Edits {
42+
params.topP = topP
43+
return this
44+
}
45+
46+
override suspend fun execute(instruction: String): List<String> {
47+
params.instruction = instruction
48+
return editsUseCase.requestEdits(params)
49+
}
50+
51+
override fun execute(instruction: String, callback: YChat.Callback<List<String>>) {
52+
scope.launch {
53+
runCatching { execute(instruction) }
54+
.onSuccess { callback.onSuccess(it) }
55+
.onFailure { callback.onError(it) }
56+
}
57+
}
58+
}

ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/YChatImpl.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import co.yml.ychat.YChat
44
import co.yml.ychat.di.module.LibraryModule
55
import co.yml.ychat.entrypoint.features.ChatCompletions
66
import co.yml.ychat.entrypoint.features.Completion
7+
import co.yml.ychat.entrypoint.features.Edits
78
import co.yml.ychat.entrypoint.features.ImageGenerations
89
import org.koin.core.KoinApplication
910

@@ -27,4 +28,8 @@ internal class YChatImpl(apiKey: String) : YChat {
2728
override fun imageGenerations(): ImageGenerations {
2829
return koinApp.koin.get()
2930
}
31+
32+
override fun edits(): Edits {
33+
return koinApp.koin.get()
34+
}
3035
}

ychat/src/commonTest/kotlin/co/yml/ychat/di/LibraryModuleTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import co.yml.ychat.domain.usecases.CompletionUseCase
99
import co.yml.ychat.domain.usecases.ImageGenerationsUseCase
1010
import co.yml.ychat.entrypoint.features.ChatCompletions
1111
import co.yml.ychat.entrypoint.features.Completion
12+
import co.yml.ychat.entrypoint.features.Edits
1213
import co.yml.ychat.entrypoint.features.ImageGenerations
1314
import io.ktor.client.HttpClient
1415
import kotlin.test.AfterTest
@@ -43,5 +44,6 @@ class LibraryModuleTest : KoinTest {
4344
get<ChatCompletions>()
4445
get<ImageGenerationsUseCase>()
4546
get<ImageGenerations>()
47+
get<Edits>()
4648
}
4749
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package co.yml.ychat.domain.mapper
2+
3+
import co.yml.ychat.data.dto.EditsChoiceDto
4+
import co.yml.ychat.data.dto.EditsDto
5+
import co.yml.ychat.data.dto.ImageGenerationsDto
6+
import co.yml.ychat.data.dto.UsageDto
7+
import co.yml.ychat.domain.model.EditsParams
8+
import co.yml.ychat.domain.model.ImageGeneratedDto
9+
import co.yml.ychat.domain.model.ImageGenerationsParams
10+
import kotlin.test.Test
11+
import kotlin.test.assertEquals
12+
13+
class EditsMapperTest {
14+
15+
@Test
16+
fun `on convert EditsDto to EditsModel`() {
17+
val listOfChoicesDto = listOf(EditsChoiceDto("text 1", 1), EditsChoiceDto("text 2", 2))
18+
val editsDto = EditsDto(
19+
created = 12345,
20+
objectType = "edit",
21+
choices = listOfChoicesDto,
22+
usage = UsageDto(1, 1, 1)
23+
)
24+
assertEquals(listOfChoicesDto.map { it.text }, editsDto.toEditsModel())
25+
}
26+
27+
@Test
28+
fun `on convert EditsParams to EditsDto`() {
29+
val editsParams = EditsParams(input = "this is a test")
30+
assertEquals("text-davinci-edit-001", editsParams.toEditsParamsDto().model)
31+
assertEquals("this is a test", editsParams.toEditsParamsDto().input)
32+
assertEquals("", editsParams.toEditsParamsDto().instruction)
33+
assertEquals(1, editsParams.toEditsParamsDto().results)
34+
assertEquals(1.0, editsParams.toEditsParamsDto().temperature)
35+
assertEquals(1.0, editsParams.toEditsParamsDto().topP)
36+
}
37+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package co.yml.ychat.domain.model
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
6+
class EditsParamsTest {
7+
8+
@Test
9+
fun `on EditsParams verify default values`() {
10+
// arrange
11+
val params = EditsParams()
12+
13+
// assert
14+
assertEquals("text-davinci-edit-001", params.model)
15+
assertEquals("", params.input)
16+
assertEquals("", params.instruction)
17+
assertEquals(1, params.results)
18+
assertEquals(1.0, params.temperature)
19+
assertEquals(1.0, params.topP)
20+
}
21+
}

ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/ImageGenerationsParamsTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import kotlin.test.assertEquals
66
class ImageGenerationsParamsTest {
77

88
@Test
9-
fun `on ChatCompletionsParams verify default values`() {
9+
fun `on ImageGenerationsParams verify default values`() {
1010
// arrange
1111
val params = ImageGenerationsParams()
1212

0 commit comments

Comments
 (0)