UI: add quick action buttons to benchmark screen's result card

This commit is contained in:
Han Yin 2025-08-29 16:21:57 -07:00
parent 659f59e22a
commit c848005d11
4 changed files with 95 additions and 28 deletions

View File

@ -259,9 +259,7 @@ fun AppContent(
// Benchmark screen
currentRoute.startsWith(AppDestinations.BENCHMARK_ROUTE) -> {
val engineState by benchmarkViewModel.engineState.collectAsState()
val showModelCard by benchmarkViewModel.showModelCard.collectAsState()
val benchmarkResults by benchmarkViewModel.benchmarkResults.collectAsState()
ScaffoldConfig(
topBarConfig = TopBarConfig.Performance(
@ -274,22 +272,9 @@ fun AppContent(
),
bottomBarConfig = BottomBarConfig.Benchmark(
engineIdle = !engineState.isUninterruptible,
onShare = {
benchmarkResults.lastOrNull()?.let {
handleScaffoldEvent(ScaffoldEvent.ShareText(it.text))
}
},
onRerun = {
if (engineState.isUninterruptible) {
handleScaffoldEvent(ScaffoldEvent.ShowSnackbar(
message = "Benchmark already in progress!\n" +
"Please wait for the current run to complete."
))
} else {
benchmarkViewModel.runBenchmark()
}
},
onClear = benchmarkViewModel::clearResults,
onShare = { benchmarkViewModel.shareResult(handleScaffoldEvent) },
onRerun = { benchmarkViewModel.rerunBenchmark(handleScaffoldEvent) },
onClear = { benchmarkViewModel.clearResults(handleScaffoldEvent) },
showModelCard = showModelCard,
onToggleModelCard = benchmarkViewModel::toggleModelCard,
)
@ -478,6 +463,7 @@ fun AppContent(
BenchmarkScreen(
loadingMetrics = metrics,
onScaffoldEvent = handleScaffoldEvent,
onNavigateBack = { navigationActions.navigateUp() },
viewModel = benchmarkViewModel
)

View File

@ -27,22 +27,25 @@ fun BenchmarkBottomBar(
showModelCard: Boolean,
onToggleModelCard: (Boolean) -> Unit,
) {
val controlTint =
if (engineIdle) MaterialTheme.colorScheme.onSurface
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)
BottomAppBar(
actions = {
IconButton(onClick = onRerun) {
Icon(
imageVector = Icons.Default.Replay,
contentDescription = "Run the benchmark again",
tint =
if (engineIdle) MaterialTheme.colorScheme.onSurface
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)
tint = controlTint
)
}
IconButton(onClick = onClear) {
Icon(
imageVector = Icons.Default.ClearAll,
contentDescription = "Clear benchmark results"
contentDescription = "Clear benchmark results",
tint = controlTint
)
}

View File

@ -1,6 +1,8 @@
package com.example.llama.ui.screens
import android.content.Intent
import android.llama.cpp.InferenceEngine.State
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@ -19,11 +21,17 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Replay
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -36,10 +44,12 @@ import androidx.compose.runtime.saveable.rememberSaveable
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.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import com.example.llama.data.model.ModelInfo
import com.example.llama.engine.ModelLoadingMetrics
import com.example.llama.ui.components.ModelCardContentArchitectureRow
@ -47,6 +57,7 @@ import com.example.llama.ui.components.ModelCardContentContextRow
import com.example.llama.ui.components.ModelCardContentField
import com.example.llama.ui.components.ModelCardCoreExpandable
import com.example.llama.ui.components.ModelUnloadDialogHandler
import com.example.llama.ui.scaffold.ScaffoldEvent
import com.example.llama.util.TableData
import com.example.llama.util.formatMilliSeconds
import com.example.llama.util.parseMarkdownTable
@ -57,9 +68,12 @@ import com.example.llama.viewmodel.BenchmarkViewModel
@Composable
fun BenchmarkScreen(
loadingMetrics: ModelLoadingMetrics,
onScaffoldEvent: (ScaffoldEvent) -> Unit,
onNavigateBack: () -> Unit,
viewModel: BenchmarkViewModel
) {
val context = LocalContext.current
// View model states
val engineState by viewModel.engineState.collectAsState()
val unloadDialogState by viewModel.unloadModelState.collectAsState()
@ -86,6 +100,12 @@ fun BenchmarkScreen(
viewModel.onBackPressed(onNavigateBack)
}
val onInfo = {
Toast.makeText(context, "Please refer to this post for more details on the benchmark methodology", Toast.LENGTH_SHORT).show()
val intent = Intent(Intent.ACTION_VIEW, "https://blog.steelph0enix.dev/posts/llama-cpp-guide/#llama-bench".toUri())
context.startActivity(intent)
}
Box(
modifier = Modifier.fillMaxSize()
) {
@ -96,7 +116,11 @@ fun BenchmarkScreen(
verticalArrangement = Arrangement.Bottom,
) {
items(items = benchmarkResults) {
BenchmarkResultCard(it)
BenchmarkResultCard(
result = it,
onRerun = { viewModel.rerunBenchmark(onScaffoldEvent) },
onInfo = onInfo,
)
}
}
@ -186,7 +210,11 @@ private fun ModelCardWithLoadingMetrics(
@Composable
fun BenchmarkResultCard(result: BenchmarkResult) {
fun BenchmarkResultCard(
result: BenchmarkResult,
onRerun: () -> Unit,
onInfo: () -> Unit,
) {
val rawTable = parseMarkdownTable(result.text.trimIndent())
val model = rawTable.getColumn("model").firstOrNull() ?: "Unknown"
val parameters = rawTable.getColumn("params").firstOrNull() ?: "-"
@ -236,6 +264,28 @@ fun BenchmarkResultCard(result: BenchmarkResult) {
BenchmarkResultTable(rawTable)
ModelCardContentField("Time spent: ", formatMilliSeconds(result.duration))
Spacer(modifier = Modifier.height(8.dp))
Row {
OutlinedButton(onClick = onRerun) {
Icon(
imageVector = Icons.Default.Replay,
contentDescription = "Run the benchmark again"
)
Text("Run again", modifier = Modifier.padding(start = 6.dp))
}
Spacer(modifier = Modifier.weight(1f))
FilledTonalButton(onClick = onInfo) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = "Information about what the result means"
)
Text("How to interpret", modifier = Modifier.padding(start = 6.dp))
}
}
}
}
}

View File

@ -4,6 +4,7 @@ import android.llama.cpp.isUninterruptible
import androidx.lifecycle.viewModelScope
import com.example.llama.data.model.ModelInfo
import com.example.llama.engine.BenchmarkService
import com.example.llama.ui.scaffold.ScaffoldEvent
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@ -30,7 +31,7 @@ class BenchmarkViewModel @Inject constructor(
val benchmarkResults: StateFlow<List<BenchmarkResult>> = _benchmarkResults.asStateFlow()
// UI state: Model card
private val _showModelCard = MutableStateFlow(true)
private val _showModelCard = MutableStateFlow(false)
val showModelCard = _showModelCard.asStateFlow()
fun toggleModelCard(show: Boolean) {
@ -68,10 +69,37 @@ class BenchmarkViewModel @Inject constructor(
return true
}
override suspend fun performCleanup() = clearResults()
override suspend fun performCleanup() { clearResults(null) }
fun clearResults() {
_benchmarkResults.value = emptyList()
fun clearResults(onScaffoldEvent: ((ScaffoldEvent) -> Unit)?) =
if (engineState.value.isUninterruptible) {
false
} else {
_benchmarkResults.value = emptyList()
onScaffoldEvent?.invoke(ScaffoldEvent.ShowSnackbar(
message = "All benchmark results cleared."
))
true
}
/**
* Rerun the benchmark
*/
fun rerunBenchmark(onScaffoldEvent: (ScaffoldEvent) -> Unit) {
if (engineState.value.isUninterruptible) {
onScaffoldEvent(ScaffoldEvent.ShowSnackbar(
message = "Benchmark already in progress!\n" +
"Please wait for the current run to complete."
))
} else {
runBenchmark()
}
}
fun shareResult(onScaffoldEvent: (ScaffoldEvent) -> Unit) {
_benchmarkResults.value.lastOrNull()?.let{
onScaffoldEvent(ScaffoldEvent.ShareText(it.text))
}
}
}