Skip to content

Commit faab7a2

Browse files
committed
Refactor KtorHttpClient for Gemini API interaction:
- Introduce `GeminiApiService` for handling Gemini API requests. - Create `KtorClientProvider` to manage the Ktor `HttpClient` and `Json` instances. - Implement `GeminiPromptBuilder` to construct API request prompts and bodies. - Add `GeminiJsonParser` for extracting and cleaning JSON from API responses. - Move data models `CodeExample`, `Section`, and `Syntax` to separate files. - Remove unused OpenAI-related data classes from `KotlinTopicDetails.kt`. - Update `DetailRepository` to use the new `GeminiApiService`.
1 parent 0236d19 commit faab7a2

File tree

10 files changed

+216
-184
lines changed

10 files changed

+216
-184
lines changed

composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/core/network/KtorHttpClient.kt

Lines changed: 0 additions & 114 deletions
This file was deleted.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.developersbreach.kotlindictionarymultiplatform.core.network.api
2+
3+
import com.developersbreach.kotlindictionarymultiplatform.Log
4+
import com.developersbreach.kotlindictionarymultiplatform.core.network.client.KtorClientProvider
5+
import com.developersbreach.kotlindictionarymultiplatform.core.network.parser.GeminiJsonParser
6+
import com.developersbreach.kotlindictionarymultiplatform.core.network.request.GeminiPromptBuilder
7+
import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails
8+
import io.ktor.client.request.post
9+
import io.ktor.client.request.setBody
10+
import io.ktor.client.statement.bodyAsText
11+
import io.ktor.http.ContentType
12+
import io.ktor.http.contentType
13+
14+
object GeminiApiService {
15+
16+
suspend fun generateTopicDetails(
17+
topicId: String,
18+
apiKey: String,
19+
): KotlinTopicDetails {
20+
val prompt = GeminiPromptBuilder.buildRequest(topicId)
21+
val requestBody = GeminiPromptBuilder.buildRequestBody(prompt)
22+
23+
val response = KtorClientProvider.client.post(
24+
"https://generativelanguage.googleapis.com/v1/models/gemini-2.0-flash:generateContent?key=$apiKey",
25+
) {
26+
contentType(ContentType.Application.Json)
27+
setBody(requestBody)
28+
}
29+
30+
val responseBody = response.bodyAsText()
31+
Log.i("GeminiRawResponse", responseBody)
32+
33+
val cleanJson = GeminiJsonParser.extractCleanJson(responseBody, KtorClientProvider.json)
34+
Log.i("CleanJson", cleanJson)
35+
36+
return KtorClientProvider.json.decodeFromString(KotlinTopicDetails.serializer(), cleanJson)
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.developersbreach.kotlindictionarymultiplatform.core.network.client
2+
3+
import io.ktor.client.HttpClient
4+
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
5+
import io.ktor.serialization.kotlinx.json.json
6+
import kotlinx.serialization.json.Json
7+
8+
object KtorClientProvider {
9+
10+
val json = Json {
11+
ignoreUnknownKeys = true
12+
prettyPrint = true
13+
}
14+
15+
val client = HttpClient {
16+
install(ContentNegotiation) {
17+
json()
18+
}
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.developersbreach.kotlindictionarymultiplatform.core.network.parser
2+
3+
import kotlinx.serialization.json.Json
4+
import kotlinx.serialization.json.jsonObject
5+
import kotlinx.serialization.json.jsonArray
6+
import kotlinx.serialization.json.jsonPrimitive
7+
8+
object GeminiJsonParser {
9+
10+
fun extractCleanJson(
11+
rawResponse: String,
12+
json: Json,
13+
): String {
14+
val root = json.parseToJsonElement(rawResponse).jsonObject
15+
val text = root["candidates"]
16+
?.jsonArray?.firstOrNull()
17+
?.jsonObject?.get("content")
18+
?.jsonObject?.get("parts")
19+
?.jsonArray?.firstOrNull()
20+
?.jsonObject?.get("text")
21+
?.jsonPrimitive?.content
22+
?: error("Malformed Gemini response")
23+
24+
return text
25+
.removePrefix("```json\n")
26+
.removePrefix("```json")
27+
.removePrefix("json\n")
28+
.removePrefix("json")
29+
.removeSuffix("```")
30+
.trim()
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.developersbreach.kotlindictionarymultiplatform.core.network.request
2+
3+
import kotlinx.serialization.json.JsonObject
4+
import kotlinx.serialization.json.buildJsonObject
5+
import kotlinx.serialization.json.addJsonObject
6+
import kotlinx.serialization.json.put
7+
import kotlinx.serialization.json.putJsonArray
8+
9+
object GeminiPromptBuilder {
10+
11+
private val jsonSchema = """
12+
{
13+
"type": "object",
14+
"properties": {
15+
"topicId": { "type": "string" },
16+
"topicName": { "type": "string" },
17+
"intro": { "type": "string" },
18+
"syntax": {
19+
"type": "object",
20+
"properties": {
21+
"signature": { "type": "string" },
22+
"notes": { "type": "string" }
23+
},
24+
"required": ["signature"]
25+
},
26+
"sections": {
27+
"type": "array",
28+
"items": {
29+
"type": "object",
30+
"properties": {
31+
"heading": { "type": "string" },
32+
"content": { "type": "string" },
33+
"codeExamples": {
34+
"type": "array",
35+
"items": {
36+
"type": "object",
37+
"properties": {
38+
"description": { "type": "string" },
39+
"code": { "type": "string" },
40+
"language": { "type": "string" }
41+
},
42+
"required": ["code"]
43+
}
44+
}
45+
},
46+
"required": ["heading", "content"]
47+
}
48+
},
49+
"pitfalls": { "type": "array", "items": { "type": "string" } },
50+
"relatedTopics": { "type": "array", "items": { "type": "string" } },
51+
"metadata": { "type": "object", "additionalProperties": true }
52+
},
53+
"required": ["topicId", "topicName", "intro", "syntax", "sections"]
54+
}
55+
""".trimIndent()
56+
57+
fun buildRequest(
58+
topicId: String,
59+
): String {
60+
return """
61+
You are a Kotlin documentation generator.
62+
63+
Generate a detailed JSON object for the topic "$topicId".
64+
The output MUST strictly conform to the following JSON schema:
65+
66+
$jsonSchema
67+
68+
Requirements:
69+
- Respond only with pure JSON.
70+
- Do not include any Markdown formatting (no ```json).
71+
- Ensure all required fields are present.
72+
- Use valid Kotlin examples.
73+
- Include at least 3 sections with headings, content, and code examples.
74+
- Use the language "kotlin" in each code example.
75+
76+
Make sure the entire response is a **valid JSON** object matching the schema above.
77+
""".trimIndent()
78+
}
79+
80+
fun buildRequestBody(
81+
prompt: String,
82+
): JsonObject {
83+
return buildJsonObject {
84+
putJsonArray("contents") {
85+
addJsonObject {
86+
putJsonArray("parts") {
87+
addJsonObject {
88+
put("text", prompt)
89+
}
90+
}
91+
}
92+
}
93+
}
94+
}
95+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.developersbreach.kotlindictionarymultiplatform.data.detail.model
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class CodeExample(
7+
val description: String? = null,
8+
val code: String,
9+
val language: String = "kotlin",
10+
)

0 commit comments

Comments
 (0)