UI: polish ModelLoading screen

This commit is contained in:
Han Yin 2025-04-20 17:53:42 -07:00
parent 57b5001f5c
commit d7afcc41d5
6 changed files with 103 additions and 58 deletions

View File

@ -17,11 +17,16 @@ 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.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.AssistChip
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -49,44 +54,70 @@ import java.util.Locale
* architecture, quantization and file size in a compact card format.
*
* @param model The model information to display
* @param onClick Action to perform when the card is clicked
* @param isSelected Optional selection state (shows checkbox when not null)
* @param isExpanded Whether additional details is expanded or shrunk
* @param onExpanded Action to perform when the card is expanded or shrunk
*/
@Composable
fun ModelCardCore(
fun ModelCardCoreExpandable(
model: ModelInfo,
onClick: () -> Unit,
isSelected: Boolean? = null,
isExpanded: Boolean = false,
onExpanded: ((Boolean) -> Unit)? = null,
) {
Card(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick),
colors = when (isSelected) {
true -> CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
.clickable { onExpanded?.invoke(!isExpanded) },
colors = when (isExpanded) {
true -> CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
)
false -> CardDefaults.cardColors()
else -> CardDefaults.cardColors()
},
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Row(
Column(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Show checkbox if in selection mode
if (isSelected != null) {
Checkbox(
checked = isSelected,
onCheckedChange = { onClick() },
modifier = Modifier.padding(end = 8.dp)
)
// Row 1: Model full name + chevron
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
ModelCardContentTitleRow(model)
CompositionLocalProvider(
LocalMinimumInteractiveComponentSize provides Dp.Unspecified
) {
IconButton(onClick = { onExpanded?.invoke(!isExpanded) }) {
Icon(
imageVector = if (isExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
contentDescription = "Tap to ${if (isExpanded) "shrink" else "expand"} model card"
)
}
}
}
// Core model info
ModelCardContentCore(
model = model,
modifier = Modifier.weight(1f)
)
// Expandable content
AnimatedVisibility(
visible = isExpanded,
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut()
) {
Column(
modifier = Modifier.weight(1f)
) {
Spacer(modifier = Modifier.height(8.dp))
// Row 2: Context length, size label
ModelCardContentContextRow(model)
Spacer(modifier = Modifier.height(8.dp))
// Row 3: Architecture, quantization, formatted size
ModelCardContentArchitectureRow(model)
}
}
}
}
}
@ -106,7 +137,7 @@ fun ModelCardCore(
*/
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ModelCardExpandable(
fun ModelCardFullExpandable(
model: ModelInfo,
isSelected: Boolean? = null,
onSelected: ((Boolean) -> Unit)? = null,
@ -117,11 +148,11 @@ fun ModelCardExpandable(
Card(
modifier = Modifier
.fillMaxWidth()
.clickable {
onExpanded?.invoke(!isExpanded)
},
.clickable { onExpanded?.invoke(!isExpanded) },
colors = when (isSelected) {
true -> CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
true -> CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
)
false -> CardDefaults.cardColors()
else -> CardDefaults.cardColors()
},
@ -148,11 +179,7 @@ fun ModelCardExpandable(
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
) {
// Row 1: Model full name
Text(
text = model.formattedFullName,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Medium
)
ModelCardContentTitleRow(model)
}
}
@ -283,11 +310,7 @@ fun ModelCardContentCore(
) =
Column(modifier = modifier) {
// Row 1: Model full name
Text(
text = model.formattedFullName,
style = MaterialTheme.typography.headlineSmall, // TODO
fontWeight = FontWeight.Medium
)
ModelCardContentTitleRow(model)
Spacer(modifier = Modifier.height(12.dp))
@ -300,6 +323,14 @@ fun ModelCardContentCore(
ModelCardContentArchitectureRow(model)
}
@Composable
private fun ModelCardContentTitleRow(model: ModelInfo) =
Text(
text = model.formattedFullName,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Medium
)
@Composable
private fun ModelCardContentContextRow(model: ModelInfo) =
Row(

View File

@ -21,26 +21,34 @@ 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.unit.dp
import com.example.llama.revamp.engine.ModelLoadingMetrics
import com.example.llama.revamp.ui.components.ModelCardCore
import com.example.llama.revamp.ui.components.ModelCardCoreExpandable
import com.example.llama.revamp.ui.components.ModelUnloadDialogHandler
import com.example.llama.revamp.ui.theme.MonospacedTextStyle
import com.example.llama.revamp.viewmodel.BenchmarkViewModel
@Composable
fun BenchmarkScreen(
// TODO-han.yin: Use loading metrics to show UI
loadingMetrics: ModelLoadingMetrics,
onNavigateBack: () -> Unit,
viewModel: BenchmarkViewModel
) {
// View model states
val engineState by viewModel.engineState.collectAsState()
val benchmarkResults by viewModel.benchmarkResults.collectAsState()
val selectedModel by viewModel.selectedModel.collectAsState()
val unloadDialogState by viewModel.unloadModelState.collectAsState()
// UI states
var isModelCardExpanded by remember { mutableStateOf(false) }
// Run benchmark when entering the screen
LaunchedEffect(selectedModel) {
viewModel.runBenchmark()
@ -59,10 +67,10 @@ fun BenchmarkScreen(
) {
// Selected model card
selectedModel?.let { model ->
ModelCardCore(
ModelCardCoreExpandable(
model = model,
onClick = { /* No action on click */ },
isSelected = null
isExpanded = isModelCardExpanded,
onExpanded = { isModelCardExpanded = !isModelCardExpanded },
)
}

View File

@ -69,6 +69,7 @@ import kotlinx.coroutines.launch
*/
@Composable
fun ConversationScreen(
// TODO-han.yin: Use loading metrics to show UI
loadingMetrics: ModelLoadingMetrics,
onNavigateBack: () -> Unit,
viewModel: ConversationViewModel

View File

@ -50,7 +50,7 @@ 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.ModelLoadingMetrics
import com.example.llama.revamp.ui.components.ModelCardCore
import com.example.llama.revamp.ui.components.ModelCardCoreExpandable
import com.example.llama.revamp.ui.components.ModelUnloadDialogHandler
import com.example.llama.revamp.viewmodel.ModelLoadingViewModel
@ -72,12 +72,15 @@ fun ModelLoadingScreen(
onNavigateToConversation: (ModelLoadingMetrics) -> Unit,
viewModel: ModelLoadingViewModel,
) {
// View model states
val engineState by viewModel.engineState.collectAsState()
val selectedModel by viewModel.selectedModel.collectAsState()
val presetPrompts by viewModel.presetPrompts.collectAsState()
val recentPrompts by viewModel.recentPrompts.collectAsState()
val unloadDialogState by viewModel.unloadModelState.collectAsState()
// UI states
var isModelCardExpanded by remember { mutableStateOf(false) }
var selectedMode by remember { mutableStateOf<Mode?>(null) }
var useSystemPrompt by remember { mutableStateOf(false) }
var selectedPrompt by remember { mutableStateOf<SystemPrompt?>(null) }
@ -116,11 +119,13 @@ fun ModelLoadingScreen(
) {
// Selected model card
selectedModel?.let { model ->
ModelCardCore(
ModelCardCoreExpandable(
model = model,
onClick = { /* No action on click */ },
isSelected = null
isExpanded = isModelCardExpanded,
onExpanded = { isModelCardExpanded = !isModelCardExpanded },
)
Spacer(modifier = Modifier.height(16.dp))
}
// Benchmark card
@ -148,7 +153,7 @@ fun ModelLoadingScreen(
)
Text(
text = "Benchmark",
style = MaterialTheme.typography.titleLarge,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(start = 8.dp)
)
}
@ -159,6 +164,12 @@ fun ModelLoadingScreen(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp)
.selectable(
selected = selectedMode == Mode.CONVERSATION,
onClick = { selectedMode = Mode.CONVERSATION },
enabled = !isLoading,
role = Role.RadioButton
)
// Only fill height if system prompt is active
.then(if (useSystemPrompt) Modifier.weight(1f) else Modifier)
) {
@ -173,12 +184,6 @@ fun ModelLoadingScreen(
Row(
modifier = Modifier
.fillMaxWidth()
.selectable(
selected = selectedMode == Mode.CONVERSATION,
onClick = { selectedMode = Mode.CONVERSATION },
enabled = !isLoading,
role = Role.RadioButton
)
.padding(top = 16.dp, start = 16.dp, end = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
@ -188,7 +193,7 @@ fun ModelLoadingScreen(
)
Text(
text = "Conversation",
style = MaterialTheme.typography.titleLarge,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(start = 8.dp)
)
}

View File

@ -34,7 +34,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.example.llama.revamp.data.model.ModelInfo
import com.example.llama.revamp.ui.components.ModelCardExpandable
import com.example.llama.revamp.ui.components.ModelCardFullExpandable
import com.example.llama.revamp.viewmodel.ModelSelectionViewModel
@OptIn(ExperimentalMaterial3Api::class)
@ -65,7 +65,7 @@ fun ModelSelectionScreen(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(items = models, key = { it.id }) { model ->
ModelCardExpandable(
ModelCardFullExpandable(
model = model,
isSelected = if (model == preselectedModel) true else null,
onSelected = { selected ->

View File

@ -36,7 +36,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import com.example.llama.revamp.data.model.ModelInfo
import com.example.llama.revamp.ui.components.ModelCardExpandable
import com.example.llama.revamp.ui.components.ModelCardFullExpandable
import com.example.llama.revamp.ui.components.ScaffoldEvent
import com.example.llama.revamp.util.formatFileByteSize
import com.example.llama.revamp.viewmodel.ModelManagementState
@ -84,7 +84,7 @@ fun ModelsManagementScreen(
items(items = sortedModels, key = { it.id }) { model ->
val isSelected = if (isMultiSelectionMode) selectedModels.contains(model.id) else null
ModelCardExpandable(
ModelCardFullExpandable(
model = model,
isSelected = isSelected,
onSelected = {