AIPage.kt 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. package eu.mjdev.desktop.components.controlcenter.pages
  2. import androidx.compose.desktop.ui.tooling.preview.Preview
  3. import androidx.compose.foundation.ExperimentalFoundationApi
  4. import androidx.compose.foundation.background
  5. import androidx.compose.foundation.layout.*
  6. import androidx.compose.foundation.lazy.LazyColumn
  7. import androidx.compose.foundation.lazy.itemsIndexed
  8. import androidx.compose.foundation.onClick
  9. import androidx.compose.foundation.shape.RoundedCornerShape
  10. import androidx.compose.material.Icon
  11. import androidx.compose.material.TextFieldDefaults
  12. import androidx.compose.runtime.Composable
  13. import androidx.compose.runtime.derivedStateOf
  14. import androidx.compose.runtime.mutableStateListOf
  15. import androidx.compose.ui.Alignment
  16. import androidx.compose.ui.Modifier
  17. import androidx.compose.ui.graphics.Color
  18. import androidx.compose.ui.platform.LocalClipboardManager
  19. import androidx.compose.ui.text.AnnotatedString
  20. import androidx.compose.ui.unit.dp
  21. import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
  22. import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPageScope.Companion.remember
  23. import eu.mjdev.desktop.components.input.SelectableOutlineEditText
  24. import eu.mjdev.desktop.components.text.TextAny
  25. import eu.mjdev.desktop.extensions.Compose.rememberState
  26. import eu.mjdev.desktop.icons.Icons
  27. // todo copy only selected text to clipboard
  28. @OptIn(ExperimentalFoundationApi::class)
  29. @Suppress("FunctionName")
  30. fun AIPage() = ControlCenterPage(
  31. icon = Icons.Chat,
  32. name = "AI",
  33. condition = { ai.isAvailable() }
  34. ) {
  35. val clipboardManager = LocalClipboardManager.current
  36. val iconSize = 24.dp
  37. val questionsList = remember { mutableStateListOf<Pair<String, String>>() }
  38. val request = rememberState("")
  39. val iconSend = androidx.compose.runtime.remember {
  40. derivedStateOf {
  41. if (request.value.isNotEmpty()) Icons.SendIcon else null
  42. }
  43. }
  44. val onDone: () -> Unit = {
  45. val what = request.value.replace("\n", "")
  46. if (what.isNotEmpty()) {
  47. questionsList.add(Pair(what, ""))
  48. request.value = ""
  49. api.ai.ask(what) { _, res ->
  50. res.replace("* **", "")
  51. .replace("**", "")
  52. .let { resx ->
  53. questionsList.removeIf { it.first == what }
  54. questionsList.add(Pair(what, resx))
  55. talk(resx)
  56. }
  57. }
  58. }
  59. }
  60. Box(
  61. modifier = Modifier.fillMaxSize()
  62. .background(backgroundColor)
  63. ) {
  64. Row {
  65. Column(
  66. modifier = Modifier.fillMaxSize()
  67. .padding(16.dp)
  68. ) {
  69. LazyColumn(
  70. modifier = Modifier.fillMaxWidth()
  71. .weight(1f)
  72. .background(
  73. Color.Black.copy(alpha = 0.3f),
  74. RoundedCornerShape(8.dp)
  75. )
  76. ) {
  77. itemsIndexed(questionsList) { idx, textData ->
  78. Column(
  79. modifier = Modifier.padding(4.dp)
  80. .fillMaxWidth()
  81. .background(
  82. Color.White.copy(alpha = 0.1f),
  83. RoundedCornerShape(8.dp)
  84. )
  85. .padding(4.dp),
  86. ) {
  87. Box(
  88. Modifier.padding(4.dp)
  89. .fillMaxWidth()
  90. .background(
  91. Color.White.copy(alpha = 0.1f),
  92. RoundedCornerShape(8.dp)
  93. )
  94. .padding(8.dp)
  95. ) {
  96. TextAny(
  97. modifier = Modifier.fillMaxWidth().padding(end = 24.dp),
  98. text = "${idx + 1}. ${textData.first}",
  99. color = Color.White,
  100. textSelectionEnabled = true
  101. )
  102. Icon(
  103. modifier = Modifier.padding(4.dp)
  104. .size(iconSize)
  105. .align(Alignment.TopEnd)
  106. .onClick {
  107. clipboardManager.setText(AnnotatedString(textData.first))
  108. },
  109. imageVector = Icons.CopyToClipboard,
  110. tint = Color.White.copy(alpha = 0.9f),
  111. contentDescription = ""
  112. )
  113. }
  114. Box(
  115. modifier = Modifier.padding(4.dp)
  116. .fillMaxWidth()
  117. .background(
  118. Color.White.copy(alpha = 0.1f),
  119. RoundedCornerShape(8.dp)
  120. )
  121. .padding(8.dp),
  122. ) {
  123. TextAny(
  124. modifier = Modifier.fillMaxWidth().padding(end = 24.dp),
  125. text = textData.second,
  126. color = Color.White.copy(alpha = 0.9f),
  127. textSelectionEnabled = true
  128. )
  129. Icon(
  130. modifier = Modifier.padding(4.dp)
  131. .size(iconSize)
  132. .align(Alignment.TopEnd)
  133. .onClick {
  134. clipboardManager.setText(AnnotatedString(textData.second))
  135. },
  136. imageVector = Icons.CopyToClipboard,
  137. tint = Color.White.copy(alpha = 0.9f),
  138. contentDescription = ""
  139. )
  140. }
  141. }
  142. }
  143. }
  144. SelectableOutlineEditText(
  145. modifier = Modifier.padding(top = 8.dp)
  146. .fillMaxWidth(),
  147. value = request.value,
  148. onValueChange = { t: String ->
  149. request.value = t
  150. if (t.contains("\n")) {
  151. onDone()
  152. }
  153. },
  154. maxLines = 4,
  155. minLines = 4,
  156. shape = RoundedCornerShape(8.dp),
  157. colors = TextFieldDefaults.outlinedTextFieldColors(
  158. backgroundColor = Color.White.copy(alpha = 0.1f),
  159. textColor = Color.White.copy(alpha = 0.9f),
  160. cursorColor = Color.White,
  161. focusedBorderColor = Color.White,
  162. unfocusedBorderColor = Color.Black
  163. ),
  164. trailingIcon = {
  165. Row(
  166. verticalAlignment = Alignment.CenterVertically,
  167. ) {
  168. if (request.value.isNotEmpty()) {
  169. Icon(
  170. modifier = Modifier.padding(4.dp)
  171. .size(iconSize)
  172. .onClick {
  173. clipboardManager.setText(AnnotatedString(request.value))
  174. },
  175. imageVector = Icons.CopyToClipboard,
  176. tint = Color.White.copy(alpha = 0.9f),
  177. contentDescription = ""
  178. )
  179. }
  180. if (iconSend.value != null) {
  181. Icon(
  182. modifier = Modifier.padding(4.dp)
  183. .size(iconSize)
  184. .onClick { onDone() },
  185. imageVector = iconSend.value!!,
  186. tint = Color.White.copy(alpha = 0.9f),
  187. contentDescription = ""
  188. )
  189. }
  190. }
  191. }
  192. )
  193. }
  194. }
  195. }
  196. }
  197. @Preview
  198. @Composable
  199. fun AIPagePreview() = AIPage().render()