11package com.developersbreach.kotlindictionarymultiplatform.core.network
22
33import com.developersbreach.kotlindictionarymultiplatform.Log
4- import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.ChatCompletionRequest
5- import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.ChatCompletionResponse
6- import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.ChatMessage
7- import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.FunctionDefinition
84import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails
95import io.ktor.client.HttpClient
106import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
11- import io.ktor.client.request.header
127import io.ktor.client.request.post
138import io.ktor.client.request.setBody
14- import io.ktor.client.statement.HttpResponse
159import io.ktor.client.statement.bodyAsText
1610import io.ktor.http.ContentType
17- import io.ktor.http.HttpHeaders
1811import io.ktor.http.contentType
1912import io.ktor.serialization.kotlinx.json.json
2013import kotlinx.serialization.json.Json
21- import kotlinx.serialization.json.decodeFromJsonElement
2214import kotlinx.serialization.json.jsonObject
15+ import kotlinx.serialization.decodeFromString
16+ import kotlinx.serialization.json.addJsonObject
17+ import kotlinx.serialization.json.buildJsonObject
18+ import kotlinx.serialization.json.jsonArray
19+ import kotlinx.serialization.json.jsonPrimitive
20+ import kotlinx.serialization.json.put
21+ import kotlinx.serialization.json.putJsonArray
2322
2423object KtorHttpClient {
25- private val json = Json { ignoreUnknownKeys = true }
24+
25+ private val json = Json {
26+ ignoreUnknownKeys = true
27+ prettyPrint = true
28+ }
2629
2730 private val client = HttpClient {
2831 install(ContentNegotiation ) {
2932 json()
3033 }
3134 }
3235
33- private val functionSchema = json.parseToJsonElement(
34- // Paste the full JSON schema for your function here
35- """
36- {
37- "type": "object",
38- "properties": {
39- "topicId": { "type": "string" },
40- "topicName": { "type": "string" },
41- "intro": { "type": "string" },
42- "syntax": {
43- "type": "object",
44- "properties": {
45- "signature": { "type": "string" },
46- "notes": { "type": "string" }
47- },
48- "required": ["signature"]
49- },
50- "sections": {
51- "type": "array",
52- "items": {
53- "type": "object",
54- "properties": {
55- "heading": { "type": "string" },
56- "content": { "type": "string" },
57- "codeExamples": {
58- "type": "array",
59- "items": {
60- "type": "object",
61- "properties": {
62- "description": { "type": "string" },
63- "code": { "type": "string" },
64- "language": { "type": "string" }
65- },
66- "required": ["code"]
67- }
68- }
69- },
70- "required": ["heading","content"]
71- }
72- },
73- "pitfalls": { "type": "array", "items": { "type": "string" } },
74- "relatedTopics": { "type": "array", "items": { "type": "string" } },
75- "metadata": { "type": "object", "additionalProperties": true }
76- },
77- "required": ["topicId","topicName","intro","syntax","sections"]
78- }
79- """ .trimIndent(),
80- )
81-
82- private val functionDef = FunctionDefinition (
83- name = " generate_kotlin_topic_details" ,
84- description = " Return a fully-featured Kotlin documentation object for a given topic" ,
85- parameters = functionSchema,
86- )
87-
88- /* *
89- * Calls the OpenAI ChatCompletion with function-calling to get topic details.
90- * @param topicId the topic identifier, e.g. "variables".
91- * @param apiKey your OpenAI API key.
92- */
9336 suspend fun generateTopicDetails (
9437 topicId : String ,
9538 apiKey : String ,
9639 ): KotlinTopicDetails {
97- // Prepare messages
98- val messages = listOf (
99- ChatMessage (" system" , " You are a Kotlin documentation generator." ),
100- ChatMessage (" user" , " Generate full Kotlin documentation for topic \" $topicId \" ." ),
101- )
40+ // Gemini-style prompt
41+ val prompt = """
42+ You are a Kotlin documentation generator.
43+ Generate a JSON object for the topic "$topicId " with the following structure:
44+
45+ {
46+ "topicId": "...",
47+ "topicName": "...",
48+ "intro": "...",
49+ "syntax": {
50+ "signature": "...",
51+ "notes": "..."
52+ },
53+ "sections": [
54+ {
55+ "heading": "...",
56+ "content": "...",
57+ "codeExamples": [
58+ {
59+ "description": "...",
60+ "code": "...",
61+ "language": "kotlin"
62+ }
63+ ]
64+ }
65+ ],
66+ "pitfalls": ["..."],
67+ "relatedTopics": ["..."],
68+ "metadata": {}
69+ }
70+
71+ Respond only with pure JSON. No explanation or markdown.
72+ """ .trimIndent()
10273
103- // Build request body
104- val request = ChatCompletionRequest (
105- model = " gpt-4o-mini" ,
106- messages = messages,
107- functions = listOf (functionDef),
108- functionCall = mapOf (" name" to functionDef.name),
109- )
74+ val requestBody = buildJsonObject {
75+ putJsonArray(" contents" ) {
76+ addJsonObject {
77+ putJsonArray(" parts" ) {
78+ addJsonObject {
79+ put(" text" , prompt)
80+ }
81+ }
82+ }
83+ }
84+ }
11085
111- // Execute HTTP request
112- val response: HttpResponse = client.post(" https://api.openai.com/v1/chat/completions" ) {
113- header(HttpHeaders .Authorization , " Bearer $apiKey " )
86+ val response = client.post(" https://generativelanguage.googleapis.com/v1/models/gemini-2.0-flash:generateContent?key=$apiKey " ) {
11487 contentType(ContentType .Application .Json )
115- setBody(json.encodeToString( ChatCompletionRequest .serializer(), request) )
88+ setBody(requestBody )
11689 }
11790
118- val text = response.bodyAsText()
119- Log .i(" RawResponse" , " RAW RESPONSE:\n $text " )
91+ val responseBody = response.bodyAsText()
92+ Log .i(" GeminiRawResponse" , responseBody)
93+
94+ // Parse root object
95+ val root = json.parseToJsonElement(responseBody).jsonObject
96+ val candidates = root[" candidates" ]?.jsonArray ? : error(" Missing candidates" )
97+ val firstCandidate = candidates.first().jsonObject
98+ val content = firstCandidate[" content" ]?.jsonObject ? : error(" Missing content" )
99+ val partsArray = content[" parts" ]?.jsonArray ? : error(" Missing parts array" )
100+ val part = partsArray.first().jsonObject
101+ val rawJson = part[" text" ]?.jsonPrimitive?.content ? : error(" Missing text in part" )
102+ Log .i(" RawJson" , rawJson)
120103
121- // Parse response
122- val chatResp = json.decodeFromString(ChatCompletionResponse .serializer(), text)
123- Log .i(" ChatResponse" , " $chatResp " )
124- val funcCall = chatResp.choices?.first()?.message?.functionCall ? : error(" No function call in response" )
104+ // Trim whitespace, remove code fences if any
105+ val cleanJson = rawJson.trim()
106+ .removePrefix(" ```json\n " )
107+ .removePrefix(" ```json" )
108+ .removePrefix(" json\n " )
109+ .removePrefix(" json" )
110+ .removeSuffix(" ```" )
111+ .trim()
125112
126- // The arguments field is a JSON string: parse and decode into our DTO
127- val argsJson = json.parseToJsonElement(funcCall.arguments)
128- return json.decodeFromJsonElement(argsJson.jsonObject)
113+ return json.decodeFromString(KotlinTopicDetails .serializer(), cleanJson)
129114 }
130115}
0 commit comments