feat: add setting to toggle model provider visibility in chat composer (#1194)
This commit is contained in:
20 files changed
+204
-32
No files matched your search
@@ -790,6 +790,7 @@ fun ChatScreen(
|
||||
},
|
||||
// Composer toolbar wiring (PR 1)
|
||||
currentSessionModel = state.currentSessionModel,
|
||||
showModelProvider = state.showModelProvider,
|
||||
reasoningLevel = state.reasoningLevel,
|
||||
onModelTap = { viewModel.openModelPicker() },
|
||||
onReasoningTap = { level -> viewModel.setReasoningLevel(level) },
|
||||
|
||||
@@ -118,6 +118,7 @@ data class ChatUiState(
|
||||
val showUserMessageTokens: Boolean = true,
|
||||
val showAssistantMessageTokens: Boolean = true,
|
||||
val showTokensPerSecond: Boolean = true,
|
||||
val showModelProvider: Boolean = false,
|
||||
// Commands catalog
|
||||
val commandCatalog: CommandCatalog = CommandCatalog(),
|
||||
// Per-command usage counts for the slash-autocomplete ranking (issue
|
||||
@@ -2605,6 +2606,7 @@ class ChatViewModel(
|
||||
showUserMessageTokens = AuthManager.isUserMessageTokensEnabled(),
|
||||
showAssistantMessageTokens = AuthManager.isAssistantMessageTokensEnabled(),
|
||||
showTokensPerSecond = AuthManager.isTokensPerSecondEnabled(),
|
||||
showModelProvider = AuthManager.isModelProviderShown(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ fun ChatInputBar(
|
||||
fastSupported: Boolean = false,
|
||||
isFastModeChanging: Boolean = false,
|
||||
onToggleFastMode: () -> Unit = {},
|
||||
showModelProvider: Boolean = false,
|
||||
) {
|
||||
// Allow sending while the agent is mid-turn or awaiting approval: the
|
||||
// gateway's prompt.submit busy-input policy queues it as the next turn
|
||||
@@ -327,6 +328,7 @@ fun ChatInputBar(
|
||||
fastSupported = fastSupported,
|
||||
isFastModeChanging = isFastModeChanging,
|
||||
onToggleFastMode = onToggleFastMode,
|
||||
showModelProvider = showModelProvider,
|
||||
)
|
||||
|
||||
AttachmentTray(
|
||||
|
||||
@@ -103,6 +103,7 @@ fun ComposerToolbar(
|
||||
fastSupported: Boolean = false,
|
||||
isFastModeChanging: Boolean = false,
|
||||
onToggleFastMode: () -> Unit = {},
|
||||
showModelProvider: Boolean = false,
|
||||
) {
|
||||
var showReasoningMenu by remember { mutableStateOf(false) }
|
||||
val palette = composerPalette()
|
||||
@@ -135,7 +136,11 @@ fun ComposerToolbar(
|
||||
// Model + reasoning pill — wraps its content inside the free space,
|
||||
// pushing the mic/action buttons to the far end
|
||||
val modelScrollState = rememberScrollState()
|
||||
LaunchedEffect(currentSessionModel) {
|
||||
val modelLabel =
|
||||
currentSessionModel?.let { model ->
|
||||
composerModelLabel(model, showProvider = showModelProvider)
|
||||
} ?: "Model"
|
||||
LaunchedEffect(modelLabel) {
|
||||
modelScrollState.scrollTo(0)
|
||||
}
|
||||
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterStart) {
|
||||
@@ -159,9 +164,7 @@ fun ComposerToolbar(
|
||||
.testTag("model_chip"),
|
||||
) {
|
||||
Text(
|
||||
// Keeps the provider so same-named models from different providers
|
||||
// stay distinguishable while allowing the full label to be revealed.
|
||||
text = currentSessionModel?.let(::composerModelLabel) ?: "Model",
|
||||
text = modelLabel,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = palette.onControl,
|
||||
maxLines = 1,
|
||||
@@ -454,19 +457,39 @@ private fun listeningIconButtonColors(): IconButtonColors =
|
||||
)
|
||||
|
||||
/**
|
||||
* Model label for the composer pill: the session's "provider/model" id with
|
||||
* Model label for the composer pill:
|
||||
* When [showProvider] is true, shows the session's "provider/model" id with
|
||||
* only the "custom:" marker removed from the provider ("custom:acme/glm-5.3"
|
||||
* → "acme/glm-5.3"). The provider itself is kept so the same model served by
|
||||
* → "acme/glm-5.3"). The provider is kept so the same model served by
|
||||
* different providers never renders identically.
|
||||
* When [showProvider] is false (default), shows only the model name ("openai/gpt-5"
|
||||
* → "gpt-5", "custom:acme/glm-5.3" → "glm-5.3").
|
||||
*/
|
||||
internal fun composerModelLabel(sessionModel: String): String {
|
||||
internal fun composerModelLabel(
|
||||
sessionModel: String,
|
||||
showProvider: Boolean = false,
|
||||
): String {
|
||||
val slash = sessionModel.indexOf('/')
|
||||
if (slash <= 0) return sessionModel
|
||||
val provider = sessionModel.substring(0, slash)
|
||||
if (!provider.startsWith(CUSTOM_PROVIDER_PREFIX) || provider.length == CUSTOM_PROVIDER_PREFIX.length) {
|
||||
return sessionModel
|
||||
|
||||
val rawProvider = sessionModel.substring(0, slash)
|
||||
val model = sessionModel.substring(slash + 1)
|
||||
if (model.isBlank()) return sessionModel
|
||||
|
||||
if (rawProvider == CUSTOM_PROVIDER_PREFIX) return sessionModel
|
||||
|
||||
if (!showProvider) {
|
||||
val leaf = sessionModel.substringAfterLast('/')
|
||||
return if (leaf.isNotBlank()) leaf else sessionModel
|
||||
}
|
||||
return provider.removePrefix(CUSTOM_PROVIDER_PREFIX) + sessionModel.substring(slash)
|
||||
|
||||
val provider =
|
||||
if (rawProvider.startsWith(CUSTOM_PROVIDER_PREFIX) && rawProvider.length > CUSTOM_PROVIDER_PREFIX.length) {
|
||||
rawProvider.removePrefix(CUSTOM_PROVIDER_PREFIX)
|
||||
} else {
|
||||
rawProvider
|
||||
}
|
||||
return "$provider/$model"
|
||||
}
|
||||
|
||||
private const val CUSTOM_PROVIDER_PREFIX = "custom:"
|
||||
|
||||
@@ -207,6 +207,8 @@ internal fun SettingsChatPage(
|
||||
onAssistantMessageTokensChange = viewModel::onAssistantMessageTokensChange,
|
||||
showTokensPerSecond = state.showTokensPerSecond,
|
||||
onTokensPerSecondChange = viewModel::onTokensPerSecondChange,
|
||||
showModelProvider = state.showModelProvider,
|
||||
onShowModelProviderChange = viewModel::onShowModelProviderChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ data class SettingsUiState(
|
||||
val showUserMessageTokens: Boolean = true,
|
||||
val showAssistantMessageTokens: Boolean = true,
|
||||
val showTokensPerSecond: Boolean = true,
|
||||
val showModelProvider: Boolean = false,
|
||||
val profiles: List<ConnectionProfile> = emptyList(),
|
||||
val selectedProfileId: String? = null,
|
||||
val renameProfileName: String = "",
|
||||
@@ -89,6 +90,7 @@ class SettingsViewModel(
|
||||
val showUserMessageTokens = AuthManager.isUserMessageTokensEnabled()
|
||||
val showAssistantMessageTokens = AuthManager.isAssistantMessageTokensEnabled()
|
||||
val showTokensPerSecond = AuthManager.isTokensPerSecondEnabled()
|
||||
val showModelProvider = AuthManager.isModelProviderShown()
|
||||
val profiles = AuthManager.getConnectionProfiles()
|
||||
val appLanguage = AuthManager.getAppLanguage()
|
||||
val renameProfileName =
|
||||
@@ -114,6 +116,7 @@ class SettingsViewModel(
|
||||
showUserMessageTokens = showUserMessageTokens,
|
||||
showAssistantMessageTokens = showAssistantMessageTokens,
|
||||
showTokensPerSecond = showTokensPerSecond,
|
||||
showModelProvider = showModelProvider,
|
||||
profiles = profiles,
|
||||
selectedProfileId = selectedId,
|
||||
renameProfileName = renameProfileName,
|
||||
@@ -360,6 +363,11 @@ class SettingsViewModel(
|
||||
AuthManager.setTokensPerSecondEnabled(enabled)
|
||||
}
|
||||
|
||||
fun onShowModelProviderChange(enabled: Boolean) {
|
||||
_uiState.update { it.copy(showModelProvider = enabled, isSaved = false) }
|
||||
AuthManager.setModelProviderShown(enabled)
|
||||
}
|
||||
|
||||
/** Clear all auth credentials — logs out and returns to landing screen. */
|
||||
fun logout() {
|
||||
AuthManager.setToken(null)
|
||||
@@ -393,6 +401,7 @@ class SettingsViewModel(
|
||||
AuthManager.setUserMessageTokensEnabled(state.showUserMessageTokens)
|
||||
AuthManager.setAssistantMessageTokensEnabled(state.showAssistantMessageTokens)
|
||||
AuthManager.setTokensPerSecondEnabled(state.showTokensPerSecond)
|
||||
AuthManager.setModelProviderShown(state.showModelProvider)
|
||||
ApiClient.rebuild()
|
||||
|
||||
viewModelScope.launch(ioDispatcher) {
|
||||
|
||||
@@ -213,6 +213,8 @@ internal fun ChatSection(
|
||||
onAssistantMessageTokensChange: (Boolean) -> Unit = {},
|
||||
showTokensPerSecond: Boolean = true,
|
||||
onTokensPerSecondChange: (Boolean) -> Unit = {},
|
||||
showModelProvider: Boolean = false,
|
||||
onShowModelProviderChange: (Boolean) -> Unit = {},
|
||||
) {
|
||||
val fontScaleOptions = listOf(0.85f, 1.0f, 1.15f, 1.30f, 1.50f)
|
||||
val currentIndex =
|
||||
@@ -425,6 +427,38 @@ internal fun ChatSection(
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f).padding(end = 16.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_item_show_model_provider),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.settings_desc_show_model_provider),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = showModelProvider,
|
||||
onCheckedChange = onShowModelProviderChange,
|
||||
modifier = Modifier.testTag("settings_show_model_provider"),
|
||||
colors =
|
||||
SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.primary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
|
||||
Reference in new issue
Block a user