Переглянути джерело

ai page preimplementation

milan jurkulák 2 роки тому
батько
коміт
e12f8e6036

+ 3 - 0
build.gradle.kts

@@ -79,6 +79,9 @@ allprojects {
         implementation("com.github.skydoves:flexible-bottomsheet-material3:0.1.5")
         // fs watcher
         implementation("io.github.irgaly.kfswatch:kfswatch:1.3.0")
+        // tts
+        implementation("nl.marc-apps:tts:2.5.0")
+        implementation("nl.marc-apps:tts-compose:2.5.0")
         // bar code
 //        implementation("io.github.alexzhirkevich:qrose-oned:1.0.1")
         // flow extensions

+ 0 - 3
src/main/kotlin/eu/mjdev/desktop/Main.kt

@@ -62,9 +62,6 @@ fun main() = application(
             ),
         ) {
             val api: DesktopProvider = LocalDesktop.current
-//            api.aiProvider.ask("weather in prague").let {
-//                println(it)
-//            }
             MainWindow(
                 panelState = panelState,
                 controlCenterState = controlCenterState,

+ 8 - 6
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt

@@ -90,11 +90,6 @@ fun ControlCenter(
                 Row(
                     modifier = Modifier.fillMaxHeight().wrapContentSize(),
                 ) {
-                    Divider(
-                        modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
-                        color = controlCenterDividerColor,
-                        thickness = controlCenterDividerWidth
-                    )
                     Box(
                         contentAlignment = Alignment.TopEnd
                     ) {
@@ -127,11 +122,18 @@ fun ControlCenter(
                                 )
                             }
                         }
+                        Divider(
+                            modifier = Modifier
+                                .fillMaxHeight()
+                                .width(2.dp),
+                            color = Color.White.copy(0.1f),
+                            thickness = 2.dp
+                        )
                         RightShadow(
                             modifier = Modifier.padding(
                                 end = controlCenterIconSize.width + 16.dp
                             ).fillMaxHeight().wrapContentSize(),
-                            color = Color.Red,
+                            color = Color.Black,
                             alpha = 0.3f,
                             contentBackgroundColor = Color.Transparent
                         ) {

+ 4 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenterPage.kt

@@ -17,7 +17,10 @@ class ControlCenterPage(
     class ControlCenterPageScope(
         val backgroundColor: Color,
         val api: DesktopProvider
-    )
+    ) {
+        val scope
+            get() = api.scope
+    }
 
     companion object {
         @Composable

+ 67 - 4
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/AIPage.kt

@@ -1,20 +1,83 @@
 package eu.mjdev.desktop.components.controlcenter.pages
 
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.OutlinedTextField
+import androidx.compose.material.Text
+import androidx.compose.material.TextFieldDefaults
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Campaign
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import kotlinx.coroutines.launch
 
 @Suppress("FunctionName")
 fun AIPage() = ControlCenterPage(
     icon = Icons.Filled.Campaign,
-    name = "AI"
+    name = "AI",
 ) {
+    val questionsList = remember { mutableStateListOf<String>() }
+    val request = remember { mutableStateOf("") }
+    val onDone: (String) -> Unit = { what ->
+        if (what.isNotEmpty()) {
+            questionsList.add(what)
+            api.aiProvider.ask(what) { _, res ->
+                talk(res)
+            }
+        }
+    }
     Box(
         modifier = Modifier.fillMaxSize()
+            .background(backgroundColor)
     ) {
-
+        Row {
+            Column(
+                modifier = Modifier.fillMaxSize()
+                    .padding(16.dp)
+            ) {
+                LazyColumn(
+                    modifier = Modifier.fillMaxWidth()
+                        .weight(1f)
+                        .background(Color.Black.copy(alpha = 0.3f), RoundedCornerShape(8.dp)),
+                ) {
+                    itemsIndexed(questionsList) { idx, text ->
+                        Text(
+                            modifier = Modifier.padding(4.dp)
+                                .fillMaxWidth()
+                                .background(Color.White.copy(alpha = 0.3f), RoundedCornerShape(8.dp))
+                                .padding(4.dp),
+                            text = "${idx + 1}. $text"
+                        )
+                    }
+                }
+                OutlinedTextField(
+                    modifier = Modifier.padding(top = 8.dp).fillMaxWidth(),
+                    value = request.value,
+                    onValueChange = { t: String ->
+                        request.value = t
+                        if (t.contains("\n")) {
+                            scope.launch {
+                                onDone(t.replace("\n", ""))
+                            }
+                            request.value = ""
+                        }
+                    },
+                    maxLines = 4,
+                    minLines = 4,
+                    shape = RoundedCornerShape(8.dp),
+                    colors = TextFieldDefaults.outlinedTextFieldColors(
+                        backgroundColor = Color.White.copy(alpha = 0.3f)
+                    )
+                )
+            }
+        }
     }
 }

+ 59 - 14
src/main/kotlin/eu/mjdev/desktop/provider/AIProvider.kt

@@ -3,23 +3,67 @@ package eu.mjdev.desktop.provider
 import dev.shreyaspatil.ai.client.generativeai.GenerativeModel
 import dev.shreyaspatil.ai.client.generativeai.type.content
 import eu.mjdev.desktop.extensions.Custom.loadKey
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import nl.marc_apps.tts.TextToSpeechFactory
+import nl.marc_apps.tts.experimental.ExperimentalDesktopTarget
 
-@Suppress("MemberVisibilityCanBePrivate", "unused")
+@Suppress("MemberVisibilityCanBePrivate", "unused", "UNUSED_PARAMETER", "RemoveEmptyClassBody")
 class AIProvider(
-    var plugin: IAIPlugin = AiPluginNull()
+    val scope: CoroutineScope,
+    var pluginAI: IAIPlugin = AiPluginNull(scope),
+    val pluginSTT: ISTTPlugin = STTPluginNull(scope),
+    val pluginTTS: TTSPlugin = TTSPluginMain(scope)
 ) {
-    fun ask(question: String): String = plugin.ask(question)
+    fun ask(
+        question: String,
+        block: AIProvider.(question: String, result: String) -> Unit
+    ) = scope.launch {
+        pluginAI.ask(question).also { result ->
+            block.invoke(this@AIProvider, question, result)
+        }
+    }
+
+    fun talk(text: String, clearQueue: Boolean = false) =
+        pluginTTS.talk(text, clearQueue)
 
     interface IAIPlugin {
-        fun ask(question: String): String
+        suspend fun ask(question: String): String
+    }
+
+    interface ISTTPlugin {
+    }
+
+    interface TTSPlugin {
+        fun talk(text: String, clearQueue: Boolean = false)
+    }
+
+    class STTPluginNull(scope: CoroutineScope) : ISTTPlugin {
+    }
+
+    @OptIn(ExperimentalDesktopTarget::class)
+    class TTSPluginMain(
+        val scope: CoroutineScope
+    ) : TTSPlugin {
+        private val textToSpeech = scope.async {
+            TextToSpeechFactory().createOrNull()
+        }
+
+        override fun talk(text: String, clearQueue: Boolean) {
+            scope.launch {
+                textToSpeech.await()?.say(text, clearQueue)
+            }
+        }
     }
 
-    class AiPluginNull : IAIPlugin {
-        override fun ask(question: String): String = ""
+    class AiPluginNull(scope: CoroutineScope) : IAIPlugin {
+        override suspend fun ask(question: String): String = ""
     }
 
-    class AiPluginGemini : IAIPlugin {
+    class AiPluginGemini(
+        val scope: CoroutineScope
+    ) : IAIPlugin {
         private val generativeModel: GenerativeModel by lazy {
             GenerativeModel(
                 modelName = "gemini-1.5-pro-latest",
@@ -27,16 +71,17 @@ class AIProvider(
             )
         }
 
-        override fun ask(question: String): String = runBlocking {
+        override suspend fun ask(question: String): String = scope.async {
+            var error: Throwable? = null
             runCatching {
                 generativeModel.generateContent(content {
                     text(question)
                 }).text
-            }.onFailure {
-                println(it)
+            }.onFailure { e ->
+                error = e
             }.getOrElse {
-                ""
-            }
-        }.orEmpty()
+                error?.message ?: ""
+            } ?: ""
+        }.await()
     }
 }

+ 2 - 2
src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt

@@ -13,7 +13,7 @@ import eu.mjdev.desktop.helpers.adb.AdbDiscover.Companion.adbDevicesHandler
 import eu.mjdev.desktop.helpers.managers.ConnectivityManager
 import eu.mjdev.desktop.helpers.managers.FileSystemWatcher
 import eu.mjdev.desktop.helpers.managers.KCEFHelper
-import eu.mjdev.desktop.provider.AIProvider.AiPluginNull
+import eu.mjdev.desktop.provider.AIProvider.AiPluginGemini
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import java.awt.GraphicsDevice
@@ -32,7 +32,7 @@ class DesktopProvider(
     val connection: ConnectivityManager = ConnectivityManager(),
     val scriptManager: ScriptEngineManager = ScriptEngineManager(),
     val kcefHelper: KCEFHelper = KCEFHelper(scope),
-    val aiProvider: AIProvider = AIProvider(AiPluginNull())
+    val aiProvider: AIProvider = AIProvider(scope, AiPluginGemini(scope))
 ) {
     private val __currentUser: User by lazy { User.load() }
     private val _currentUser: MutableState<User> = mutableStateOf(__currentUser)