UI: polish model cards on Benchmark and Conversation screens to show model loading metrics

This commit is contained in:
Han Yin 2025-04-20 18:37:48 -07:00
parent 10ca2fa834
commit 56a7272858
4 changed files with 158 additions and 119 deletions

View File

@ -32,10 +32,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
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.text.font.FontStyle
@ -48,10 +45,32 @@ import java.util.Date
import java.util.Locale
/**
* Displays model information in a card format with core details.
* Displays model information in a card format expandable with core details
* such as context length, architecture, quantization and file size
*
* This component shows essential model information like name, context length,
* architecture, quantization and file size in a compact card format.
* @param model The model information to display
* @param isExpanded Whether additional details is expanded or shrunk
* @param onExpanded Action to perform when the card is expanded or shrunk
*/
@Composable
fun ModelCardCoreExpandable(
model: ModelInfo,
isExpanded: Boolean = false,
onExpanded: ((Boolean) -> Unit)? = null
) = ModelCardCoreExpandable(model, isExpanded, onExpanded) {
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)
}
/**
* Displays model information in a card format expandable with customizable extra content.
*
* @param model The model information to display
* @param isExpanded Whether additional details is expanded or shrunk
@ -62,6 +81,7 @@ fun ModelCardCoreExpandable(
model: ModelInfo,
isExpanded: Boolean = false,
onExpanded: ((Boolean) -> Unit)? = null,
expandableSection: @Composable () -> Unit
) {
Card(
modifier = Modifier
@ -107,15 +127,7 @@ fun ModelCardCoreExpandable(
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)
expandableSection()
}
}
}
@ -234,97 +246,14 @@ fun ModelCardFullExpandable(
}
}
/**
* Expandable card that shows model info and system prompt
/*
*
* Individual components to be orchestrated into a model card's content
*
*/
@Composable
fun ModelCardWithSystemPrompt(
model: ModelInfo,
systemPrompt: String?,
modifier: Modifier = Modifier,
initiallyExpanded: Boolean = false
) {
var expanded by remember { mutableStateOf(initiallyExpanded) }
Card(
modifier = modifier
.fillMaxWidth(),
onClick = {
if (systemPrompt != null) {
expanded = !expanded
}
}
) {
Column(
modifier = Modifier.padding(16.dp)
) {
// Model info section
ModelCardContentCore(
model = model
)
// Add divider between model info and system prompt
if (!systemPrompt.isNullOrBlank()) {
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "System Prompt",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(1f)
)
Text(
text = if (expanded) "Hide" else "Show",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.primary
)
}
AnimatedVisibility(
visible = expanded,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
Text(
text = systemPrompt,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
)
}
}
}
}
}
@Composable
fun ModelCardContentCore(
model: ModelInfo,
modifier: Modifier = Modifier
) =
Column(modifier = modifier) {
// Row 1: Model full name
ModelCardContentTitleRow(model)
Spacer(modifier = Modifier.height(12.dp))
// Row 2: Context length, size label
ModelCardContentContextRow(model)
Spacer(modifier = Modifier.height(8.dp))
// Row 3: Architecture, quantization, formatted size
ModelCardContentArchitectureRow(model)
}
@Composable
private fun ModelCardContentTitleRow(model: ModelInfo) =
fun ModelCardContentTitleRow(model: ModelInfo) =
Text(
text = model.formattedFullName,
style = MaterialTheme.typography.titleLarge,
@ -332,7 +261,7 @@ private fun ModelCardContentTitleRow(model: ModelInfo) =
)
@Composable
private fun ModelCardContentContextRow(model: ModelInfo) =
fun ModelCardContentContextRow(model: ModelInfo) =
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
@ -343,7 +272,7 @@ private fun ModelCardContentContextRow(model: ModelInfo) =
}
@Composable
private fun ModelCardContentArchitectureRow(model: ModelInfo) =
fun ModelCardContentArchitectureRow(model: ModelInfo) =
Row(
modifier = Modifier.fillMaxWidth(),
) {
@ -355,7 +284,7 @@ private fun ModelCardContentArchitectureRow(model: ModelInfo) =
}
@Composable
private fun ModelCardContentDatesRow(model: ModelInfo) {
fun ModelCardContentDatesRow(model: ModelInfo) {
val dateFormatter = remember { SimpleDateFormat("MMM d, yyyy", Locale.getDefault()) }
Row(
@ -374,7 +303,7 @@ private fun ModelCardContentDatesRow(model: ModelInfo) {
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun ModelCardContentTagsSection(tags: List<String>) =
fun ModelCardContentTagsSection(tags: List<String>) =
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
@ -398,7 +327,7 @@ private fun ModelCardContentTagsSection(tags: List<String>) =
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun ModelCardContentLanguagesSections(languages: List<String>) =
fun ModelCardContentLanguagesSections(languages: List<String>) =
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
@ -421,7 +350,7 @@ private fun ModelCardContentLanguagesSections(languages: List<String>) =
}
@Composable
private fun ModelCardContentField(name: String, value: String) =
fun ModelCardContentField(name: String, value: String) =
Row {
Text(
text = name,

View File

@ -27,15 +27,19 @@ 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.data.model.ModelInfo
import com.example.llama.revamp.engine.ModelLoadingMetrics
import com.example.llama.revamp.ui.components.ModelCardContentArchitectureRow
import com.example.llama.revamp.ui.components.ModelCardContentContextRow
import com.example.llama.revamp.ui.components.ModelCardContentField
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.util.formatMilliSeconds
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
@ -67,8 +71,9 @@ fun BenchmarkScreen(
) {
// Selected model card
selectedModel?.let { model ->
ModelCardCoreExpandable(
ModelCardWithLoadingMetrics(
model = model,
loadingMetrics = loadingMetrics,
isExpanded = isModelCardExpanded,
onExpanded = { isModelCardExpanded = !isModelCardExpanded },
)
@ -141,3 +146,26 @@ fun BenchmarkScreen(
onNavigateBack = onNavigateBack,
)
}
@Composable
private fun ModelCardWithLoadingMetrics(
model: ModelInfo,
loadingMetrics: ModelLoadingMetrics,
isExpanded: Boolean = false,
onExpanded: ((Boolean) -> Unit)? = null,
) = ModelCardCoreExpandable(model, isExpanded, onExpanded) {
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)
Spacer(modifier = Modifier.height(8.dp))
// Row 4: Model loading time
ModelCardContentField("Loading time", formatMilliSeconds(loadingMetrics.modelLoadingTimeMs))
}

View File

@ -30,6 +30,7 @@ import androidx.compose.material.icons.filled.Send
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@ -52,14 +53,21 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import com.example.llama.revamp.APP_NAME
import com.example.llama.revamp.data.model.ModelInfo
import com.example.llama.revamp.engine.ModelLoadingMetrics
import com.example.llama.revamp.ui.components.ModelCardWithSystemPrompt
import com.example.llama.revamp.ui.components.ModelCardContentArchitectureRow
import com.example.llama.revamp.ui.components.ModelCardContentContextRow
import com.example.llama.revamp.ui.components.ModelCardContentField
import com.example.llama.revamp.ui.components.ModelCardCoreExpandable
import com.example.llama.revamp.ui.components.ModelUnloadDialogHandler
import com.example.llama.revamp.util.formatMilliSeconds
import com.example.llama.revamp.viewmodel.ConversationViewModel
import com.example.llama.revamp.viewmodel.Message
import kotlinx.coroutines.launch
@ -69,11 +77,11 @@ import kotlinx.coroutines.launch
*/
@Composable
fun ConversationScreen(
// TODO-han.yin: Use loading metrics to show UI
loadingMetrics: ModelLoadingMetrics,
onNavigateBack: () -> Unit,
viewModel: ConversationViewModel
) {
// View model states
val engineState by viewModel.engineState.collectAsState()
val messages by viewModel.messages.collectAsState()
val systemPrompt by viewModel.systemPrompt.collectAsState()
@ -83,10 +91,12 @@ fun ConversationScreen(
val isProcessing = engineState is State.ProcessingUserPrompt
val isGenerating = engineState is State.Generating
// UI states
val lifecycleOwner = LocalLifecycleOwner.current
val coroutineScope = rememberCoroutineScope()
var isModelCardExpanded by remember { mutableStateOf(false) }
val listState = rememberLazyListState()
var inputText by remember { mutableStateOf("") }
val coroutineScope = rememberCoroutineScope()
val lifecycleOwner = LocalLifecycleOwner.current
// Auto-scroll to bottom when messages change or when typing
val shouldScrollToBottom by remember(messages.size, isGenerating) {
@ -129,7 +139,15 @@ fun ConversationScreen(
) {
// System prompt display (collapsible)
selectedModel?.let {
ModelCardWithSystemPrompt(it, systemPrompt)
Box(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
ModelCardWithSystemPrompt(
model = it,
loadingMetrics = loadingMetrics,
systemPrompt = systemPrompt,
isExpanded = isModelCardExpanded,
onExpanded = { isModelCardExpanded = !isModelCardExpanded }
)
}
}
// Messages list
@ -167,6 +185,58 @@ fun ConversationScreen(
)
}
@Composable
fun ModelCardWithSystemPrompt(
model: ModelInfo,
loadingMetrics: ModelLoadingMetrics,
systemPrompt: String?,
isExpanded: Boolean = false,
onExpanded: ((Boolean) -> Unit)? = null,
) = ModelCardCoreExpandable(model, isExpanded, onExpanded) {
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)
Spacer(modifier = Modifier.height(8.dp))
// Row 4: Model loading time
ModelCardContentField("Loading time", formatMilliSeconds(loadingMetrics.modelLoadingTimeMs))
if (!systemPrompt.isNullOrBlank()) {
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
Text(
text = "System Prompt",
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Normal,
color = MaterialTheme.colorScheme.onSurface,
)
Spacer(Modifier.height(6.dp))
Text(
text = systemPrompt,
style = MaterialTheme.typography.bodySmall,
fontWeight = FontWeight.ExtraLight,
fontStyle = FontStyle.Italic,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(Modifier.height(6.dp))
loadingMetrics.systemPromptProcessingTimeMs?.let {
ModelCardContentField("Processing time", formatMilliSeconds(it))
}
}
}
@Composable
private fun ConversationMessageList(
messages: List<Message>,

View File

@ -22,7 +22,9 @@ import androidx.compose.ui.text.font.FontWeight
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.ui.components.ModelCardContentCore
import com.example.llama.revamp.ui.components.ModelCardContentArchitectureRow
import com.example.llama.revamp.ui.components.ModelCardContentContextRow
import com.example.llama.revamp.ui.components.ModelCardContentTitleRow
import com.example.llama.revamp.util.FileType
import java.text.SimpleDateFormat
import java.util.Date
@ -39,8 +41,18 @@ fun ModelDetailsScreen(
.padding(horizontal = 16.dp, vertical = 8.dp)
.verticalScroll(rememberScrollState())
) {
// Always show the core and expanded content
ModelCardContentCore(model = model)
// Row 1: Model full name
ModelCardContentTitleRow(model)
Spacer(modifier = Modifier.height(12.dp))
// Row 2: Context length, size label
ModelCardContentContextRow(model)
Spacer(modifier = Modifier.height(8.dp))
// Row 3: Architecture, quantization, formatted size
ModelCardContentArchitectureRow(model)
Spacer(modifier = Modifier.height(16.dp))