UI: implement core flow's screens
This commit is contained in:
parent
5ad65919e9
commit
7e5c80cee9
|
|
@ -0,0 +1,143 @@
|
||||||
|
package com.example.llama.revamp.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.DrawerState
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.example.llama.revamp.engine.InferenceEngine
|
||||||
|
import com.example.llama.revamp.navigation.NavigationActions
|
||||||
|
import com.example.llama.revamp.ui.components.AppScaffold
|
||||||
|
import com.example.llama.revamp.ui.theme.MonospacedTextStyle
|
||||||
|
import com.example.llama.revamp.viewmodel.MainViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BenchmarkScreen(
|
||||||
|
onBackPressed: () -> Unit,
|
||||||
|
onRerunPressed: () -> Unit,
|
||||||
|
onSharePressed: () -> Unit,
|
||||||
|
drawerState: DrawerState,
|
||||||
|
navigationActions: NavigationActions,
|
||||||
|
viewModel: MainViewModel = viewModel()
|
||||||
|
) {
|
||||||
|
val engineState by viewModel.engineState.collectAsState()
|
||||||
|
val benchmarkResults by viewModel.benchmarkResults.collectAsState()
|
||||||
|
val selectedModel by viewModel.selectedModel.collectAsState()
|
||||||
|
|
||||||
|
AppScaffold(
|
||||||
|
title = "Benchmark Results",
|
||||||
|
drawerState = drawerState,
|
||||||
|
navigationActions = navigationActions,
|
||||||
|
onBackPressed = onBackPressed,
|
||||||
|
onRerunPressed = onRerunPressed,
|
||||||
|
onSharePressed = onSharePressed
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(16.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
// Model info
|
||||||
|
selectedModel?.let { model ->
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = model.name,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "${model.parameters} [${model.quantization}]",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark results or loading indicator
|
||||||
|
when {
|
||||||
|
engineState is InferenceEngine.State.Benchmarking -> {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(200.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Text(
|
||||||
|
text = "Running benchmark...",
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmarkResults != null -> {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = benchmarkResults ?: "",
|
||||||
|
style = MonospacedTextStyle,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(200.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Benchmark results will appear here",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,354 @@
|
||||||
|
package com.example.llama.revamp.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ExpandLess
|
||||||
|
import androidx.compose.material.icons.filled.ExpandMore
|
||||||
|
import androidx.compose.material.icons.filled.Send
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.DrawerState
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.example.llama.revamp.engine.InferenceEngine
|
||||||
|
import com.example.llama.revamp.navigation.NavigationActions
|
||||||
|
import com.example.llama.revamp.ui.components.AppScaffold
|
||||||
|
import com.example.llama.revamp.viewmodel.MainViewModel
|
||||||
|
import com.example.llama.revamp.viewmodel.Message
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ConversationScreen(
|
||||||
|
onBackPressed: () -> Unit,
|
||||||
|
drawerState: DrawerState,
|
||||||
|
navigationActions: NavigationActions,
|
||||||
|
viewModel: MainViewModel = viewModel()
|
||||||
|
) {
|
||||||
|
val engineState by viewModel.engineState.collectAsState()
|
||||||
|
val messages by viewModel.messages.collectAsState()
|
||||||
|
val systemPrompt by viewModel.systemPrompt.collectAsState()
|
||||||
|
val selectedModel by viewModel.selectedModel.collectAsState()
|
||||||
|
|
||||||
|
val isProcessing = engineState is InferenceEngine.State.ProcessingUserPrompt
|
||||||
|
val isGenerating = engineState is InferenceEngine.State.Generating
|
||||||
|
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
var inputText by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
// Auto-scroll to bottom when messages change
|
||||||
|
LaunchedEffect(messages.size) {
|
||||||
|
if (messages.isNotEmpty()) {
|
||||||
|
lazyListState.animateScrollToItem(messages.size - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppScaffold(
|
||||||
|
title = selectedModel?.name ?: "Conversation",
|
||||||
|
drawerState = drawerState,
|
||||||
|
navigationActions = navigationActions,
|
||||||
|
onBackPressed = onBackPressed
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
) {
|
||||||
|
// System prompt display (collapsible)
|
||||||
|
systemPrompt?.let { prompt ->
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "System Prompt",
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
IconButton(onClick = { expanded = !expanded }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
|
||||||
|
contentDescription = if (expanded) "Collapse" else "Expand"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(visible = expanded) {
|
||||||
|
Text(
|
||||||
|
text = prompt,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
LazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
items(messages) { message ->
|
||||||
|
MessageBubble(message = message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show thinking indicator when processing
|
||||||
|
item {
|
||||||
|
if (isProcessing) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// Assistant avatar
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.primary),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "AI",
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Thinking...",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input area
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = inputText,
|
||||||
|
onValueChange = { inputText = it },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
placeholder = { Text("Type your message...") },
|
||||||
|
singleLine = false,
|
||||||
|
maxLines = 5,
|
||||||
|
enabled = !isProcessing && !isGenerating
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
if (inputText.isNotBlank()) {
|
||||||
|
viewModel.sendMessage(inputText)
|
||||||
|
inputText = ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = inputText.isNotBlank() && !isProcessing && !isGenerating
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Send,
|
||||||
|
contentDescription = "Send",
|
||||||
|
tint = if (inputText.isNotBlank() && !isProcessing && !isGenerating)
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MessageBubble(message: Message) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.Top
|
||||||
|
) {
|
||||||
|
when (message) {
|
||||||
|
is Message.User -> {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(40.dp)
|
||||||
|
.height(40.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.secondaryContainer),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "You",
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(5f)
|
||||||
|
.clip(
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStart = 16.dp,
|
||||||
|
topEnd = 16.dp,
|
||||||
|
bottomStart = 16.dp,
|
||||||
|
bottomEnd = 4.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||||
|
.padding(12.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = message.content,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = message.formattedTime,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.7f),
|
||||||
|
modifier = Modifier.align(Alignment.End)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is Message.Assistant -> {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.primary),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "AI",
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(5f)
|
||||||
|
.clip(
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStart = 16.dp,
|
||||||
|
topEnd = 16.dp,
|
||||||
|
bottomStart = 4.dp,
|
||||||
|
bottomEnd = 16.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
.padding(12.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = message.content,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
if (message.isComplete) {
|
||||||
|
Text(
|
||||||
|
text = message.formattedTime,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||||
|
modifier = Modifier.align(Alignment.End)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(12.dp)
|
||||||
|
.align(Alignment.End),
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,393 @@
|
||||||
|
package com.example.llama.revamp.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.selection.selectable
|
||||||
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ExpandLess
|
||||||
|
import androidx.compose.material.icons.filled.ExpandMore
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.DrawerState
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.TabRow
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.example.llama.revamp.data.model.SystemPrompt
|
||||||
|
import com.example.llama.revamp.engine.InferenceEngine
|
||||||
|
import com.example.llama.revamp.navigation.NavigationActions
|
||||||
|
import com.example.llama.revamp.ui.components.AppScaffold
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ModeSelectionScreen(
|
||||||
|
engineState: InferenceEngine.State,
|
||||||
|
onBenchmarkSelected: () -> Unit,
|
||||||
|
onConversationSelected: (String?) -> Unit,
|
||||||
|
onBackPressed: () -> Unit,
|
||||||
|
drawerState: DrawerState,
|
||||||
|
navigationActions: NavigationActions
|
||||||
|
) {
|
||||||
|
val staffPickedPrompts = remember { SystemPrompt.getStaffPickedPrompts() }
|
||||||
|
val recentPrompts = remember { SystemPrompt.getRecentPrompts() }
|
||||||
|
|
||||||
|
var selectedMode by remember { mutableStateOf<Mode?>(null) }
|
||||||
|
var useSystemPrompt by remember { mutableStateOf(false) }
|
||||||
|
var selectedPrompt by remember { mutableStateOf<SystemPrompt?>(null) }
|
||||||
|
var tabIndex by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
|
// Custom prompt sheet state
|
||||||
|
val sheetState = rememberModalBottomSheetState()
|
||||||
|
var showCustomPromptSheet by remember { mutableStateOf(false) }
|
||||||
|
var customPromptText by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
// Check if we're in a loading state
|
||||||
|
val isLoading = engineState !is InferenceEngine.State.Uninitialized &&
|
||||||
|
engineState !is InferenceEngine.State.LibraryLoaded &&
|
||||||
|
engineState !is InferenceEngine.State.AwaitingUserPrompt
|
||||||
|
|
||||||
|
AppScaffold(
|
||||||
|
title = "Select Mode",
|
||||||
|
drawerState = drawerState,
|
||||||
|
navigationActions = navigationActions,
|
||||||
|
onBackPressed = onBackPressed
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
// Mode selection cards
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 8.dp)
|
||||||
|
.selectable(
|
||||||
|
selected = selectedMode == Mode.BENCHMARK,
|
||||||
|
onClick = { selectedMode = Mode.BENCHMARK },
|
||||||
|
enabled = !isLoading,
|
||||||
|
role = Role.RadioButton
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = selectedMode == Mode.BENCHMARK,
|
||||||
|
onClick = null // handled by parent selectable
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Benchmark",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier.padding(start = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 8.dp)
|
||||||
|
.selectable(
|
||||||
|
selected = selectedMode == Mode.CONVERSATION,
|
||||||
|
onClick = {
|
||||||
|
selectedMode = Mode.CONVERSATION
|
||||||
|
},
|
||||||
|
enabled = !isLoading,
|
||||||
|
role = Role.RadioButton
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = selectedMode == Mode.CONVERSATION,
|
||||||
|
onClick = null // handled by parent selectable
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Conversation",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier.padding(start = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System prompt section (only visible when conversation mode is selected)
|
||||||
|
AnimatedVisibility(visible = selectedMode == Mode.CONVERSATION) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 8.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.selectableGroup(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Use system prompt",
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = { useSystemPrompt = !useSystemPrompt }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (useSystemPrompt) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
|
||||||
|
contentDescription = if (useSystemPrompt) "Collapse" else "Expand"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(visible = useSystemPrompt) {
|
||||||
|
Column {
|
||||||
|
TabRow(selectedTabIndex = tabIndex) {
|
||||||
|
Tab(
|
||||||
|
selected = tabIndex == 0,
|
||||||
|
onClick = { tabIndex = 0 },
|
||||||
|
text = { Text("Staff Picks") }
|
||||||
|
)
|
||||||
|
|
||||||
|
Tab(
|
||||||
|
selected = tabIndex == 1,
|
||||||
|
onClick = { tabIndex = 1 },
|
||||||
|
text = { Text("Recent") }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab content
|
||||||
|
when (tabIndex) {
|
||||||
|
0 -> PromptList(
|
||||||
|
prompts = staffPickedPrompts,
|
||||||
|
selectedPrompt = selectedPrompt,
|
||||||
|
onPromptSelected = { selectedPrompt = it }
|
||||||
|
)
|
||||||
|
1 -> PromptList(
|
||||||
|
prompts = recentPrompts,
|
||||||
|
selectedPrompt = selectedPrompt,
|
||||||
|
onPromptSelected = { selectedPrompt = it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom prompt button
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { showCustomPromptSheet = true },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
Text("Custom prompt...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
// Start button
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
when (selectedMode) {
|
||||||
|
Mode.BENCHMARK -> onBenchmarkSelected()
|
||||||
|
Mode.CONVERSATION -> {
|
||||||
|
val systemPrompt = if (useSystemPrompt) {
|
||||||
|
selectedPrompt?.content ?: customPromptText.takeIf { it.isNotBlank() }
|
||||||
|
} else null
|
||||||
|
onConversationSelected(systemPrompt)
|
||||||
|
}
|
||||||
|
null -> { /* No mode selected */ }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(56.dp),
|
||||||
|
enabled = selectedMode != null && !isLoading
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(24.dp)
|
||||||
|
.width(24.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = when(engineState) {
|
||||||
|
is InferenceEngine.State.LoadingModel -> "Loading model..."
|
||||||
|
is InferenceEngine.State.ProcessingSystemPrompt -> "Processing system prompt..."
|
||||||
|
is InferenceEngine.State.ModelLoaded -> "Preparing conversation..."
|
||||||
|
else -> "Processing..."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text("Start")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom prompt bottom sheet
|
||||||
|
if (showCustomPromptSheet) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = { showCustomPromptSheet = false },
|
||||||
|
sheetState = sheetState
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Custom System Prompt",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = customPromptText,
|
||||||
|
onValueChange = { customPromptText = it },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(200.dp),
|
||||||
|
label = { Text("Enter system prompt") },
|
||||||
|
placeholder = { Text("You are a helpful assistant...") }
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
sheetState.hide()
|
||||||
|
showCustomPromptSheet = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
selectedPrompt = null
|
||||||
|
coroutineScope.launch {
|
||||||
|
sheetState.hide()
|
||||||
|
showCustomPromptSheet = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = customPromptText.isNotBlank()
|
||||||
|
) {
|
||||||
|
Text("Use Custom Prompt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PromptList(
|
||||||
|
prompts: List<SystemPrompt>,
|
||||||
|
selectedPrompt: SystemPrompt?,
|
||||||
|
onPromptSelected: (SystemPrompt) -> Unit
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(200.dp)
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
items(prompts) { prompt ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.selectable(
|
||||||
|
selected = selectedPrompt?.id == prompt.id,
|
||||||
|
onClick = { onPromptSelected(prompt) },
|
||||||
|
role = Role.RadioButton
|
||||||
|
)
|
||||||
|
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = selectedPrompt?.id == prompt.id,
|
||||||
|
onClick = null // handled by parent selectable
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(start = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = prompt.name,
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = prompt.content,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Mode {
|
||||||
|
BENCHMARK,
|
||||||
|
CONVERSATION
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
package com.example.llama.revamp.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.DrawerState
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.example.llama.revamp.data.model.ModelInfo
|
||||||
|
import com.example.llama.revamp.navigation.NavigationActions
|
||||||
|
import com.example.llama.revamp.ui.components.AppScaffold
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ModelSelectionScreen(
|
||||||
|
onModelSelected: (ModelInfo) -> Unit,
|
||||||
|
onManageModelsClicked: () -> Unit,
|
||||||
|
onMenuClicked: () -> Unit,
|
||||||
|
drawerState: DrawerState,
|
||||||
|
navigationActions: NavigationActions
|
||||||
|
) {
|
||||||
|
// For demo purposes, we'll use sample models
|
||||||
|
val models = remember { ModelInfo.getSampleModels() }
|
||||||
|
|
||||||
|
AppScaffold(
|
||||||
|
title = "Models",
|
||||||
|
drawerState = drawerState,
|
||||||
|
navigationActions = navigationActions,
|
||||||
|
onMenuPressed = onMenuClicked
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = onManageModelsClicked,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.End)
|
||||||
|
.padding(top = 8.dp, bottom = 8.dp)
|
||||||
|
) {
|
||||||
|
Text("Manage Models")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Downloaded Models",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
LazyColumn {
|
||||||
|
items(models) { model ->
|
||||||
|
ModelCard(
|
||||||
|
model = model,
|
||||||
|
onClick = { onModelSelected(model) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ModelCard(
|
||||||
|
model: ModelInfo,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
elevation = CardDefaults.cardElevation(
|
||||||
|
defaultElevation = 2.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = model.name,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
text = model.parameters,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = " • ${model.quantization}",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = " • ${model.formattedSize}",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Context Length: ${model.contextLength}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
model.lastUsed?.let { lastUsed ->
|
||||||
|
val dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.getDefault())
|
||||||
|
Text(
|
||||||
|
text = "Last used: ${dateFormat.format(Date(lastUsed))}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = onClick
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.PlayArrow,
|
||||||
|
contentDescription = "Select model",
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,259 @@
|
||||||
|
package com.example.llama.revamp.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.DrawerState
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.TabRow
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.example.llama.revamp.data.preferences.UserPreferences
|
||||||
|
import com.example.llama.revamp.monitoring.PerformanceMonitor
|
||||||
|
import com.example.llama.revamp.navigation.NavigationActions
|
||||||
|
import com.example.llama.revamp.ui.components.AppScaffold
|
||||||
|
import com.example.llama.revamp.util.ViewModelFactoryProvider
|
||||||
|
import com.example.llama.revamp.viewmodel.PerformanceViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tabs for the settings screen.
|
||||||
|
*/
|
||||||
|
enum class SettingsTab {
|
||||||
|
GENERAL,
|
||||||
|
MODEL_MANAGEMENT,
|
||||||
|
ADVANCED
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsScreen(
|
||||||
|
selectedTab: SettingsTab = SettingsTab.GENERAL,
|
||||||
|
onBackPressed: () -> Unit,
|
||||||
|
drawerState: DrawerState,
|
||||||
|
navigationActions: NavigationActions
|
||||||
|
) {
|
||||||
|
// State for tab selection
|
||||||
|
var currentTab by remember { mutableStateOf(selectedTab) }
|
||||||
|
|
||||||
|
// Create dependencies for PerformanceViewModel
|
||||||
|
val context = LocalContext.current
|
||||||
|
val performanceMonitor = remember { PerformanceMonitor(context) }
|
||||||
|
val userPreferences = remember { UserPreferences(context) }
|
||||||
|
|
||||||
|
// Create factory for PerformanceViewModel
|
||||||
|
val factory = remember { ViewModelFactoryProvider.getPerformanceViewModelFactory(performanceMonitor, userPreferences) }
|
||||||
|
|
||||||
|
// Get ViewModel instance with factory
|
||||||
|
val performanceViewModel: PerformanceViewModel = viewModel(factory = factory)
|
||||||
|
|
||||||
|
AppScaffold(
|
||||||
|
title = "Settings",
|
||||||
|
drawerState = drawerState,
|
||||||
|
navigationActions = navigationActions,
|
||||||
|
onBackPressed = onBackPressed
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
) {
|
||||||
|
// Tabs for different settings categories
|
||||||
|
TabRow(selectedTabIndex = currentTab.ordinal) {
|
||||||
|
Tab(
|
||||||
|
selected = currentTab == SettingsTab.GENERAL,
|
||||||
|
onClick = { currentTab = SettingsTab.GENERAL },
|
||||||
|
text = { Text("General") }
|
||||||
|
)
|
||||||
|
|
||||||
|
Tab(
|
||||||
|
selected = currentTab == SettingsTab.MODEL_MANAGEMENT,
|
||||||
|
onClick = { currentTab = SettingsTab.MODEL_MANAGEMENT },
|
||||||
|
text = { Text("Models") }
|
||||||
|
)
|
||||||
|
|
||||||
|
Tab(
|
||||||
|
selected = currentTab == SettingsTab.ADVANCED,
|
||||||
|
onClick = { currentTab = SettingsTab.ADVANCED },
|
||||||
|
text = { Text("Advanced") }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content for the selected tab
|
||||||
|
when (currentTab) {
|
||||||
|
SettingsTab.GENERAL -> GeneralSettings(performanceViewModel)
|
||||||
|
SettingsTab.MODEL_MANAGEMENT -> ModelManagementSettings()
|
||||||
|
SettingsTab.ADVANCED -> AdvancedSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GeneralSettings(performanceViewModel: PerformanceViewModel) {
|
||||||
|
val isMonitoringEnabled by performanceViewModel.isMonitoringEnabled.collectAsState()
|
||||||
|
val useFahrenheit by performanceViewModel.useFahrenheitUnit.collectAsState()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
SettingsCategory(title = "Performance Monitoring") {
|
||||||
|
SettingsSwitch(
|
||||||
|
title = "Enable Monitoring",
|
||||||
|
description = "Display memory, battery and temperature information",
|
||||||
|
checked = isMonitoringEnabled,
|
||||||
|
onCheckedChange = { performanceViewModel.setMonitoringEnabled(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsSwitch(
|
||||||
|
title = "Use Fahrenheit",
|
||||||
|
description = "Display temperature in Fahrenheit instead of Celsius",
|
||||||
|
checked = useFahrenheit,
|
||||||
|
onCheckedChange = { performanceViewModel.setUseFahrenheitUnit(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCategory(title = "Theme") {
|
||||||
|
SettingsSwitch(
|
||||||
|
title = "Dark Theme",
|
||||||
|
description = "Use dark theme throughout the app",
|
||||||
|
checked = true, // This would be connected to theme state in a real app
|
||||||
|
onCheckedChange = { /* TODO: Implement theme switching */ }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ModelManagementSettings() {
|
||||||
|
// This would be populated with actual functionality in a real implementation
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Model Management",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "This section will allow you to download, delete, and manage LLM models.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AdvancedSettings() {
|
||||||
|
// This would be populated with actual functionality in a real implementation
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Advanced Settings",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "This section will contain advanced settings such as memory management, cache configuration, and debugging options.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsCategory(
|
||||||
|
title: String,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsSwitch(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
checked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = description,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = checked,
|
||||||
|
onCheckedChange = onCheckedChange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue