Skip to content

Commit eca1efe

Browse files
committed
feat(message): map stream response gpt3.5turbo
1 parent 6c8d456 commit eca1efe

26 files changed

+456
-60
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
.externalNativeBuild
1414
.cxx
1515
local.properties
16+
/app/google-services.json

app/build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ plugins {
88
// DI
99
id 'kotlin-kapt'
1010
id 'com.google.dagger.hilt.android'
11+
12+
id 'kotlinx-serialization'
1113
}
1214

1315
android {
@@ -91,6 +93,14 @@ dependencies {
9193
kapt "com.google.dagger:hilt-compiler:2.44"
9294
implementation 'com.google.code.gson:gson:2.9.0'
9395
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
96+
97+
// Networking
98+
def retrofit_version = '2.9.0'
99+
def okhttp_version = '4.10.0'
100+
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
101+
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
102+
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
103+
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
94104
}
95105

96106
// Allow references to generated code

app/src/main/java/com/chatgptlite/wanted/MainActivity.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ class MainActivity : ComponentActivity() {
7474
scope.launch {
7575
drawerState.close()
7676
}
77-
78-
println(it)
7977
},
8078
onNewChatClicked = {
8179
scope.launch {

app/src/main/java/com/chatgptlite/wanted/MainViewModel.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package com.chatgptlite.wanted
22

3-
import androidx.activity.compose.BackHandler
43
import androidx.lifecycle.ViewModel
54
import kotlinx.coroutines.flow.MutableStateFlow
65
import kotlinx.coroutines.flow.asStateFlow
7-
import kotlinx.coroutines.launch
86

97
/**
108
* Used to communicate between screens.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.chatgptlite.wanted.constants
2+
3+
const val baseUrlOpenAI = "https://api.openai.com/"
4+
const val textCompletionsEndpoint = "v1/completions"
5+
const val textCompletionsTurboEndpoint = "v1/chat/completions"
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.chatgptlite.wanted.constants
22

3+
const val openAIApiKey = "api_key_here"
4+
35
const val urlToImageAppIcon = "https://res.cloudinary.com/apideck/image/upload/v1672442492/marketplaces/ckhg56iu1mkpc0b66vj7fsj3o/listings/-4-ans_frontend_assets.images.poe.app_icon.png-26-8aa0a2e5f237894d_tbragv.png"
46
const val urlToImageAuthor = "https://avatars.githubusercontent.com/u/60530946?v=4"
57
const val urlToAvatarGPT = "https://gptapk.com/wp-content/uploads/2023/02/chatgpt-icon.png"
6-
const val urlToGithub = "https://github.com/lambiengcode"
8+
const val urlToGithub = "https://github.com/lambiengcode"
9+
10+
const val matchResultTurboString = "\"content\":"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.chatgptlite.wanted.data.api
2+
3+
import com.chatgptlite.wanted.constants.textCompletionsTurboEndpoint
4+
import com.google.gson.JsonObject
5+
import okhttp3.ResponseBody
6+
import retrofit2.Call
7+
import retrofit2.http.*
8+
9+
interface OpenAIApi {
10+
@POST(textCompletionsTurboEndpoint)
11+
@Streaming
12+
fun textCompletionsWithStream(@Body body: JsonObject): Call<ResponseBody>
13+
}

app/src/main/java/com/chatgptlite/wanted/data/remote/MessageRepositoryImpl.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.chatgptlite.wanted.data.remote
33
import com.chatgptlite.wanted.constants.messageCollection
44
import com.chatgptlite.wanted.models.MessageModel
55
import com.google.firebase.firestore.FirebaseFirestore
6+
import com.google.firebase.firestore.Query
67
import com.google.firebase.firestore.QuerySnapshot
78
import kotlinx.coroutines.channels.awaitClose
89
import kotlinx.coroutines.flow.Flow
@@ -16,7 +17,7 @@ class MessageRepositoryImpl @Inject constructor(
1617
override fun fetchMessages(conversationId: String): Flow<List<MessageModel>> = callbackFlow {
1718
val result: QuerySnapshot =
1819
fsInstance.collection(messageCollection).whereEqualTo("conversationId", conversationId)
19-
.get().await()
20+
.orderBy("createdAt", Query.Direction.DESCENDING).get().await()
2021

2122
if (result.documents.isNotEmpty()) {
2223
val documents = result.documents
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.chatgptlite.wanted.data.remote
2+
3+
import com.chatgptlite.wanted.models.TextCompletionsParam
4+
import kotlinx.coroutines.flow.Flow
5+
6+
interface OpenAIRepository {
7+
fun textCompletionsWithStream(params: TextCompletionsParam): Flow<String>
8+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.chatgptlite.wanted.data.remote
2+
3+
import com.chatgptlite.wanted.constants.matchResultTurboString
4+
import com.chatgptlite.wanted.data.api.OpenAIApi
5+
import com.chatgptlite.wanted.models.TextCompletionsParam
6+
import com.chatgptlite.wanted.models.toJson
7+
import com.google.gson.Gson
8+
import com.google.gson.JsonObject
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.channels.awaitClose
11+
import kotlinx.coroutines.flow.Flow
12+
import kotlinx.coroutines.flow.callbackFlow
13+
import kotlinx.coroutines.withContext
14+
import okio.IOException
15+
import org.json.JSONException
16+
import org.json.JSONObject
17+
import javax.inject.Inject
18+
19+
20+
@Suppress("UNREACHABLE_CODE")
21+
class OpenAIRepositoryImpl @Inject constructor(
22+
private val openAIApi: OpenAIApi,
23+
): OpenAIRepository {
24+
override fun textCompletionsWithStream(params: TextCompletionsParam): Flow<String> = callbackFlow {
25+
withContext(Dispatchers.IO) {
26+
val response = openAIApi.textCompletionsWithStream(params.toJson()).execute()
27+
28+
if (response.isSuccessful) {
29+
val input = response.body()?.byteStream()?.bufferedReader() ?: throw Exception()
30+
try {
31+
while (true) {
32+
val line =
33+
withContext(Dispatchers.IO) {
34+
input.readLine()
35+
} ?: continue
36+
if (line == "data: [DONE]") {
37+
close()
38+
} else if (line.startsWith("data:")) {
39+
try {
40+
// Handle & convert data -> emit to client
41+
val value = lookupDataFromResponseTurbo(line)
42+
trySend(value)
43+
} catch (e: Exception) {
44+
e.printStackTrace()
45+
}
46+
}
47+
}
48+
} catch (e: IOException) {
49+
throw Exception(e)
50+
} finally {
51+
withContext(Dispatchers.IO) {
52+
input.close()
53+
}
54+
55+
awaitClose {
56+
close()
57+
}
58+
}
59+
} else {
60+
if (!response.isSuccessful) {
61+
var jsonObject: JSONObject? = null
62+
try {
63+
jsonObject = JSONObject(response.errorBody()!!.string())
64+
} catch (e: JSONException) {
65+
e.printStackTrace()
66+
}
67+
}
68+
trySend("Failure! Try again.")
69+
awaitClose {
70+
close()
71+
}
72+
}
73+
}
74+
close()
75+
}
76+
77+
private fun lookupDataFromResponseTurbo(jsonString: String): String {
78+
val splitsJsonString = jsonString.split("[{")
79+
80+
val indexOfResult: Int = splitsJsonString.indexOfLast {
81+
it.contains(matchResultTurboString)
82+
}
83+
84+
val textSplits = if (indexOfResult == -1) listOf() else splitsJsonString[indexOfResult].split(",")
85+
86+
val indexOfText: Int = textSplits.indexOfLast {
87+
it.contains(matchResultTurboString)
88+
}
89+
90+
if (indexOfText != -1) {
91+
try {
92+
val gson = Gson()
93+
val jsonObject = gson.fromJson("{${textSplits[indexOfText]}}", JsonObject::class.java)
94+
95+
return jsonObject.getAsJsonObject("delta").get("content").asString
96+
} catch (e: java.lang.Exception) {
97+
println(e.localizedMessage)
98+
}
99+
}
100+
101+
return ""
102+
}
103+
}

0 commit comments

Comments
 (0)