Milan Jurkulak 1 год назад
Родитель
Сommit
84fe461420
41 измененных файлов с 216 добавлено и 279 удалено
  1. 1 6
      .gitignore
  2. 6 2
      composeApp/build.gradle.kts
  3. 13 9
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/context/DesktopContext.kt
  4. 0 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/main/MainView.kt
  5. 0 70
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/ai/AIManager.kt
  6. 0 49
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginGemini.kt
  7. 11 11
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginAndroid.kt
  8. 6 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/keys/KeysManager.kt
  9. 0 31
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/os/base/OSManagerStub.kt
  10. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/ai/AIPage.kt
  11. 3 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/desktoppanel/DesktopPanel.kt
  12. 1 0
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/desktoppanel/applets/DesktopPanelDateTime.kt
  13. 2 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/DesktopContextScope.kt
  14. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/EmptyDesktopContext.kt
  15. 0 5
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/CustomExt.kt
  16. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/interfaces/IDesktopContext.kt
  17. 12 8
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/AiManager.kt
  18. 3 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/ActionCalculator.kt
  19. 18 0
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/ActionCurrentTime.kt
  20. 17 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/base/ActionProviderScope.kt
  21. 6 13
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/base/ActionsProvider.kt
  22. 5 5
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/base/IAiManager.kt
  23. 0 13
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AIPlugin.kt
  24. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginEmpty.kt
  25. 4 4
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginGemini.kt
  26. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/base/AIPlugin.kt
  27. 4 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginEmpty.kt
  28. 11 8
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginMain.kt
  29. 2 0
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/tts/base/TTSPlugin.kt
  30. 0 0
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/os/base/OSManagerStub.kt
  31. 0 0
      composeApp/src/commonMain/resources/keys/chatgpt.key
  32. 0 0
      composeApp/src/commonMain/resources/keys/gemini.key
  33. 0 2
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/components/desktoppanel/DesktopPanelWindow.kt
  34. 12 4
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/context/DeskptopContext.kt
  35. 15 0
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginDesktop.kt
  36. 2 0
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginSwift.kt
  37. 1 1
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/keys/KeysManager.kt
  38. 10 5
      composeApp/src/wasmJsMain/kotlin/org/mjdev/desktop/MainView.kt
  39. 22 15
      composeApp/src/wasmJsMain/kotlin/org/mjdev/desktop/context/DesktopContext.kt
  40. 21 0
      composeApp/src/wasmJsMain/kotlin/org/mjdev/desktop/managers/os/OSManager.kt
  41. 3 3
      gradle/libs.versions.toml

+ 1 - 6
.gitignore

@@ -27,9 +27,6 @@ packages/
 src/main/resources/dependencies/linux/usr/share/backgrounds
 /src/main/resources/dependencies/linux/usr/share/backgrounds
 /src/main/resources/dependencies/linux/usr/share/backgrounds/
-src/main/resources/keys
-/src/main/resources/keys
-/src/main/resources/keys/
 
 ### cache ###
 /cache
@@ -50,6 +47,4 @@ sources/
 /.intellijPlatform/
 
 ### no keys
-/composeApp/src/commonMain/resources/keys
-composeApp/src/commonMain/resources/keys/
-/composeApp/src/commonMain/resources/keys/
+keys/*.key

+ 6 - 2
composeApp/build.gradle.kts

@@ -136,6 +136,8 @@ kotlin {
                 implementation(libs.androidx.core.splashscreen)
                 // permissions
                 implementation(libs.accompanist.permissions)
+                // ai gemini
+                implementation(libs.sh.google.generative.ai)
             }
         }
         // wasm dependencies
@@ -145,6 +147,8 @@ kotlin {
                 implementation(kotlin("reflect"))
                 // preview
                 implementation(compose.components.uiToolingPreview)
+                // ai gemini
+                implementation(libs.sh.google.generative.ai)
             }
         }
         // desktop dependencies
@@ -162,8 +166,8 @@ kotlin {
             implementation(libs.okhttp3.client)
             // images okhttp
             implementation(libs.coil.okhttp)
-            // gemini ai
-            implementation(libs.google.generative.ai)
+            // ai gemini
+            implementation(libs.sh.google.generative.ai)
             // chatgpt
             implementation(libs.ychat)
             // desktop files

+ 13 - 9
composeApp/src/androidMain/kotlin/org/mjdev/desktop/context/DesktopContext.kt

@@ -26,25 +26,27 @@ import org.mjdev.desktop.interfaces.ITheme
 import org.mjdev.desktop.interfaces.IUser
 import org.mjdev.desktop.managers.language.Translator
 import org.mjdev.desktop.log.Log
-import org.mjdev.desktop.managers.ai.IAiManager
+import org.mjdev.desktop.managers.ai.AiManager
+import org.mjdev.desktop.managers.ai.base.IAiManager
 import org.mjdev.desktop.managers.os.IOSManager
 import org.mjdev.desktop.managers.theme.IThemeManager
 import org.mjdev.desktop.managers.translations.ITranslator
-import org.mjdev.desktop.managers.ai.AIManager.Companion.AiManager
 import org.mjdev.desktop.managers.ai.plugins.AiPluginGemini
 import org.mjdev.desktop.managers.ai.stt.STTPluginEmpty
-import org.mjdev.desktop.managers.ai.tts.TTSPluginMain
+import org.mjdev.desktop.managers.ai.tts.TTSPluginAndroid
 import org.mjdev.desktop.managers.base.IDelegate
 import org.mjdev.desktop.managers.connectivity.ConnectivityManager
 import org.mjdev.desktop.managers.os.OsManager
 import org.mjdev.desktop.managers.processes.ProcessManager
 import kotlin.reflect.KClass
 import org.mjdev.desktop.managers.apps.AppsManager
+import org.mjdev.desktop.managers.keys.IKeyManager
+import org.mjdev.desktop.managers.keys.KeysManager
 import org.mjdev.desktop.managers.palette.Palette
 import org.mjdev.desktop.managers.theme.ThemeManager
 import kotlin.reflect.full.companionObject
 
-@Suppress("unused")
+@Suppress("unused", "MemberVisibilityCanBePrivate")
 class DesktopContext(
     val context: Context? = null,
     override val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
@@ -77,7 +79,7 @@ class DesktopContext(
     val allUsers
         get() = runCatching {
             User.allUsers(this)
-        }.getOrNull() ?: emptyList<User>()
+        }.getOrNull() ?: emptyList()
 
     override suspend fun logOut() {
     }
@@ -110,16 +112,18 @@ class DesktopContext(
         IPalette::class -> Palette(this)
         ITranslator::class -> Translator(this)
         IConnectivityManager::class -> ConnectivityManager(this)
-        IAiManager::class -> AiManager(this).apply {
+        IAiManager::class -> AiManager(
+            context = this,
             // todo user can configure
-            pluginAI = AiPluginGemini(this@DesktopContext)
-            pluginTTS = TTSPluginMain(this@DesktopContext)
+            pluginAI = AiPluginGemini(this@DesktopContext),
+            pluginTTS = TTSPluginAndroid(this@DesktopContext),
             pluginSTT = STTPluginEmpty(this@DesktopContext)
-        }
+        )
 
         IAppsManager::class -> AppsManager(this)
         IThemeManager::class -> ThemeManager(this)
         IProcessManager::class -> ProcessManager(this)
+        IKeyManager::class -> KeysManager(this)
         else -> cls.companionObject?.members?.first {
             it.name == "EMPTY"
         }?.call() as IDelegate

+ 0 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/main/MainView.kt

@@ -119,7 +119,6 @@ fun MainView() = withDesktopContext {
     }
 }
 
-@Suppress("PreviewDeviceShouldUseNewSpec")
 @Preview(device = Devices.TABLET)
 @Composable
 fun MainViewPreview() = preview {

+ 0 - 70
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/ai/AIManager.kt

@@ -1,70 +0,0 @@
-/*
- * Copyright (c) Milan Jurkulák 2024.
- *  Contact:
- *  e: mimoccc@gmail.com
- *  e: mj@mjdev.org
- *  w: https://mjdev.org
- */
-
-package org.mjdev.desktop.managers.ai
-
-import org.mjdev.desktop.managers.ai.plugins.AiPluginEmpty
-import org.mjdev.desktop.managers.ai.stt.STTPluginEmpty
-import org.mjdev.desktop.managers.ai.stt.base.STTPlugin
-import org.mjdev.desktop.managers.ai.tts.base.TTSPlugin
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import org.mjdev.desktop.interfaces.IDesktopContext
-import org.mjdev.desktop.managers.ai.actions.ActionCalculator
-import org.mjdev.desktop.managers.ai.actions.ActionsProvider
-import org.mjdev.desktop.managers.ai.actions.base.ActionException.ActionNone
-import org.mjdev.desktop.managers.ai.plugins.AiPluginGemini
-import org.mjdev.desktop.managers.ai.tts.TTSPluginMain
-
-@Suppress("MemberVisibilityCanBePrivate", "unused", "FunctionName")
-class AIManager(
-    val context: IDesktopContext,
-    var pluginAI: AIPlugin,
-    var pluginSTT: STTPlugin,
-    var pluginTTS: TTSPlugin,
-    var actions: ActionsProvider
-) : IAiManager {
-
-    override val isAvailable: () -> Boolean = { pluginAI !is AiPluginEmpty }
-
-    override fun ask(
-        question: String,
-        block: (question: String, result: String) -> Unit
-    ) {
-        context.scope.launch(Dispatchers.Default) {
-            val actResult = actions.tryAction(question)
-            if (actResult is ActionNone) {
-                pluginAI.ask(question).also { result ->
-                    block.invoke(question, result)
-                }
-            } else {
-                actResult.message?.also { message ->
-                    block.invoke(question, message)
-                }
-            }
-        }
-    }
-
-    override fun talk(
-        text: String,
-        clearQueue: Boolean
-    ) = pluginTTS.talk(text, clearQueue)
-
-    companion object {
-        fun AiManager(
-            context: IDesktopContext,
-            ai: AIPlugin = AiPluginGemini(context),
-            stt: STTPlugin = STTPluginEmpty(context),
-            tts: TTSPlugin = TTSPluginMain(context),
-            actions: ActionsProvider = ActionsProvider(context) {
-                ActionCalculator // todo infix fnc
-            }
-        ) = AIManager(context, ai, stt, tts, actions)
-    }
-}

+ 0 - 49
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginGemini.kt

@@ -1,49 +0,0 @@
-/*
- * Copyright (c) Milan Jurkulák 2024.
- *  Contact:
- *  e: mimoccc@gmail.com
- *  e: mj@mjdev.org
- *  w: https://mjdev.org
- */
-
-package org.mjdev.desktop.managers.ai.plugins
-
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.async
-import org.mjdev.desktop.interfaces.IDesktopContext
-import org.mjdev.desktop.managers.ai.AIPlugin
-
-/*
-* Gemini key : https://makersuite.google.com/app/apikey
-* */
-class AiPluginGemini(
-    private val context: IDesktopContext,
-    private val scope: CoroutineScope = context.scope
-) : AIPlugin {
-    private val key = context.keysManager.loadKey("gemini")
-    private val generativeModel: GenerativeModel? by lazy {
-        if (key.isNotEmpty()) {
-            GenerativeModel(
-                modelName = "gemini-1.5-pro-latest",
-                apiKey = key
-            )
-        } else null
-    }
-
-    override suspend fun ask(question: String): String = scope.async {
-        var error: Throwable? = null
-        runCatching {
-            if (key.isEmpty()) {
-                throw (Exception("Error: No gemini api key provided, pleas read manual and provide Your api key."))
-            } else {
-                generativeModel?.generateContent(
-                    content {
-                        text(question)
-                    }
-                )?.text
-            }
-        }.onFailure { e ->
-            error = e
-        }.getOrNull() ?: error?.message ?: ""
-    }.await()
-}

+ 11 - 11
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginMain.kt → composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginAndroid.kt

@@ -9,24 +9,24 @@
 package org.mjdev.desktop.managers.ai.tts
 
 import org.mjdev.desktop.log.Log
-import org.mjdev.desktop.managers.ai.tts.base.TTSPlugin
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
 import kotlinx.coroutines.launch
 import nl.marc_apps.tts.TextToSpeechEngine
+import nl.marc_apps.tts.TextToSpeechFactory
 import org.mjdev.desktop.context.DesktopContext
 import org.mjdev.desktop.interfaces.IDesktopContext
 
 @Suppress("unused")
