UI: show RAM warning if model too large
This commit is contained in:
parent
a5a54375a2
commit
712bc745df
|
|
@ -193,7 +193,7 @@ fun AppContent(
|
||||||
val showSortMenu by modelSelectionViewModel.showSortMenu.collectAsState()
|
val showSortMenu by modelSelectionViewModel.showSortMenu.collectAsState()
|
||||||
val activeFilters by modelSelectionViewModel.activeFilters.collectAsState()
|
val activeFilters by modelSelectionViewModel.activeFilters.collectAsState()
|
||||||
val showFilterMenu by modelSelectionViewModel.showFilterMenu.collectAsState()
|
val showFilterMenu by modelSelectionViewModel.showFilterMenu.collectAsState()
|
||||||
val preselectedModel by modelSelectionViewModel.preselectedModel.collectAsState()
|
val preselection by modelSelectionViewModel.preselection.collectAsState()
|
||||||
|
|
||||||
ScaffoldConfig(
|
ScaffoldConfig(
|
||||||
topBarConfig =
|
topBarConfig =
|
||||||
|
|
@ -230,12 +230,13 @@ fun AppContent(
|
||||||
toggleMenu = modelSelectionViewModel::toggleFilterMenu
|
toggleMenu = modelSelectionViewModel::toggleFilterMenu
|
||||||
),
|
),
|
||||||
runAction = BottomBarConfig.ModelSelection.RunActionConfig(
|
runAction = BottomBarConfig.ModelSelection.RunActionConfig(
|
||||||
selectedModel = preselectedModel,
|
preselection = preselection,
|
||||||
onRun = { model ->
|
onClickRun = { preselection ->
|
||||||
modelSelectionViewModel.confirmSelectedModel(model)
|
if (modelSelectionViewModel.selectModel(preselection)) {
|
||||||
navigationActions.navigateToModelLoading()
|
navigationActions.navigateToModelLoading()
|
||||||
modelSelectionViewModel.toggleSearchState(false)
|
modelSelectionViewModel.toggleSearchState(false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -437,6 +438,12 @@ fun AppContent(
|
||||||
onManageModelsClicked = {
|
onManageModelsClicked = {
|
||||||
navigationActions.navigateToModelsManagement()
|
navigationActions.navigateToModelsManagement()
|
||||||
},
|
},
|
||||||
|
onConfirmSelection = { modelInfo, ramWarning ->
|
||||||
|
if (modelSelectionViewModel.confirmSelectedModel(modelInfo, ramWarning)) {
|
||||||
|
navigationActions.navigateToModelLoading()
|
||||||
|
modelSelectionViewModel.toggleSearchState(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
viewModel = modelSelectionViewModel
|
viewModel = modelSelectionViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package com.example.llama.ui.components
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
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.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
|
@ -27,13 +26,14 @@ data class InfoAction(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InfoView(
|
fun InfoView(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
title: String,
|
title: String,
|
||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
message: String? = null,
|
message: String? = null,
|
||||||
action: InfoAction? = null
|
action: InfoAction? = null
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize().padding(16.dp),
|
modifier = modifier.padding(16.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
|
|
@ -62,10 +62,9 @@ fun InfoView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
action?.let {
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
|
||||||
action?.let {
|
|
||||||
Button(onClick = action.onAction) {
|
Button(onClick = action.onAction) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = action.icon,
|
imageVector = action.icon,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.text.input.TextFieldState
|
||||||
import com.example.llama.data.model.ModelFilter
|
import com.example.llama.data.model.ModelFilter
|
||||||
import com.example.llama.data.model.ModelInfo
|
import com.example.llama.data.model.ModelInfo
|
||||||
import com.example.llama.data.model.ModelSortOrder
|
import com.example.llama.data.model.ModelSortOrder
|
||||||
|
import com.example.llama.viewmodel.Preselection
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [BottomAppBar] configurations
|
* [BottomAppBar] configurations
|
||||||
|
|
@ -42,8 +43,8 @@ sealed class BottomBarConfig {
|
||||||
)
|
)
|
||||||
|
|
||||||
data class RunActionConfig(
|
data class RunActionConfig(
|
||||||
val selectedModel: ModelInfo?,
|
val preselection: Preselection?,
|
||||||
val onRun: (ModelInfo) -> Unit
|
val onClickRun: (Preselection) -> Unit,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,12 +173,12 @@ fun ModelSelectionBottomBar(
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
// Only show FAB if a model is selected
|
// Only show FAB if a model is selected
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = runAction.selectedModel != null,
|
visible = runAction.preselection != null,
|
||||||
enter = scaleIn() + fadeIn(),
|
enter = scaleIn() + fadeIn(),
|
||||||
exit = scaleOut() + fadeOut()
|
exit = scaleOut() + fadeOut()
|
||||||
) {
|
) {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = { runAction.selectedModel?.let { runAction.onRun(it) } },
|
onClick = { runAction.preselection?.let { runAction.onClickRun(it) } },
|
||||||
containerColor = MaterialTheme.colorScheme.primary
|
containerColor = MaterialTheme.colorScheme.primary
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import androidx.compose.material.icons.filled.FolderOpen
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material.icons.filled.SearchOff
|
import androidx.compose.material.icons.filled.SearchOff
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.DockedSearchBar
|
import androidx.compose.material3.DockedSearchBar
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
|
@ -27,6 +29,7 @@ import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.SearchBarDefaults
|
import androidx.compose.material3.SearchBarDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
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
|
||||||
|
|
@ -40,20 +43,24 @@ import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
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.data.model.ModelInfo
|
||||||
import com.example.llama.ui.components.InfoAction
|
import com.example.llama.ui.components.InfoAction
|
||||||
import com.example.llama.ui.components.InfoView
|
import com.example.llama.ui.components.InfoView
|
||||||
import com.example.llama.ui.components.ModelCardFullExpandable
|
import com.example.llama.ui.components.ModelCardFullExpandable
|
||||||
|
import com.example.llama.util.formatFileByteSize
|
||||||
import com.example.llama.viewmodel.ModelSelectionViewModel
|
import com.example.llama.viewmodel.ModelSelectionViewModel
|
||||||
|
import com.example.llama.viewmodel.Preselection.RamWarning
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ModelSelectionScreen(
|
fun ModelSelectionScreen(
|
||||||
onManageModelsClicked: () -> Unit,
|
onManageModelsClicked: () -> Unit,
|
||||||
|
onConfirmSelection: (ModelInfo, RamWarning) -> Unit,
|
||||||
viewModel: ModelSelectionViewModel,
|
viewModel: ModelSelectionViewModel,
|
||||||
) {
|
) {
|
||||||
// Data: models
|
// Data: models
|
||||||
val filteredModels by viewModel.filteredModels.collectAsState()
|
val filteredModels by viewModel.filteredModels.collectAsState()
|
||||||
val preselectedModel by viewModel.preselectedModel.collectAsState()
|
val preselection by viewModel.preselection.collectAsState()
|
||||||
|
|
||||||
// Query states
|
// Query states
|
||||||
val textFieldState = viewModel.searchFieldState
|
val textFieldState = viewModel.searchFieldState
|
||||||
|
|
@ -83,7 +90,7 @@ fun ModelSelectionScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle back button press
|
// Handle back button press
|
||||||
BackHandler(preselectedModel != null || isSearchActive) {
|
BackHandler(preselection != null || isSearchActive) {
|
||||||
if (isSearchActive) {
|
if (isSearchActive) {
|
||||||
viewModel.toggleSearchState(false)
|
viewModel.toggleSearchState(false)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -143,7 +150,7 @@ fun ModelSelectionScreen(
|
||||||
items(items = queryResults, key = { it.id }) { model ->
|
items(items = queryResults, key = { it.id }) { model ->
|
||||||
ModelCardFullExpandable(
|
ModelCardFullExpandable(
|
||||||
model = model,
|
model = model,
|
||||||
isSelected = if (model == preselectedModel) true else null,
|
isSelected = if (model == preselection?.modelInfo) true else null,
|
||||||
onSelected = { selected ->
|
onSelected = { selected ->
|
||||||
if (selected) {
|
if (selected) {
|
||||||
toggleSearchFocusAndIme(false)
|
toggleSearchFocusAndIme(false)
|
||||||
|
|
@ -152,7 +159,7 @@ fun ModelSelectionScreen(
|
||||||
toggleSearchFocusAndIme(true)
|
toggleSearchFocusAndIme(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isExpanded = model == preselectedModel,
|
isExpanded = model == preselection?.modelInfo,
|
||||||
onExpanded = { expanded ->
|
onExpanded = { expanded ->
|
||||||
viewModel.preselectModel(model, expanded)
|
viewModel.preselectModel(model, expanded)
|
||||||
toggleSearchFocusAndIme(!expanded)
|
toggleSearchFocusAndIme(!expanded)
|
||||||
|
|
@ -176,11 +183,11 @@ fun ModelSelectionScreen(
|
||||||
items(items = filteredModels, key = { it.id }) { model ->
|
items(items = filteredModels, key = { it.id }) { model ->
|
||||||
ModelCardFullExpandable(
|
ModelCardFullExpandable(
|
||||||
model = model,
|
model = model,
|
||||||
isSelected = if (model == preselectedModel) true else null,
|
isSelected = if (model == preselection?.modelInfo) true else null,
|
||||||
onSelected = { selected ->
|
onSelected = { selected ->
|
||||||
if (!selected) viewModel.resetSelection()
|
if (!selected) viewModel.resetSelection()
|
||||||
},
|
},
|
||||||
isExpanded = model == preselectedModel,
|
isExpanded = model == preselection?.modelInfo,
|
||||||
onExpanded = { expanded ->
|
onExpanded = { expanded ->
|
||||||
viewModel.preselectModel(model, expanded)
|
viewModel.preselectModel(model, expanded)
|
||||||
}
|
}
|
||||||
|
|
@ -189,6 +196,19 @@ fun ModelSelectionScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show insufficient RAM warning
|
||||||
|
preselection?.let {
|
||||||
|
it.ramWarning?.let { warning ->
|
||||||
|
if (warning.showing) {
|
||||||
|
RamErrorDialog(
|
||||||
|
warning,
|
||||||
|
onDismiss = { viewModel.dismissRamWarning() },
|
||||||
|
onConfirm = { onConfirmSelection(it.modelInfo, warning) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,6 +223,7 @@ private fun EmptyModelsView(
|
||||||
else -> "No models match the selected filters"
|
else -> "No models match the selected filters"
|
||||||
}
|
}
|
||||||
InfoView(
|
InfoView(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
title = "No Models Available",
|
title = "No Models Available",
|
||||||
icon = Icons.Default.FolderOpen,
|
icon = Icons.Default.FolderOpen,
|
||||||
message = message,
|
message = message,
|
||||||
|
|
@ -253,3 +274,42 @@ private fun EmptySearchResultsView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RamErrorDialog(
|
||||||
|
ramError: RamWarning,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onConfirm: () -> Unit,
|
||||||
|
) {
|
||||||
|
val requiredRam = formatFileByteSize(ramError.requiredRam)
|
||||||
|
val availableRam = formatFileByteSize(ramError.availableRam)
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
text = {
|
||||||
|
InfoView(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
title = "Insufficient RAM",
|
||||||
|
icon = Icons.Default.Warning,
|
||||||
|
message = "You are trying to run a $requiredRam size model, " +
|
||||||
|
"but currently there's only $availableRam memory available!",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||||
|
textContentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onConfirm) {
|
||||||
|
Text(
|
||||||
|
text = "Proceed",
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ fun ModelsManagementScreen(
|
||||||
else -> "No models match the selected filters"
|
else -> "No models match the selected filters"
|
||||||
}
|
}
|
||||||
InfoView(
|
InfoView(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
title = "No Models Available",
|
title = "No Models Available",
|
||||||
icon = Icons.Default.FolderOpen,
|
icon = Icons.Default.FolderOpen,
|
||||||
message = message,
|
message = message,
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,18 @@ import com.example.llama.data.model.queryBy
|
||||||
import com.example.llama.data.model.sortByOrder
|
import com.example.llama.data.model.sortByOrder
|
||||||
import com.example.llama.data.repo.ModelRepository
|
import com.example.llama.data.repo.ModelRepository
|
||||||
import com.example.llama.engine.InferenceService
|
import com.example.llama.engine.InferenceService
|
||||||
|
import com.example.llama.monitoring.PerformanceMonitor
|
||||||
|
import com.example.llama.viewmodel.Preselection.RamWarning
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -29,13 +33,14 @@ import javax.inject.Inject
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ModelSelectionViewModel @Inject constructor(
|
class ModelSelectionViewModel @Inject constructor(
|
||||||
|
modelRepository: ModelRepository,
|
||||||
|
private val performanceMonitor: PerformanceMonitor,
|
||||||
private val inferenceService: InferenceService,
|
private val inferenceService: InferenceService,
|
||||||
modelRepository: ModelRepository
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
// UI state: search mode
|
// UI state: search mode
|
||||||
private val _isSearchActive = MutableStateFlow(false)
|
private val _isSearchActive = MutableStateFlow(false)
|
||||||
val isSearchActive: StateFlow<Boolean> = _isSearchActive.asStateFlow()
|
val isSearchActive = _isSearchActive.asStateFlow()
|
||||||
|
|
||||||
fun toggleSearchState(active: Boolean) {
|
fun toggleSearchState(active: Boolean) {
|
||||||
_isSearchActive.value = active
|
_isSearchActive.value = active
|
||||||
|
|
@ -50,14 +55,14 @@ class ModelSelectionViewModel @Inject constructor(
|
||||||
|
|
||||||
// UI state: sort menu
|
// UI state: sort menu
|
||||||
private val _sortOrder = MutableStateFlow(ModelSortOrder.LAST_USED)
|
private val _sortOrder = MutableStateFlow(ModelSortOrder.LAST_USED)
|
||||||
val sortOrder: StateFlow<ModelSortOrder> = _sortOrder.asStateFlow()
|
val sortOrder = _sortOrder.asStateFlow()
|
||||||
|
|
||||||
fun setSortOrder(order: ModelSortOrder) {
|
fun setSortOrder(order: ModelSortOrder) {
|
||||||
_sortOrder.value = order
|
_sortOrder.value = order
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _showSortMenu = MutableStateFlow(false)
|
private val _showSortMenu = MutableStateFlow(false)
|
||||||
val showSortMenu: StateFlow<Boolean> = _showSortMenu.asStateFlow()
|
val showSortMenu = _showSortMenu.asStateFlow()
|
||||||
|
|
||||||
fun toggleSortMenu(visible: Boolean) {
|
fun toggleSortMenu(visible: Boolean) {
|
||||||
_showSortMenu.value = visible
|
_showSortMenu.value = visible
|
||||||
|
|
@ -84,7 +89,7 @@ class ModelSelectionViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _showFilterMenu = MutableStateFlow(false)
|
private val _showFilterMenu = MutableStateFlow(false)
|
||||||
val showFilterMenu: StateFlow<Boolean> = _showFilterMenu.asStateFlow()
|
val showFilterMenu = _showFilterMenu.asStateFlow()
|
||||||
|
|
||||||
fun toggleFilterMenu(visible: Boolean) {
|
fun toggleFilterMenu(visible: Boolean) {
|
||||||
_showFilterMenu.value = visible
|
_showFilterMenu.value = visible
|
||||||
|
|
@ -92,15 +97,34 @@ class ModelSelectionViewModel @Inject constructor(
|
||||||
|
|
||||||
// Data: filtered & sorted models
|
// Data: filtered & sorted models
|
||||||
private val _filteredModels = MutableStateFlow<List<ModelInfo>>(emptyList())
|
private val _filteredModels = MutableStateFlow<List<ModelInfo>>(emptyList())
|
||||||
val filteredModels: StateFlow<List<ModelInfo>> = _filteredModels.asStateFlow()
|
val filteredModels = _filteredModels.asStateFlow()
|
||||||
|
|
||||||
// Data: queried models
|
// Data: queried models
|
||||||
private val _queryResults = MutableStateFlow<List<ModelInfo>>(emptyList())
|
private val _queryResults = MutableStateFlow<List<ModelInfo>>(emptyList())
|
||||||
val queryResults: StateFlow<List<ModelInfo>> = _queryResults.asStateFlow()
|
val queryResults = _queryResults.asStateFlow()
|
||||||
|
|
||||||
// Data: pre-selected model in expansion mode
|
// Data: pre-selected model in expansion mode
|
||||||
private val _preselectedModel = MutableStateFlow<ModelInfo?>(null)
|
private val _preselectedModel = MutableStateFlow<ModelInfo?>(null)
|
||||||
val preselectedModel: StateFlow<ModelInfo?> = _preselectedModel.asStateFlow()
|
private val _showRamWarning = MutableStateFlow(false)
|
||||||
|
val preselection = combine(
|
||||||
|
_preselectedModel,
|
||||||
|
performanceMonitor.monitorMemoryUsage(),
|
||||||
|
_showRamWarning,
|
||||||
|
) { model, memory, show ->
|
||||||
|
if (model == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
if (memory.availableMem >= model.sizeInBytes + RAM_LOAD_MODEL_BUFFER_BYTES) {
|
||||||
|
Preselection(model, null)
|
||||||
|
} else {
|
||||||
|
Preselection(model, RamWarning(model.sizeInBytes, memory.availableMem, show))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(SUBSCRIPTION_TIMEOUT_MS),
|
||||||
|
initialValue = null
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|
@ -132,24 +156,58 @@ class ModelSelectionViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pre-select a model
|
* Pre-select a model to expand its details and show Run FAB
|
||||||
*/
|
*/
|
||||||
fun preselectModel(modelInfo: ModelInfo, preselected: Boolean) =
|
fun preselectModel(modelInfo: ModelInfo, preselected: Boolean) {
|
||||||
_preselectedModel.update { current ->
|
_preselectedModel.value = if (preselected) modelInfo else null
|
||||||
if (preselected) modelInfo else null
|
_showRamWarning.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Confirm currently selected model
|
* Select the currently pre-selected model
|
||||||
|
*
|
||||||
|
* @return True if RAM enough, otherwise False.
|
||||||
*/
|
*/
|
||||||
fun confirmSelectedModel(modelInfo: ModelInfo) =
|
fun selectModel(preselection: Preselection) =
|
||||||
|
when (preselection.ramWarning?.showing) {
|
||||||
|
null -> {
|
||||||
|
inferenceService.setCurrentModel(preselection.modelInfo)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
false -> {
|
||||||
|
_showRamWarning.value = true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acknowledge RAM warnings and confirm currently pre-selected model
|
||||||
|
*
|
||||||
|
* @return True if confirmed, otherwise False.
|
||||||
|
*/
|
||||||
|
fun confirmSelectedModel(modelInfo: ModelInfo, ramWarning: RamWarning): Boolean =
|
||||||
|
if (ramWarning.showing) {
|
||||||
inferenceService.setCurrentModel(modelInfo)
|
inferenceService.setCurrentModel(modelInfo)
|
||||||
|
_showRamWarning.value = false
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dismiss the RAM warnings
|
||||||
|
*/
|
||||||
|
fun dismissRamWarning() {
|
||||||
|
_showRamWarning.value = false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset selected model to none (before navigating away)
|
* Reset selected model to none (before navigating away)
|
||||||
*/
|
*/
|
||||||
fun resetSelection() {
|
fun resetSelection() {
|
||||||
_preselectedModel.value = null
|
_preselectedModel.value = null
|
||||||
|
_showRamWarning.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -164,6 +222,20 @@ class ModelSelectionViewModel @Inject constructor(
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = ModelSelectionViewModel::class.java.simpleName
|
private val TAG = ModelSelectionViewModel::class.java.simpleName
|
||||||
|
|
||||||
|
private const val SUBSCRIPTION_TIMEOUT_MS = 5000L
|
||||||
private const val QUERY_DEBOUNCE_TIMEOUT_MS = 500L
|
private const val QUERY_DEBOUNCE_TIMEOUT_MS = 500L
|
||||||
|
|
||||||
|
private const val RAM_LOAD_MODEL_BUFFER_BYTES = 300 * 1024
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class Preselection(
|
||||||
|
val modelInfo: ModelInfo,
|
||||||
|
val ramWarning: RamWarning?,
|
||||||
|
) {
|
||||||
|
data class RamWarning(
|
||||||
|
val requiredRam: Long,
|
||||||
|
val availableRam: Long,
|
||||||
|
val showing: Boolean,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue