Phase 1/4: terminal-style console UI (CLI mode)
Browse files
app/src/main/java/io/github/abrarul/modularai/ConsoleActivity.kt
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package io.github.abrarul.modularai
|
| 2 |
+
|
| 3 |
+
import android.os.Bundle
|
| 4 |
+
import androidx.activity.ComponentActivity
|
| 5 |
+
import androidx.activity.compose.setContent
|
| 6 |
+
import androidx.compose.foundation.background
|
| 7 |
+
import androidx.compose.foundation.layout.*
|
| 8 |
+
import androidx.compose.foundation.lazy.LazyColumn
|
| 9 |
+
import androidx.compose.foundation.lazy.items
|
| 10 |
+
import androidx.compose.foundation.lazy.rememberLazyListState
|
| 11 |
+
import androidx.compose.material3.*
|
| 12 |
+
import androidx.compose.runtime.*
|
| 13 |
+
import androidx.compose.ui.Modifier
|
| 14 |
+
import androidx.compose.ui.graphics.Color
|
| 15 |
+
import androidx.compose.ui.text.font.FontFamily
|
| 16 |
+
import androidx.compose.ui.unit.dp
|
| 17 |
+
import androidx.compose.ui.unit.sp
|
| 18 |
+
import androidx.lifecycle.lifecycleScope
|
| 19 |
+
import io.github.abrarul.modularai.cli.Cli
|
| 20 |
+
import kotlinx.coroutines.launch
|
| 21 |
+
|
| 22 |
+
class ConsoleActivity : ComponentActivity() {
|
| 23 |
+
private val cli by lazy { Cli(this) }
|
| 24 |
+
private val lines = mutableStateListOf("ModularAI console. Type 'help'.")
|
| 25 |
+
|
| 26 |
+
private fun append(s: String) = runOnUiThread {
|
| 27 |
+
lines.add(s)
|
| 28 |
+
while (lines.size > 2000) lines.removeAt(0)
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
override fun onCreate(savedInstanceState: Bundle?) {
|
| 32 |
+
super.onCreate(savedInstanceState)
|
| 33 |
+
setContent {
|
| 34 |
+
MaterialTheme(colorScheme = darkColorScheme()) {
|
| 35 |
+
var input by remember { mutableStateOf("") }
|
| 36 |
+
var busy by remember { mutableStateOf(false) }
|
| 37 |
+
val history = remember { mutableStateListOf<String>() }
|
| 38 |
+
var hIdx by remember { mutableIntStateOf(0) }
|
| 39 |
+
val list = rememberLazyListState()
|
| 40 |
+
LaunchedEffect(lines.size) { if (lines.isNotEmpty()) list.scrollToItem(lines.size - 1) }
|
| 41 |
+
|
| 42 |
+
fun run() {
|
| 43 |
+
val cmd = input.trim()
|
| 44 |
+
if (cmd.isEmpty() || busy) return
|
| 45 |
+
input = ""; history.add(cmd); hIdx = history.size
|
| 46 |
+
append("$ $cmd")
|
| 47 |
+
busy = true
|
| 48 |
+
lifecycleScope.launch {
|
| 49 |
+
cli.exec(cmd, ::append)
|
| 50 |
+
busy = false
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
Column(Modifier.fillMaxSize().background(Color(0xFF0B0F14)).statusBarsPadding().navigationBarsPadding().imePadding()) {
|
| 55 |
+
LazyColumn(Modifier.weight(1f).padding(horizontal = 8.dp), state = list) {
|
| 56 |
+
items(lines) { Text(it, fontFamily = FontFamily.Monospace, fontSize = 12.sp, color = Color(0xFFD6E2F0)) }
|
| 57 |
+
}
|
| 58 |
+
Row(Modifier.fillMaxWidth().padding(8.dp)) {
|
| 59 |
+
OutlinedTextField(
|
| 60 |
+
value = input, onValueChange = { input = it },
|
| 61 |
+
modifier = Modifier.weight(1f), singleLine = true,
|
| 62 |
+
placeholder = { Text(if (busy) "running..." else "command") }
|
| 63 |
+
)
|
| 64 |
+
Spacer(Modifier.width(6.dp))
|
| 65 |
+
Button(onClick = { if (hIdx > 0) { hIdx--; input = history[hIdx] } }, enabled = history.isNotEmpty()) { Text("^") }
|
| 66 |
+
Spacer(Modifier.width(6.dp))
|
| 67 |
+
Button(onClick = ::run, enabled = !busy) { Text("Run") }
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
}
|