-class TTSPluginMain(
-    val context: IDesktopContext
-) : TTSPlugin {
-    private val textToSpeech = context.scope.async {
-        nl.marc_apps.tts.TextToSpeechFactory(
-            (context as DesktopContext).androidContext!!,
-            TextToSpeechEngine.SystemDefault
-        ).createOrNull()
-    }
+class TTSPluginAndroid(
+    context: IDesktopContext,
+) : TTSPluginMain(context, {
+    TextToSpeechFactory(
+        (context as? DesktopContext)?.androidContext!!,
+        TextToSpeechEngine.SystemDefault
+    ).createOrNull()
+}) {
+    // todo
+    override val isPresent: Boolean = true
 
     override fun talk(text: String, clearQueue: Boolean) {
         Log.i("talking: $text")

+ 6 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/keys/KeysManager.kt

@@ -1,8 +1,13 @@
 package org.mjdev.desktop.managers.keys
 
+import kotlinx.coroutines.CoroutineScope
 import org.mjdev.desktop.helpers.streams.ResourceStream
+import org.mjdev.desktop.interfaces.IDesktopContext
 
-class KeysManager : IKeyManager {
+class KeysManager(
+    val context: IDesktopContext,
+    val scope: CoroutineScope = context.scope
+) : IKeyManager {
     override fun loadKey(key: String): String = runCatching {
         ResourceStream("keys/$key.key").text
     }.getOrNull().orEmpty()

+ 0 - 31
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/os/base/OSManagerStub.kt

@@ -1,31 +0,0 @@
-/*
- * Copyright (c) Milan Jurkulák 2024.
- *  Contact:
- *  e: mimoccc@gmail.com
- *  e: mj@mjdev.org
- *  w: https://mjdev.org
- */
-
-package org.mjdev.desktop.managers.os.base
-
-import org.mjdev.desktop.interfaces.IDesktopContext
-import org.mjdev.desktop.managers.os.IOSManager
-
-open class OSManagerStub(
-    private val context: IDesktopContext
-) : IOSManager {
-    open val prettyName: String = ""
-    open val name: String = ""
-    open val versionId: String = ""
-    open val version: String = ""
-    open val versionCodeName: String = ""
-    open val id: String = ""
-    open val idLike: String = ""
-    open val homeUrl: String = ""
-    open val supportUrl: String = ""
-    open val bugReportUrl: String = ""
-    open val privacyPolicyUrl: String = ""
-    open val codename: String = ""
-    open val logo: String = ""
-    override val machineName: String = ""
-}

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/ai/AIPage.kt

@@ -68,7 +68,7 @@ fun AIPage() = ControlCenterPage(
                     .let { resx ->
                         questionsList.replaceLast(Pair(what, resx))
                         scrollState.scrollWithAnimToLast(scope)
-                        context.ai.talk(resx)
+                        context.ai.say(resx)
                     }
             }
         }

+ 3 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/desktoppanel/DesktopPanel.kt

@@ -65,7 +65,6 @@ fun DesktopPanel(
     onAppClick: DesktopContextScope.(IApp) -> Unit = {},
     onAppContextMenuClick: (IApp) -> Unit = {},
     onLanguageClick: () -> Unit = {},
-    onClockClick: () -> Unit = {},
     onTooltip: (item: Any?) -> Unit = {},
 ) = withDesktopContext {
     val panelHeight: (visible: Boolean) -> Dp = { visible ->
@@ -195,7 +194,9 @@ fun DesktopPanel(
                                         )
                                         DesktopPanelDateTime(
                                             onTooltip = onTooltip,
-                                            onClick = onClockClick
+                                            onClick = {
+                                                ai.ask("Whats current time?")
+                                            }
                                         )
                                     }
                                 }

+ 1 - 0
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/desktoppanel/applets/DesktopPanelDateTime.kt

@@ -23,6 +23,7 @@ import org.mjdev.desktop.extensions.Modifier.onMouseLeave
 import org.mjdev.desktop.extensions.MutableStateExt.rememberState
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.extensions.Modifier.onMousePress
 
 @Composable
 fun DesktopPanelDateTime(

+ 2 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/DesktopContextScope.kt

@@ -58,8 +58,8 @@ open class DesktopContextScope(
     val iconSet
         get() = currentUser.theme.iconSet
 
-//    val ai
-//        get() = context.ai
+    val ai
+        get() = context.ai
 
 //    val adb
 //        get() = api.adbHandler

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/EmptyDesktopContext.kt

@@ -12,7 +12,7 @@ import org.mjdev.desktop.interfaces.ILocale
 import org.mjdev.desktop.interfaces.IPalette
 import org.mjdev.desktop.interfaces.ITheme
 import org.mjdev.desktop.interfaces.IUser
-import org.mjdev.desktop.managers.ai.IAiManager
+import org.mjdev.desktop.managers.ai.base.IAiManager
 import org.mjdev.desktop.managers.apps.IAppsManager
 import org.mjdev.desktop.managers.base.IDelegate
 import org.mjdev.desktop.managers.connectivity.IConnectivityManager

+ 0 - 5
composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/CustomExt.kt

@@ -10,11 +10,6 @@ import kotlinx.datetime.Instant
 import kotlinx.datetime.toLocalDateTime
 import kotlinx.datetime.TimeZone
 import kotlinx.datetime.TimeZone.Companion.currentSystemDefault
-import okio.Path
-import org.mjdev.desktop.extensions.PathExt.exists
-import org.mjdev.desktop.extensions.PathExt.text
-import org.mjdev.desktop.helpers.streams.ResourceStream
-import org.mjdev.desktop.interfaces.ILocale
 
 object CustomExt {
 

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/interfaces/IDesktopContext.kt

@@ -16,7 +16,7 @@ import org.mjdev.desktop.components.controlcenter.pages.sound.SoundSettingsPage
 import org.mjdev.desktop.components.controlcenter.pages.theme.ThemeSettingsPage
 import org.mjdev.desktop.components.controlcenter.pages.wifi.WifiSettingsPage
 import org.mjdev.desktop.data.Category
-import org.mjdev.desktop.managers.ai.IAiManager
+import org.mjdev.desktop.managers.ai.base.IAiManager
 import org.mjdev.desktop.managers.apps.IAppsManager
 import org.mjdev.desktop.managers.connectivity.IConnectivityManager
 import org.mjdev.desktop.managers.os.IOSManager

+ 12 - 8
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/ai/AiManager.kt → composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/AiManager.kt

@@ -15,43 +15,47 @@ import org.mjdev.desktop.managers.ai.tts.base.TTSPlugin
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.managers.ai.plugins.AiPluginGemini
 import org.mjdev.desktop.managers.ai.actions.ActionCalculator
-import org.mjdev.desktop.managers.ai.actions.ActionsProvider
+import org.mjdev.desktop.managers.ai.actions.base.ActionsProvider
+import org.mjdev.desktop.managers.ai.actions.ActionCurrentTime
 import org.mjdev.desktop.managers.ai.actions.base.ActionException.ActionNone
-import org.mjdev.desktop.managers.ai.plugins.AiPluginGemini
-import org.mjdev.desktop.managers.ai.tts.TTSPluginMain
+import org.mjdev.desktop.managers.ai.base.IAiManager
+import org.mjdev.desktop.managers.ai.plugins.base.AIPlugin
+import org.mjdev.desktop.managers.ai.tts.TTSPluginEmpty
 
 @Suppress("MemberVisibilityCanBePrivate", "unused")
 class AiManager(
     val context: IDesktopContext,
     var pluginAI: AIPlugin = AiPluginGemini(context),
     var pluginSTT: STTPlugin = STTPluginEmpty(context),
-    var pluginTTS: TTSPlugin = TTSPluginMain(context),
+    var pluginTTS: TTSPlugin = TTSPluginEmpty(context),
     var actions: ActionsProvider = ActionsProvider(context) {
         ActionCalculator // todo infix fnc
+        ActionCurrentTime
     }
 ) : IAiManager {
     override val isAvailable: () -> Boolean = { pluginAI !is AiPluginEmpty }
 
     override fun ask(
         question: String,
-        block: (question: String, result: String) -> Unit
+        block: IAiManager.(question: String, result: String) -> Unit
     ) {
         context.scope.launch(Dispatchers.Default) {
             val actResult = actions.tryAction(question)
             if (actResult is ActionNone) {
                 pluginAI.ask(question).also { result ->
-                    block.invoke(question, result)
+                    block.invoke(this@AiManager, question, result)
                 }
             } else {
                 actResult.message?.also { message ->
-                    block.invoke(question, message)
+                    block.invoke(this@AiManager, question, message)
                 }
             }
         }
     }
 
-    override fun talk(
+    override fun say(
         text: String,
         clearQueue: Boolean
     ) = pluginTTS.talk(text, clearQueue)

+ 3 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/ActionCalculator.kt

@@ -1,8 +1,10 @@
 package org.mjdev.desktop.managers.ai.actions
 
+import org.mjdev.desktop.managers.ai.actions.base.ActionsProvider
+
 val ActionsProvider.ActionCalculator
     get() = action(
         "open calculator"
     ) {
         open("calculator")
-    }
+    }

+ 18 - 0
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/ActionCurrentTime.kt

@@ -0,0 +1,18 @@
+package org.mjdev.desktop.managers.ai.actions
+
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.toLocalDateTime
+import org.mjdev.desktop.managers.ai.actions.base.ActionsProvider
+
+val ActionsProvider.ActionCurrentTime
+    get() = action(
+        "current time"
+    ) {
+        val timeZone = TimeZone.currentSystemDefault()
+        val localDateTime = kotlinx.datetime.Clock.System.now()
+            .toLocalDateTime(timeZone)
+        val hours = localDateTime.hour.toString().padStart(2, '0')
+        val minutes = localDateTime.minute.toString().padStart(2, '0')
+        val seconds = localDateTime.second.toString().padStart(2, '0')
+        success("Current time is: $hours hours $minutes minutes $seconds seconds.")
+    }

+ 17 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/base/ActionProviderScope.kt

@@ -11,10 +11,16 @@ package org.mjdev.desktop.managers.ai.actions.base
 import org.mjdev.desktop.managers.ai.actions.base.ActionException.ActionFail
 import org.mjdev.desktop.managers.ai.actions.base.ActionException.ActionSuccess
 import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.managers.ai.base.IAiManager
 
+@Suppress("RedundantSuspendModifier", "unused")
 class ActionProviderScope(
-    private val context: IDesktopContext
+    private val context: IDesktopContext,
+    private val ai: IAiManager = context.ai
 ) {
+
+    suspend fun success(text:String) = ActionSuccess(text)
+
     // todo actions
     suspend fun open(
         what: String
@@ -24,4 +30,14 @@ class ActionProviderScope(
     } catch (e: Exception) {
         ActionFail(e)
     }
+
+    suspend fun say(
+        what: String,
+        clearQueue: Boolean = false
+    ): ActionException = try {
+        ai.say(what, clearQueue)
+        ActionSuccess()
+    } catch (e: Exception) {
+        ActionFail(e)
+    }
 }

+ 6 - 13
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/ActionsProvider.kt → composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/base/ActionsProvider.kt

@@ -6,14 +6,11 @@
  *  w: https://mjdev.org
  */
 
-package org.mjdev.desktop.managers.ai.actions
+package org.mjdev.desktop.managers.ai.actions.base
 
 import org.mjdev.desktop.log.Log
-import org.mjdev.desktop.managers.ai.actions.base.Action
-import org.mjdev.desktop.managers.ai.actions.base.ActionException
 import org.mjdev.desktop.managers.ai.actions.base.ActionException.ActionFail
 import org.mjdev.desktop.managers.ai.actions.base.ActionException.ActionSuccess
-import org.mjdev.desktop.managers.ai.actions.base.ActionProviderScope
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import org.mjdev.desktop.extensions.Compose.addIfNotExists
@@ -47,13 +44,7 @@ class ActionsProvider(
         command: String,
         action: suspend ActionProviderScope.() -> ActionException,
     ) {
-        actions.add(
-            Action(
-                name = command,
-                text = command,
-                action = action
-            )
-        )
+        actions.add(Action(name = command, text = command, action = action))
     }
 
     // todo regexp
@@ -81,8 +72,10 @@ class ActionsProvider(
                 action.lastSeen = currentTimeMillis()
                 action.history.addIfNotExists(t) { t1, t2 -> t1.contentEquals(t2, true) }
                 action.action.invoke(ActionProviderScope(context)).let { r ->
-                    if (r is ActionFail) r
-                    else ActionSuccess(action.responseSuccess, r)
+                    when (r) {
+                        is ActionFail, is ActionSuccess-> r
+                        else -> ActionSuccess(action.responseSuccess, r)
+                    }
                 }
             } else {
                 ActionException.ActionNone

+ 5 - 5
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/IAiManager.kt → composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/base/IAiManager.kt

@@ -1,4 +1,4 @@
-package org.mjdev.desktop.managers.ai
+package org.mjdev.desktop.managers.ai.base
 
 import org.mjdev.desktop.managers.base.IDelegate
 
@@ -7,10 +7,10 @@ interface IAiManager : IDelegate {
 
     fun ask(
         question: String,
-        block: (question: String, result: String) -> Unit
+        block: IAiManager.(question: String, result: String) -> Unit = { _, r -> say(r) }
     )
 
-    fun talk(
+    fun say(
         text: String,
         clearQueue: Boolean = false
     )
@@ -18,8 +18,8 @@ interface IAiManager : IDelegate {
     companion object {
         val EMPTY = object : IAiManager {
             override val isAvailable: () -> Boolean = { false }
-            override fun ask(question: String, block: (question: String, result: String) -> Unit) {}
-            override fun talk(text: String, clearQueue: Boolean) {}
+            override fun ask(question: String, block: IAiManager.(question: String, result: String) -> Unit) {}
+            override fun say(text: String, clearQueue: Boolean) {}
         }
     }
 }

+ 0 - 13
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AIPlugin.kt

@@ -1,13 +0,0 @@
-/*
- * Copyright (c) Milan Jurkulák 2024.
- *  Contact:
- *  e: mimoccc@gmail.com
- *  e: mj@mjdev.org
- *  w: https://mjdev.org
- */
-
-package org.mjdev.desktop.managers.ai.plugins
-
-interface AIPlugin {
-    suspend fun ask(question: String): String
-}

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginEmpty.kt

@@ -9,7 +9,7 @@
 package org.mjdev.desktop.managers.ai.plugins
 
 import org.mjdev.desktop.interfaces.IDesktopContext
-import org.mjdev.desktop.managers.ai.AIPlugin
+import org.mjdev.desktop.managers.ai.plugins.base.AIPlugin
 
 @Suppress("UNUSED_PARAMETER")
 class AiPluginEmpty(

+ 4 - 4
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginGemini.kt → composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginGemini.kt

@@ -13,14 +13,14 @@ import dev.shreyaspatil.ai.client.generativeai.type.content
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.async
 import org.mjdev.desktop.interfaces.IDesktopContext
-import org.mjdev.desktop.managers.ai.AIPlugin
+import org.mjdev.desktop.managers.ai.plugins.base.AIPlugin
 
 /*
 * Gemini key : https://makersuite.google.com/app/apikey
 * */
 class AiPluginGemini(
     private val context: IDesktopContext,
-    private val aiModel: Model = Model.Gemini1_5Pro,
+    private val aiModel: GeminiModel = GeminiModel.Gemini1_5Pro,
     private val scope: CoroutineScope = context.scope
 ) : AIPlugin {
     private val key = context.keysManager.loadKey("gemini")
@@ -51,7 +51,7 @@ class AiPluginGemini(
     }.await()
 
     @Suppress("EnumEntryName")
-    enum class Model(val model: String) {
+    enum class GeminiModel(val model: String) {
         Gemini1_5Pro("gemini-1.5-pro-latest")
     }
-}
+}

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/AIPlugin.kt → composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/base/AIPlugin.kt

@@ -6,7 +6,7 @@
  *  w: https://mjdev.org
  */
 
-package org.mjdev.desktop.managers.ai
+package org.mjdev.desktop.managers.ai.plugins.base
 
 interface AIPlugin {
     suspend fun ask(question: String): String

+ 4 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginEmpty.kt

@@ -1,11 +1,13 @@
-package org.mjdev.desktop.managers.ai.tts.base
+package org.mjdev.desktop.managers.ai.tts
 
 import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.managers.ai.tts.base.TTSPlugin
 
 @Suppress("UNUSED_PARAMETER")
 class TTSPluginEmpty(
-    context: IDesktopContext
+    context: IDesktopContext,
 ) : TTSPlugin {
+    override val isPresent: Boolean = true
     override fun talk(text: String, clearQueue: Boolean) {
     }
 }

+ 11 - 8
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginMain.kt → composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginMain.kt

@@ -8,23 +8,26 @@
 
 package org.mjdev.desktop.managers.ai.tts
 
+import kotlinx.coroutines.Deferred
 import org.mjdev.desktop.log.Log
 import org.mjdev.desktop.managers.ai.tts.base.TTSPlugin
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
 import kotlinx.coroutines.launch
+import nl.marc_apps.tts.TextToSpeechEngine
 import nl.marc_apps.tts.TextToSpeechFactory
-import nl.marc_apps.tts.experimental.ExperimentalDesktopTarget
+import nl.marc_apps.tts.TextToSpeechInstance
 import org.mjdev.desktop.interfaces.IDesktopContext
 
-@Suppress("unused")
-class TTSPluginMain(
-    val context: IDesktopContext
+open class TTSPluginMain(
+    val context: IDesktopContext,
+    val instantiate: suspend IDesktopContext.() -> TextToSpeechInstance? = { null }
 ) : TTSPlugin {
-    @OptIn(ExperimentalDesktopTarget::class)
-    private val textToSpeech = context.scope.async {
-        TextToSpeechFactory().createOrNull()
+    // todo
+    override val isPresent: Boolean = true
+
+    val textToSpeech: Deferred<TextToSpeechInstance?> = context.scope.async {
+        runCatching { instantiate(context) }.getOrNull()
     }
 
     override fun talk(text: String, clearQueue: Boolean) {

+ 2 - 0
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/tts/base/TTSPlugin.kt

@@ -9,5 +9,7 @@
 package org.mjdev.desktop.managers.ai.tts.base
 
 interface TTSPlugin {
+    val isPresent: Boolean
+
     fun talk(text: String, clearQueue: Boolean = false)
 }

+ 0 - 0
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/os/base/OSManagerStub.kt → composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/os/base/OSManagerStub.kt


+ 0 - 0
composeApp/src/commonMain/resources/keys/chatgpt.key


+ 0 - 0
composeApp/src/commonMain/resources/keys/gemini.key


+ 0 - 2
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/components/desktoppanel/DesktopPanelWindow.kt

@@ -45,7 +45,6 @@ fun DesktopPanelWindow(
     },
     onAppContextMenuClick: (IApp) -> Unit = {},
     onLanguageClick: () -> Unit = {},
-    onClockClick: () -> Unit = {},
     onTooltip: (item: Any?) -> Unit = {},
 ) = withDesktopContext {
     val panelHeight: (visible: Boolean) -> Dp = { visible ->
@@ -137,7 +136,6 @@ fun DesktopPanelWindow(
             onAppClick = onAppClick,
             onAppContextMenuClick = onAppContextMenuClick,
             onLanguageClick = onLanguageClick,
-            onClockClick = onClockClick,
             onTooltip = onTooltip,
             onFocusChange = { focused ->
                 if (focused) {

+ 12 - 4
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/context/DeskptopContext.kt

@@ -31,7 +31,11 @@ import org.mjdev.desktop.interfaces.IUser
 import org.mjdev.desktop.managers.language.Translator
 import org.mjdev.desktop.log.Log
 import org.mjdev.desktop.managers.ai.AiManager
-import org.mjdev.desktop.managers.ai.IAiManager
+import org.mjdev.desktop.managers.ai.base.IAiManager
+import org.mjdev.desktop.managers.ai.plugins.AiPluginGemini
+import org.mjdev.desktop.managers.ai.stt.STTPluginEmpty
+import org.mjdev.desktop.managers.ai.tts.TTSPluginDesktop
+import org.mjdev.desktop.managers.ai.tts.TTSPluginSwift
 import org.mjdev.desktop.managers.apps.IAppsManager
 import org.mjdev.desktop.managers.os.IOSManager
 import org.mjdev.desktop.managers.process.IProcessManager
@@ -48,7 +52,6 @@ import java.awt.Desktop
 import java.awt.Toolkit
 import java.io.File
 import java.net.URI
-import javax.net.ssl.KeyManager
 import kotlin.coroutines.CoroutineContext
 import kotlin.reflect.KClass
 import kotlin.reflect.full.companionObject
@@ -204,7 +207,6 @@ class DesktopContext(
 //    override suspend fun logout() : Boolean = true // todo
 
 
-
     fun lock() = runAsync {
         Shell.executeAndRead("/usr/bin/loginctl", "lock-sessions")
     }
@@ -234,7 +236,13 @@ class DesktopContext(
         IPalette::class -> Palette(this)
         ITranslator::class -> Translator(this)
         IConnectivityManager::class -> ConnectivityManager(this)
-        IAiManager::class -> AiManager(this)
+        IAiManager::class -> AiManager(
+            context = this,
+            // todo user can configure
+            pluginAI = AiPluginGemini(this@DesktopContext),
+            pluginTTS = TTSPluginSwift(this@DesktopContext),
+            pluginSTT = STTPluginEmpty(this@DesktopContext)
+        )
         IAppsManager::class -> AppsManager(this)
         IThemeManager::class -> ThemeManager(this)
         IProcessManager::class -> ProcessManager(this)

+ 15 - 0
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginDesktop.kt

@@ -0,0 +1,15 @@
+package org.mjdev.desktop.managers.ai.tts
+
+import nl.marc_apps.tts.TextToSpeechFactory
+import nl.marc_apps.tts.experimental.ExperimentalDesktopTarget
+import org.mjdev.desktop.interfaces.IDesktopContext
+
+@OptIn(ExperimentalDesktopTarget::class)
+class TTSPluginDesktop(
+    context: IDesktopContext,
+) : TTSPluginMain(context, {
+    TextToSpeechFactory().createOrNull()
+}) {
+    // todo
+    override val isPresent: Boolean = true
+}

+ 2 - 0
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginSwift.kt

@@ -15,12 +15,14 @@ import kotlinx.coroutines.launch
 import org.mjdev.desktop.helpers.system.shell.Shell
 import org.mjdev.desktop.interfaces.IDesktopContext
 import org.mjdev.desktop.managers.ai.tts.base.TTSPlugin
+import java.io.File
 
 @Suppress("unused")
 class TTSPluginSwift(
     private val context: IDesktopContext,
     private val scope: CoroutineScope = context.scope
 ) : TTSPlugin {
+    override val isPresent: Boolean = File("/opt/swift").exists()
     override fun talk(text: String, clearQueue: Boolean) {
         Log.i("talking: $text")
         scope.launch(Dispatchers.Default) {

+ 1 - 1
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/keys/KeysManager.kt

@@ -11,4 +11,4 @@ class KeysManager(
     override fun loadKey(key: String): String = runCatching {
         ResourceStream("keys/$key.key").text
     }.getOrNull().orEmpty()
-}
+}

+ 10 - 5
composeApp/src/wasmJsMain/kotlin/org/mjdev/desktop/MainView.kt

@@ -110,11 +110,16 @@ fun MainView() = withDesktopContext {
 //                contextMenuState.show()
                 })
         }
-        DesktopPanel(panelState = panelState, onTooltip = onTooltip, onMenuIconClicked = {
-            runAsync {
-                appsMenuState.show()
-            }
-        })
+        DesktopPanel(
+            panelState = panelState,
+            onTooltip = onTooltip,
+            onMenuIconClicked = {
+                runAsync {
+                    appsMenuState.show()
+                }
+            },
+            onTimeDateClick = onTimeDateClick
+        )
         ControlCenter(
             modifier = Modifier
                 .fillMaxHeight()

+ 22 - 15
composeApp/src/wasmJsMain/kotlin/org/mjdev/desktop/context/DesktopContext.kt

@@ -17,10 +17,19 @@ import org.mjdev.desktop.helpers.image.ImageLoader.asyncImageLoader
 import org.mjdev.desktop.interfaces.IApp
 import org.mjdev.desktop.interfaces.IDesktopContext
 import org.mjdev.desktop.interfaces.ILocale
+import org.mjdev.desktop.interfaces.IPalette
 import org.mjdev.desktop.interfaces.ITheme
 import org.mjdev.desktop.interfaces.IUser
 import org.mjdev.desktop.log.Log
+import org.mjdev.desktop.managers.ai.AiManager
+import org.mjdev.desktop.managers.ai.base.IAiManager
+import org.mjdev.desktop.managers.ai.stt.STTPluginEmpty
 import org.mjdev.desktop.managers.base.IDelegate
+import org.mjdev.desktop.managers.os.IOSManager
+import org.mjdev.desktop.managers.os.OsManager
+import org.mjdev.desktop.managers.palette.Palette
+import org.mjdev.desktop.managers.ai.plugins.AiPluginGemini
+import org.mjdev.desktop.managers.ai.tts.TTSPluginEmpty
 import kotlin.reflect.KClass
 
 @Suppress("unused")
@@ -76,26 +85,24 @@ class DesktopContext(
     override fun dispose() {
     }
 
-    override fun createManager(cls: KClass<*>): IDelegate?  = null
-
-//    override fun createManager(cls: KClass<*>): IDelegate = when (cls) {
-//        IOSManager::class -> OsManager(this)
-//        IPalette::class -> Palette(this)
+    override fun createManager(cls: KClass<*>): IDelegate? = when (cls) {
+        IOSManager::class -> OsManager(this)
+        IPalette::class -> Palette(this)
 //        ITranslator::class -> Translator(this)
 //        IConnectivityManager::class -> ConnectivityManager(this)
-//        IAiManager::class -> AiManager(this).apply {
-//             todo user can configure
-//            pluginAI = AiPluginGemini(this@DesktopContext)
-//            pluginTTS = TTSPluginMain(this@DesktopContext)
-//            pluginSTT = STTPluginEmpty(this@DesktopContext)
-//        }
+        IAiManager::class -> AiManager(
+            context = this,
+            //  todo user can configure
+            pluginAI = AiPluginGemini(this@DesktopContext),
+            pluginTTS = TTSPluginEmpty(this@DesktopContext),
+            pluginSTT = STTPluginEmpty(this@DesktopContext)
+        )
 //        IAppsManager::class -> AppsManager(this)
 //        IThemeManager::class -> ThemeManager(this)
 //        IProcessManager::class -> ProcessManager(this)
-//        else -> cls.companionObject?.members?.first {
-//            it.name == "EMPTY"
-//        }?.call()
-//    }
+//        IKeyManager::class -> KeysManager(this)
+        else -> null
+    }
 
     companion object {
         @Composable

+ 21 - 0
composeApp/src/wasmJsMain/kotlin/org/mjdev/desktop/managers/os/OSManager.kt

@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.desktop.managers.os
+
+import org.mjdev.desktop.managers.os.base.OSManagerStub
+import org.jetbrains.skiko.hostOs
+import org.mjdev.desktop.interfaces.IDesktopContext
+
+@Suppress("FunctionName")
+fun OsManager(
+    context: IDesktopContext
+) = when (hostOs) {
+    // todo other platforms
+    else -> OSManagerStub(context)
+}

+ 3 - 3
gradle/libs.versions.toml

@@ -40,7 +40,7 @@ okio = "3.11.0"
 qrose = "1.0.1"
 tts = "3.0.0"
 ychat = "1.4.1"
-#generativeai = "0.9.0"
+#google-generative-ai = "0.9.0"
 alert-kmp = "2.0.0"
 #emoji2 = "1.5.0"
 accompanist-permissions = "0.37.2"
@@ -98,8 +98,8 @@ ini4j = { module = "org.ini4j:ini4j", version.ref = "ini4j" }
 # tts
 tts = { module = "nl.marc-apps:tts", version.ref = "tts" }
 # ai
-google-generative-ai = { module = "dev.shreyaspatil.generativeai:generativeai-google-jvm", version.ref = "generative-ai" }
-#generativeai = { group = "com.google.ai.client.generativeai", name = "generativeai", version.ref = "generativeai" }
+sh-google-generative-ai = { module = "dev.shreyaspatil.generativeai:generativeai-google-jvm", version.ref = "generative-ai" }
+#google-generative-ai = { group = "com.google.ai.client.generativeai", name = "generativeai", version.ref = "google-generative-ai" }
 # strings
 human-readable = { module = "nl.jacobras:Human-Readable", version.ref = "human-readable" }
 # alerts