diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/components/ModelCards.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/components/ModelCards.kt index 136e4b94b8..03ff799087 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/components/ModelCards.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/components/ModelCards.kt @@ -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) = +fun ModelCardContentTagsSection(tags: List) = FlowRow( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -398,7 +327,7 @@ private fun ModelCardContentTagsSection(tags: List) = @OptIn(ExperimentalLayoutApi::class) @Composable -private fun ModelCardContentLanguagesSections(languages: List) = +fun ModelCardContentLanguagesSections(languages: List) = FlowRow( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -421,7 +350,7 @@ private fun ModelCardContentLanguagesSections(languages: List) = } @Composable -private fun ModelCardContentField(name: String, value: String) = +fun ModelCardContentField(name: String, value: String) = Row { Text( text = name, diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/BenchmarkScreen.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/BenchmarkScreen.kt index c68616528f..fce93d89e0 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/BenchmarkScreen.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/BenchmarkScreen.kt @@ -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)) +} diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ConversationScreen.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ConversationScreen.kt index 69477dd0c8..84d447f896 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ConversationScreen.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ConversationScreen.kt @@ -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, diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ModelDetailsScreen.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ModelDetailsScreen.kt index 7bfbe6c871..52bce55810 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ModelDetailsScreen.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ModelDetailsScreen.kt @@ -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))