fix(chat): support confirmation dialog when switching to expensive or data-policy models (#1050)
This commit is contained in:
7 files changed
+201
-4
No files matched your search
@@ -38,6 +38,7 @@ import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@@ -911,6 +912,25 @@ fun ChatScreen(
|
||||
)
|
||||
}
|
||||
|
||||
// Expensive / Data-policy model confirmation dialog (issue #589 follow-up)
|
||||
if (state.modelSwitchConfirmMessage != null) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { viewModel.dismissModelSwitchConfirm() },
|
||||
title = { Text(stringResource(R.string.model_expensive_title)) },
|
||||
text = { Text(state.modelSwitchConfirmMessage!!) },
|
||||
confirmButton = {
|
||||
Button(onClick = { viewModel.confirmModelSwitchExpensive() }) {
|
||||
Text(stringResource(R.string.model_confirm_continue))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { viewModel.dismissModelSwitchConfirm() }) {
|
||||
Text(stringResource(R.string.common_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showContextSheet && state.contextBreakdown != null) {
|
||||
ContextDetailSheet(
|
||||
breakdown = state.contextBreakdown!!,
|
||||
|
||||
@@ -327,6 +327,7 @@ data class ChatUiState(
|
||||
val modelPickerProviders: List<ModelProvider> = emptyList(),
|
||||
val modelPickerPinned: List<PinnedModel> = emptyList(),
|
||||
val modelPickerLoading: Boolean = false,
|
||||
val modelSwitchConfirmMessage: String? = null,
|
||||
// Current session's active model label (provider/model), shown in the chip
|
||||
val currentSessionModel: String? = null,
|
||||
// Per-model reasoning capabilities for the current session's model
|
||||
@@ -463,6 +464,15 @@ class ChatViewModel(
|
||||
|
||||
private val sessionRequestById = ConcurrentHashMap<String, SessionRequest>()
|
||||
private var sessionGeneration = 0L
|
||||
|
||||
private data class ActiveModelSwitch(
|
||||
val spec: String,
|
||||
val previousModel: String?,
|
||||
)
|
||||
|
||||
private val pendingModelSwitchRequests = ConcurrentHashMap<String, ActiveModelSwitch>()
|
||||
private var activeModelSwitchConfirmation: ActiveModelSwitch? = null
|
||||
private var optimisticPreviousModel: String? = null
|
||||
private var resumeRequestSequence = 0L
|
||||
private var activeResumeRequestSequence = 0L
|
||||
private var hydrationRequestSequence = 0L
|
||||
@@ -1208,6 +1218,25 @@ class ChatViewModel(
|
||||
addSystemMessage("Approval submitted")
|
||||
}
|
||||
}
|
||||
|
||||
WsMethods.CONFIG_SET -> {
|
||||
val pending = pendingModelSwitchRequests.remove(id)
|
||||
val map = result as? Map<*, *> ?: return
|
||||
val key = map["key"] as? String
|
||||
if (key == "model") {
|
||||
val confirmRequired = map["confirm_required"] as? Boolean ?: false
|
||||
if (confirmRequired) {
|
||||
val confirmMessage =
|
||||
(map["confirm_message"] as? String)
|
||||
?: (map["warning"] as? String)
|
||||
?: "This model requires confirmation to switch. Continue?"
|
||||
activeModelSwitchConfirmation = pending
|
||||
_uiState.update {
|
||||
it.copy(modelSwitchConfirmMessage = confirmMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1889,7 +1918,10 @@ class ChatViewModel(
|
||||
* `<model> --provider <slug> --session`
|
||||
* (matching the TUI client's `modelValueForConfigSet`).
|
||||
*/
|
||||
private fun handleModelSwitch(command: String) {
|
||||
private fun handleModelSwitch(
|
||||
command: String,
|
||||
confirmExpensive: Boolean = false,
|
||||
) {
|
||||
val sessionId = runtimeSessionId
|
||||
if (sessionId == null) {
|
||||
addAssistantMessage("No active session. Use `/new` to create one.")
|
||||
@@ -1906,11 +1938,25 @@ class ChatViewModel(
|
||||
} else {
|
||||
command.trim()
|
||||
}
|
||||
val previousModel = optimisticPreviousModel ?: _uiState.value.currentSessionModel
|
||||
optimisticPreviousModel = null
|
||||
val params =
|
||||
mutableMapOf<String, Any>(
|
||||
"key" to "model",
|
||||
"value" to spec,
|
||||
"session_id" to sessionId,
|
||||
)
|
||||
if (confirmExpensive) {
|
||||
params["confirm_expensive_model"] = true
|
||||
}
|
||||
viewModelScope.launch(ioDispatcher) {
|
||||
wsClient.send(
|
||||
WsMethods.CONFIG_SET,
|
||||
mapOf("key" to "model", "value" to spec, "session_id" to sessionId),
|
||||
onSent = { id -> trackRequest(id, WsMethods.CONFIG_SET) },
|
||||
params,
|
||||
onSent = { id ->
|
||||
trackRequest(id, WsMethods.CONFIG_SET)
|
||||
pendingModelSwitchRequests[id] = ActiveModelSwitch(spec, previousModel)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2265,6 +2311,7 @@ class ChatViewModel(
|
||||
provider: String,
|
||||
model: String,
|
||||
) {
|
||||
optimisticPreviousModel = _uiState.value.currentSessionModel
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
showModelPicker = false,
|
||||
@@ -2280,6 +2327,28 @@ class ChatViewModel(
|
||||
handleSlashCommand("/model $model --provider $provider --session")
|
||||
}
|
||||
|
||||
fun dismissModelSwitchConfirm() {
|
||||
val pending = activeModelSwitchConfirmation
|
||||
activeModelSwitchConfirmation = null
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
modelSwitchConfirmMessage = null,
|
||||
currentSessionModel = pending?.previousModel ?: it.currentSessionModel,
|
||||
)
|
||||
}
|
||||
syncCurrentModelCapabilities()
|
||||
fetchContextUsage()
|
||||
}
|
||||
|
||||
fun confirmModelSwitchExpensive() {
|
||||
val pending = activeModelSwitchConfirmation
|
||||
activeModelSwitchConfirmation = null
|
||||
_uiState.update { it.copy(modelSwitchConfirmMessage = null) }
|
||||
if (pending != null) {
|
||||
handleModelSwitch(pending.spec, confirmExpensive = true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reasoning effort level for the current session.
|
||||
*
|
||||
@@ -2575,6 +2644,7 @@ class ChatViewModel(
|
||||
showSessionPicker = false,
|
||||
showModelPicker = false,
|
||||
modelPickerLoading = false,
|
||||
modelSwitchConfirmMessage = null,
|
||||
currentSessionModel = null,
|
||||
currentModelCapabilities = null,
|
||||
reasoningLevel = null,
|
||||
|
||||
Reference in new issue
Block a user