@@ -281,6 +281,14 @@ class ScreenCaptureService : Service() {
281281 val result = callVercelApi(modelName, apiKey, chatHistory, inputContent)
282282 responseText = result.first
283283 errorMessage = result.second
284+ } else if (apiProvider == ApiProvider .MISTRAL ) {
285+ val result = callMistralApi(modelName, apiKey, chatHistory, inputContent)
286+ responseText = result.first
287+ errorMessage = result.second
288+ } else if (apiProvider == ApiProvider .PUTER ) {
289+ val result = callPuterApi(modelName, apiKey, chatHistory, inputContent)
290+ responseText = result.first
291+ errorMessage = result.second
284292 } else {
285293 val generativeModel = GenerativeModel (
286294 modelName = modelName,
@@ -781,13 +789,20 @@ private suspend fun callVercelApi(modelName: String, apiKey: String, chatHistory
781789 ignoreUnknownKeys = true
782790 }
783791
792+ val currentModelOption = com.google.ai.sample.ModelOption .values().find { it.modelName == modelName }
793+ val supportsScreenshot = currentModelOption?.supportsScreenshot ? : true
794+
784795 try {
785796 val messages = (chatHistory + inputContent).map { content ->
786- val parts = content.parts.map { part ->
797+ val parts = content.parts.mapNotNull { part ->
787798 when (part) {
788799 is TextPart -> VercelTextContent (content = part.text)
789- is ImagePart -> VercelImageContent (content = VercelImageUrl (url = part.image.toBase64()))
790- else -> VercelTextContent (content = " " ) // Or handle other part types appropriately
800+ is ImagePart -> {
801+ if (supportsScreenshot) {
802+ VercelImageContent (content = VercelImageUrl (url = part.image.toBase64()))
803+ } else null
804+ }
805+ else -> null
791806 }
792807 }
793808 VercelMessage (role = if (content.role == " user" ) " user" else " assistant" , content = parts)
@@ -829,3 +844,181 @@ private suspend fun callVercelApi(modelName: String, apiKey: String, chatHistory
829844
830845 return Pair (responseText, errorMessage)
831846}
847+
848+ // Data classes for Mistral API in Service
849+ @Serializable
850+ data class ServiceMistralRequest (
851+ val model : String ,
852+ val messages : List <ServiceMistralMessage >,
853+ val max_tokens : Int = 4096 ,
854+ val temperature : Double = 0.7 ,
855+ val top_p : Double = 1.0 ,
856+ val stream : Boolean = false
857+ )
858+
859+ @Serializable
860+ data class ServiceMistralMessage (
861+ val role : String ,
862+ val content : List <ServiceMistralContent >
863+ )
864+
865+ @Serializable
866+ @JsonClassDiscriminator(" type" )
867+ sealed class ServiceMistralContent
868+
869+ @Serializable
870+ @SerialName(" text" )
871+ data class ServiceMistralTextContent (@SerialName(" text" ) val text : String ) : ServiceMistralContent()
872+
873+ @Serializable
874+ @SerialName(" image_url" )
875+ data class ServiceMistralImageContent (@SerialName(" image_url" ) val imageUrl : ServiceMistralImageUrl ) : ServiceMistralContent()
876+
877+ @Serializable
878+ data class ServiceMistralImageUrl (val url : String )
879+
880+ @Serializable
881+ data class ServiceMistralResponse (
882+ val choices : List <ServiceMistralChoice >
883+ )
884+
885+ @Serializable
886+ data class ServiceMistralChoice (
887+ val message : ServiceMistralResponseMessage
888+ )
889+
890+ @Serializable
891+ data class ServiceMistralResponseMessage (
892+ val role : String ,
893+ val content : String
894+ )
895+
896+ private suspend fun callMistralApi (modelName : String , apiKey : String , chatHistory : List <Content >, inputContent : Content ): Pair <String ?, String ?> {
897+ var responseText: String? = null
898+ var errorMessage: String? = null
899+
900+ val json = Json {
901+ serializersModule = SerializersModule {
902+ polymorphic(ServiceMistralContent ::class ) {
903+ subclass(ServiceMistralTextContent ::class , ServiceMistralTextContent .serializer())
904+ subclass(ServiceMistralImageContent ::class , ServiceMistralImageContent .serializer())
905+ }
906+ }
907+ ignoreUnknownKeys = true
908+ }
909+
910+ val currentModelOption = com.google.ai.sample.ModelOption .values().find { it.modelName == modelName }
911+ val supportsScreenshot = currentModelOption?.supportsScreenshot ? : true
912+
913+ try {
914+ val apiMessages = mutableListOf<ServiceMistralMessage >()
915+
916+ // Combine history and input, but handle system role if needed
917+ (chatHistory + inputContent).forEach { content ->
918+ val parts = content.parts.mapNotNull { part ->
919+ when (part) {
920+ is TextPart -> if (part.text.isNotBlank()) ServiceMistralTextContent (text = part.text) else null
921+ is ImagePart -> {
922+ if (supportsScreenshot) {
923+ ServiceMistralImageContent (imageUrl = ServiceMistralImageUrl (url = part.image.toBase64()))
924+ } else null
925+ }
926+ else -> null
927+ }
928+ }
929+ if (parts.isNotEmpty()) {
930+ val role = when (content.role) {
931+ " user" -> " user"
932+ " system" -> " system"
933+ else -> " assistant"
934+ }
935+ apiMessages.add(ServiceMistralMessage (role = role, content = parts))
936+ }
937+ }
938+
939+ val requestBody = ServiceMistralRequest (
940+ model = modelName,
941+ messages = apiMessages
942+ )
943+
944+ val client = OkHttpClient ()
945+ val mediaType = " application/json" .toMediaType()
946+ val jsonBody = json.encodeToString(ServiceMistralRequest .serializer(), requestBody)
947+
948+ val request = Request .Builder ()
949+ .url(" https://api.mistral.ai/v1/chat/completions" )
950+ .post(jsonBody.toRequestBody(mediaType))
951+ .addHeader(" Content-Type" , " application/json" )
952+ .addHeader(" Authorization" , " Bearer $apiKey " )
953+ .build()
954+
955+ client.newCall(request).execute().use { response ->
956+ val responseBody = response.body?.string()
957+ if (! response.isSuccessful) {
958+ Log .e(" ScreenCaptureService" , " Mistral API Error ($response .code): $responseBody " )
959+ errorMessage = " Mistral Error ${response.code} : $responseBody "
960+ } else {
961+ if (responseBody != null ) {
962+ val mistralResponse = json.decodeFromString(ServiceMistralResponse .serializer(), responseBody)
963+ responseText = mistralResponse.choices.firstOrNull()?.message?.content ? : " No response from model"
964+ } else {
965+ errorMessage = " Empty response body from Mistral"
966+ }
967+ }
968+ }
969+ } catch (e: Exception ) {
970+ errorMessage = e.localizedMessage ? : " Mistral API call failed"
971+ Log .e(" ScreenCaptureService" , " Mistral API failure" , e)
972+ }
973+
974+ return Pair (responseText, errorMessage)
975+ }
976+
977+ private suspend fun callPuterApi (modelName : String , apiKey : String , chatHistory : List <Content >, inputContent : Content ): Pair <String ?, String ?> {
978+ var responseText: String? = null
979+ var errorMessage: String? = null
980+
981+ val currentModelOption = com.google.ai.sample.ModelOption .values().find { it.modelName == modelName }
982+ val supportsScreenshot = currentModelOption?.supportsScreenshot ? : true
983+
984+ try {
985+ val apiMessages = mutableListOf< com.google.ai.sample.network.PuterMessage > ()
986+
987+ // Combine history and input, but handle system role if needed
988+ (chatHistory + inputContent).forEach { content ->
989+ val parts = content.parts.mapNotNull { part ->
990+ when (part) {
991+ is TextPart -> if (part.text.isNotBlank()) com.google.ai.sample.network.PuterTextContent (text = part.text) else null
992+ is ImagePart -> {
993+ if (supportsScreenshot) {
994+ val base64Uri = com.google.ai.sample.network.PuterApiClient .bitmapToBase64DataUri(part.image)
995+ com.google.ai.sample.network.PuterImageContent (image_url = com.google.ai.sample.network.PuterImageUrl (url = base64Uri))
996+ } else null
997+ }
998+ else -> null
999+ }
1000+ }
1001+ if (parts.isNotEmpty()) {
1002+ val role = when (content.role) {
1003+ " user" -> " user"
1004+ " system" -> " system"
1005+ else -> " assistant"
1006+ }
1007+ apiMessages.add(com.google.ai.sample.network.PuterMessage (role = role, content = parts))
1008+ }
1009+ }
1010+
1011+ val requestBody = com.google.ai.sample.network.PuterRequest (
1012+ model = modelName,
1013+ messages = apiMessages
1014+ )
1015+
1016+ responseText = com.google.ai.sample.network.PuterApiClient .call(apiKey, requestBody)
1017+
1018+ } catch (e: Exception ) {
1019+ errorMessage = e.localizedMessage ? : " Puter API call failed"
1020+ Log .e(" ScreenCaptureService" , " Puter API failure" , e)
1021+ }
1022+
1023+ return Pair (responseText, errorMessage)
1024+ }
0 commit comments