fuzzyworld

ai-chat-android-auto / files / app / src / main / java / net / fuzzyworld / aichat / GeminiProvider.kt

GeminiProvider.kt

4.2 KB · history · blame

1package net.fuzzyworld.aichat
2
3import okhttp3.Call
4import okhttp3.Callback
5import okhttp3.MediaType.Companion.toMediaType
6import okhttp3.Request
7import okhttp3.RequestBody.Companion.toRequestBody
8import okhttp3.Response
9import org.json.JSONArray
10import org.json.JSONObject
11import java.io.IOException
12
13object GeminiProvider : AiProvider {
14 override val id = "gemini"
15 override val displayName = "Google Gemini"
16 override val defaultBaseUrl = "https://generativelanguage.googleapis.com"
17 override val defaultModel = "gemini-2.0-flash"
18 override val requiresApiKey = true
19 override val allowsBaseUrlOverride = false
20 override val supportsWebSearch = false
21
22 override fun ask(
23 apiKey: String,
24 baseUrl: String,
25 model: String,
26 question: String,
27 onResult: (String?) -> Unit,
28 ) {
29 val payload = JSONObject().apply {
30 put("system_instruction", JSONObject().apply {
31 put("parts", JSONArray().apply {
32 put(JSONObject().apply { put("text", SYSTEM_PROMPT) })
33 })
34 })
35 put("contents", JSONArray().apply {
36 put(JSONObject().apply {
37 put("role", "user")
38 put("parts", JSONArray().apply {
39 put(JSONObject().apply { put("text", question) })
40 })
41 })
42 })
43 }.toString().toRequestBody("application/json".toMediaType())
44
45 val request = Request.Builder()
46 .url("$baseUrl/v1beta/models/$model:generateContent?key=$apiKey")
47 .post(payload)
48 .build()
49
50 Http.client.newCall(request).enqueue(object : Callback {
51 override fun onFailure(call: Call, e: IOException) = onResult(null)
52 override fun onResponse(call: Call, response: Response) {
53 response.use {
54 if (!it.isSuccessful) { onResult(null); return }
55 try {
56 val text = JSONObject(it.body!!.string())
57 .getJSONArray("candidates")
58 .getJSONObject(0)
59 .getJSONObject("content")
60 .getJSONArray("parts")
61 .getJSONObject(0)
62 .getString("text")
63 onResult(text.trim().ifEmpty { null })
64 } catch (_: Exception) { onResult(null) }
65 }
66 }
67 })
68 }
69
70 override fun listModels(
71 apiKey: String,
72 baseUrl: String,
73 onResult: (List<ModelInfo>?) -> Unit,
74 ) {
75 val request = Request.Builder()
76 .url("$baseUrl/v1beta/models?key=$apiKey")
77 .get()
78 .build()
79
80 Http.client.newCall(request).enqueue(object : Callback {
81 override fun onFailure(call: Call, e: IOException) = onResult(null)
82 override fun onResponse(call: Call, response: Response) {
83 response.use {
84 if (!it.isSuccessful) { onResult(null); return }
85 try {
86 val arr = JSONObject(it.body!!.string()).getJSONArray("models")
87 val models = (0 until arr.length())
88 .map { i -> arr.getJSONObject(i) }
89 .filter { obj ->
90 val methods = obj.optJSONArray("supportedGenerationMethods")
91 methods != null && (0 until methods.length()).any { j ->
92 methods.getString(j) == "generateContent"
93 }
94 }
95 .map { obj ->
96 // "name" is "models/gemini-2.0-flash" — strip the prefix
97 val mid = obj.getString("name").removePrefix("models/")
98 ModelInfo(
99 id = mid,
100 displayName = obj.optString("displayName", mid),
101 )
102 }
103 onResult(models)
104 } catch (_: Exception) { onResult(null) }
105 }
106 }
107 })
108 }
109}