UI: polish ModelLoading screen
This commit is contained in:
parent
57b5001f5c
commit
d7afcc41d5
|
|
@ -17,11 +17,16 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
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.AssistChip
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
|
@ -49,44 +54,70 @@ import java.util.Locale
|
||||||
* architecture, quantization and file size in a compact card format.
|
* architecture, quantization and file size in a compact card format.
|
||||||
*
|
*
|
||||||
* @param model The model information to display
|
* @param model The model information to display
|
||||||
* @param onClick Action to perform when the card is clicked
|
* @param isExpanded Whether additional details is expanded or shrunk
|
||||||
* @param isSelected Optional selection state (shows checkbox when not null)
|
* @param onExpanded Action to perform when the card is expanded or shrunk
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ModelCardCore(
|
fun ModelCardCoreExpandable(
|
||||||
model: ModelInfo,
|
model: ModelInfo,
|
||||||
onClick: () -> Unit,
|
isExpanded: Boolean = false,
|
||||||
isSelected: Boolean? = null,
|
onExpanded: ((Boolean) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(onClick = onClick),
|
.clickable { onExpanded?.invoke(!isExpanded) },
|
||||||
colors = when (isSelected) {
|
colors = when (isExpanded) {
|
||||||
true -> CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
|
true -> CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer
|
||||||
|
)
|
||||||
false -> CardDefaults.cardColors()
|
false -> CardDefaults.cardColors()
|
||||||
else -> CardDefaults.cardColors()
|
|
||||||
},
|
},
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
// Show checkbox if in selection mode
|
// Row 1: Model full name + chevron
|
||||||
if (isSelected != null) {
|
Row(
|
||||||
Checkbox(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
checked = isSelected,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
onCheckedChange = { onClick() },
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(end = 8.dp)
|
) {
|
||||||
|
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
|
// Expandable content
|
||||||
ModelCardContentCore(
|
AnimatedVisibility(
|
||||||
model = model,
|
visible = isExpanded,
|
||||||
|
enter = expandVertically() + fadeIn(),
|
||||||
|
exit = shrinkVertically() + fadeOut()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
modifier = Modifier.weight(1f)
|
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)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ModelCardExpandable(
|
fun ModelCardFullExpandable(
|
||||||
model: ModelInfo,
|
model: ModelInfo,
|
||||||
isSelected: Boolean? = null,
|
isSelected: Boolean? = null,
|
||||||
onSelected: ((Boolean) -> Unit)? = null,
|
onSelected: ((Boolean) -> Unit)? = null,
|
||||||
|
|
@ -117,11 +148,11 @@ fun ModelCardExpandable(
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable { onExpanded?.invoke(!isExpanded) },
|
||||||
onExpanded?.invoke(!isExpanded)
|
|
||||||
},
|
|
||||||
colors = when (isSelected) {
|
colors = when (isSelected) {
|
||||||
true -> CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
|
true -> CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer
|
||||||
|
)
|
||||||
false -> CardDefaults.cardColors()
|
false -> CardDefaults.cardColors()
|
||||||
else -> CardDefaults.cardColors()
|
else -> CardDefaults.cardColors()
|
||||||
},
|
},
|
||||||
|
|
@ -148,11 +179,7 @@ fun ModelCardExpandable(
|
||||||
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
||||||
) {
|
) {
|
||||||
// Row 1: Model full name
|
// Row 1: Model full name
|
||||||
Text(
|
ModelCardContentTitleRow(model)
|
||||||
text = model.formattedFullName,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Medium
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,11 +310,7 @@ fun ModelCardContentCore(
|
||||||
) =
|
) =
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
// Row 1: Model full name
|
// Row 1: Model full name
|
||||||
Text(
|
ModelCardContentTitleRow(model)
|
||||||
text = model.formattedFullName,
|
|
||||||
style = MaterialTheme.typography.headlineSmall, // TODO
|
|
||||||
fontWeight = FontWeight.Medium
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
|
@ -300,6 +323,14 @@ fun ModelCardContentCore(
|
||||||
ModelCardContentArchitectureRow(model)
|
ModelCardContentArchitectureRow(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ModelCardContentTitleRow(model: ModelInfo) =
|
||||||
|
Text(
|
||||||
|
text = model.formattedFullName,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ModelCardContentContextRow(model: ModelInfo) =
|
private fun ModelCardContentContextRow(model: ModelInfo) =
|
||||||
Row(
|
Row(
|
||||||
|
|
|
||||||
|
|
@ -21,26 +21,34 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.example.llama.revamp.engine.ModelLoadingMetrics
|
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.components.ModelUnloadDialogHandler
|
||||||
import com.example.llama.revamp.ui.theme.MonospacedTextStyle
|
import com.example.llama.revamp.ui.theme.MonospacedTextStyle
|
||||||
import com.example.llama.revamp.viewmodel.BenchmarkViewModel
|
import com.example.llama.revamp.viewmodel.BenchmarkViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BenchmarkScreen(
|
fun BenchmarkScreen(
|
||||||
|
// TODO-han.yin: Use loading metrics to show UI
|
||||||
loadingMetrics: ModelLoadingMetrics,
|
loadingMetrics: ModelLoadingMetrics,
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
viewModel: BenchmarkViewModel
|
viewModel: BenchmarkViewModel
|
||||||
) {
|
) {
|
||||||
|
// View model states
|
||||||
val engineState by viewModel.engineState.collectAsState()
|
val engineState by viewModel.engineState.collectAsState()
|
||||||
val benchmarkResults by viewModel.benchmarkResults.collectAsState()
|
val benchmarkResults by viewModel.benchmarkResults.collectAsState()
|
||||||
val selectedModel by viewModel.selectedModel.collectAsState()
|
val selectedModel by viewModel.selectedModel.collectAsState()
|
||||||
val unloadDialogState by viewModel.unloadModelState.collectAsState()
|
val unloadDialogState by viewModel.unloadModelState.collectAsState()
|
||||||
|
|
||||||
|
// UI states
|
||||||
|
var isModelCardExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Run benchmark when entering the screen
|
// Run benchmark when entering the screen
|
||||||
LaunchedEffect(selectedModel) {
|
LaunchedEffect(selectedModel) {
|
||||||
viewModel.runBenchmark()
|
viewModel.runBenchmark()
|
||||||
|
|
@ -59,10 +67,10 @@ fun BenchmarkScreen(
|
||||||
) {
|
) {
|
||||||
// Selected model card
|
// Selected model card
|
||||||
selectedModel?.let { model ->
|
selectedModel?.let { model ->
|
||||||
ModelCardCore(
|
ModelCardCoreExpandable(
|
||||||
model = model,
|
model = model,
|
||||||
onClick = { /* No action on click */ },
|
isExpanded = isModelCardExpanded,
|
||||||
isSelected = null
|
onExpanded = { isModelCardExpanded = !isModelCardExpanded },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ import kotlinx.coroutines.launch
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ConversationScreen(
|
fun ConversationScreen(
|
||||||
|
// TODO-han.yin: Use loading metrics to show UI
|
||||||
loadingMetrics: ModelLoadingMetrics,
|
loadingMetrics: ModelLoadingMetrics,
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
viewModel: ConversationViewModel
|
viewModel: ConversationViewModel
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.example.llama.revamp.data.model.SystemPrompt
|
import com.example.llama.revamp.data.model.SystemPrompt
|
||||||
import com.example.llama.revamp.engine.ModelLoadingMetrics
|
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.components.ModelUnloadDialogHandler
|
||||||
import com.example.llama.revamp.viewmodel.ModelLoadingViewModel
|
import com.example.llama.revamp.viewmodel.ModelLoadingViewModel
|
||||||
|
|
||||||
|
|
@ -72,12 +72,15 @@ fun ModelLoadingScreen(
|
||||||
onNavigateToConversation: (ModelLoadingMetrics) -> Unit,
|
onNavigateToConversation: (ModelLoadingMetrics) -> Unit,
|
||||||
viewModel: ModelLoadingViewModel,
|
viewModel: ModelLoadingViewModel,
|
||||||
) {
|
) {
|
||||||
|
// View model states
|
||||||
val engineState by viewModel.engineState.collectAsState()
|
val engineState by viewModel.engineState.collectAsState()
|
||||||
val selectedModel by viewModel.selectedModel.collectAsState()
|
val selectedModel by viewModel.selectedModel.collectAsState()
|
||||||
val presetPrompts by viewModel.presetPrompts.collectAsState()
|
val presetPrompts by viewModel.presetPrompts.collectAsState()
|
||||||
val recentPrompts by viewModel.recentPrompts.collectAsState()
|
val recentPrompts by viewModel.recentPrompts.collectAsState()
|
||||||
val unloadDialogState by viewModel.unloadModelState.collectAsState()
|
val unloadDialogState by viewModel.unloadModelState.collectAsState()
|
||||||
|
|
||||||
|
// UI states
|
||||||
|
var isModelCardExpanded by remember { mutableStateOf(false) }
|
||||||
var selectedMode by remember { mutableStateOf<Mode?>(null) }
|
var selectedMode by remember { mutableStateOf<Mode?>(null) }
|
||||||
var useSystemPrompt by remember { mutableStateOf(false) }
|
var useSystemPrompt by remember { mutableStateOf(false) }
|
||||||
var selectedPrompt by remember { mutableStateOf<SystemPrompt?>(null) }
|
var selectedPrompt by remember { mutableStateOf<SystemPrompt?>(null) }
|
||||||
|
|
@ -116,11 +119,13 @@ fun ModelLoadingScreen(
|
||||||
) {
|
) {
|
||||||
// Selected model card
|
// Selected model card
|
||||||
selectedModel?.let { model ->
|
selectedModel?.let { model ->
|
||||||
ModelCardCore(
|
ModelCardCoreExpandable(
|
||||||
model = model,
|
model = model,
|
||||||
onClick = { /* No action on click */ },
|
isExpanded = isModelCardExpanded,
|
||||||
isSelected = null
|
onExpanded = { isModelCardExpanded = !isModelCardExpanded },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmark card
|
// Benchmark card
|
||||||
|
|
@ -148,7 +153,7 @@ fun ModelLoadingScreen(
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Benchmark",
|
text = "Benchmark",
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
modifier = Modifier.padding(start = 8.dp)
|
modifier = Modifier.padding(start = 8.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -159,6 +164,12 @@ fun ModelLoadingScreen(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(bottom = 4.dp)
|
.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
|
// Only fill height if system prompt is active
|
||||||
.then(if (useSystemPrompt) Modifier.weight(1f) else Modifier)
|
.then(if (useSystemPrompt) Modifier.weight(1f) else Modifier)
|
||||||
) {
|
) {
|
||||||
|
|
@ -173,12 +184,6 @@ fun ModelLoadingScreen(
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.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),
|
.padding(top = 16.dp, start = 16.dp, end = 16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
|
@ -188,7 +193,7 @@ fun ModelLoadingScreen(
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Conversation",
|
text = "Conversation",
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
modifier = Modifier.padding(start = 8.dp)
|
modifier = Modifier.padding(start = 8.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.example.llama.revamp.data.model.ModelInfo
|
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
|
import com.example.llama.revamp.viewmodel.ModelSelectionViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -65,7 +65,7 @@ fun ModelSelectionScreen(
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
items(items = models, key = { it.id }) { model ->
|
items(items = models, key = { it.id }) { model ->
|
||||||
ModelCardExpandable(
|
ModelCardFullExpandable(
|
||||||
model = model,
|
model = model,
|
||||||
isSelected = if (model == preselectedModel) true else null,
|
isSelected = if (model == preselectedModel) true else null,
|
||||||
onSelected = { selected ->
|
onSelected = { selected ->
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import com.example.llama.revamp.data.model.ModelInfo
|
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.ui.components.ScaffoldEvent
|
||||||
import com.example.llama.revamp.util.formatFileByteSize
|
import com.example.llama.revamp.util.formatFileByteSize
|
||||||
import com.example.llama.revamp.viewmodel.ModelManagementState
|
import com.example.llama.revamp.viewmodel.ModelManagementState
|
||||||
|
|
@ -84,7 +84,7 @@ fun ModelsManagementScreen(
|
||||||
items(items = sortedModels, key = { it.id }) { model ->
|
items(items = sortedModels, key = { it.id }) { model ->
|
||||||
val isSelected = if (isMultiSelectionMode) selectedModels.contains(model.id) else null
|
val isSelected = if (isMultiSelectionMode) selectedModels.contains(model.id) else null
|
||||||
|
|
||||||
ModelCardExpandable(
|
ModelCardFullExpandable(
|
||||||
model = model,
|
model = model,
|
||||||
isSelected = isSelected,
|
isSelected = isSelected,
|
||||||
onSelected = {
|
onSelected = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue