From 3377af7d8aec24ca81ed72190ab48e768c3aefc1 Mon Sep 17 00:00:00 2001 From: Paul Peavyhouse Date: Tue, 21 Jan 2025 17:05:17 -0800 Subject: [PATCH] Add `model` Preference --- .../swooby/alfredai/PushToTalkPreferences.kt | 80 ++++++++++++++++--- .../swooby/alfredai/PushToTalkViewModel.kt | 37 ++++++++- 2 files changed, 103 insertions(+), 14 deletions(-) diff --git a/mobile/src/main/java/com/swooby/alfredai/PushToTalkPreferences.kt b/mobile/src/main/java/com/swooby/alfredai/PushToTalkPreferences.kt index 576eb4c..f42c1ed 100644 --- a/mobile/src/main/java/com/swooby/alfredai/PushToTalkPreferences.kt +++ b/mobile/src/main/java/com/swooby/alfredai/PushToTalkPreferences.kt @@ -17,10 +17,14 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.Checkbox +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.Slider import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -42,7 +46,6 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.openai.infrastructure.Serializer import com.openai.models.RealtimeSessionCreateRequest import com.openai.models.RealtimeSessionInputAudioTranscription import com.openai.models.RealtimeSessionModel @@ -110,6 +113,10 @@ class PushToTalkPreferences(context: Context) { private val prefs: SharedPreferences = context.getSharedPreferences("pushToTalkPreferences", Context.MODE_PRIVATE) + // + //region generic get/set primitives + // + @Suppress("SameParameterValue") private fun getBoolean(key: String, default: Boolean): Boolean { return prefs.getBoolean(key, default) @@ -161,6 +168,10 @@ class PushToTalkPreferences(context: Context) { } } + // + //endregion + // + var autoConnect: Boolean get() = getBoolean("autoConnect", autoConnectDefault) set(value) = putBoolean("autoConnect", value) @@ -180,6 +191,16 @@ class PushToTalkPreferences(context: Context) { putString("apiKey", apiKeyEncrypted) } + var model: RealtimeSessionModel + get() { + return getString("model", modelDefault.name).let { + RealtimeSessionModel.valueOf(it) + } + } + set(value) { + putString("model", value.name) + } + var instructions: String get() = getString("instructions", instructionsDefault) set(value) = putString("instructions", value) @@ -192,24 +213,27 @@ class PushToTalkPreferences(context: Context) { @OptIn(ExperimentalMaterial3Api::class) @Composable fun PushToTalkPreferenceScreen( - pushToTalkViewModel2: PushToTalkViewModel? = null, + pushToTalkViewModel: PushToTalkViewModel? = null, onSaveSuccess: (() -> Unit)? = null, setSaveButtonCallback: (((() -> Unit)?) -> Unit)? = null, ) { - val _autoConnect by (pushToTalkViewModel2?.autoConnect ?: MutableStateFlow(PushToTalkPreferences.autoConnectDefault).asStateFlow()).collectAsState() - val _apiKey by (pushToTalkViewModel2?.apiKey ?: MutableStateFlow(PushToTalkPreferences.apiKeyDefault).asStateFlow()).collectAsState() - val _instructions by (pushToTalkViewModel2?.instructions ?: MutableStateFlow(PushToTalkPreferences.instructionsDefault).asStateFlow()).collectAsState() - val _temperature by (pushToTalkViewModel2?.temperature ?: MutableStateFlow(PushToTalkPreferences.temperatureDefault).asStateFlow()).collectAsState() - - var editedAutoConnect by remember { mutableStateOf(_autoConnect) } - var editedApiKey by remember { mutableStateOf(_apiKey) } - var editedInstructions by remember { mutableStateOf(_instructions) } - var editedTemperature by remember { mutableStateOf(_temperature) } + val initialAutoConnect by (pushToTalkViewModel?.autoConnect ?: MutableStateFlow(PushToTalkPreferences.autoConnectDefault).asStateFlow()).collectAsState() + val initialApiKey by (pushToTalkViewModel?.apiKey ?: MutableStateFlow(PushToTalkPreferences.apiKeyDefault).asStateFlow()).collectAsState() + val initialModel by (pushToTalkViewModel?.model ?: MutableStateFlow(PushToTalkPreferences.modelDefault).asStateFlow()).collectAsState() + val initialInstructions by (pushToTalkViewModel?.instructions ?: MutableStateFlow(PushToTalkPreferences.instructionsDefault).asStateFlow()).collectAsState() + val initialTemperature by (pushToTalkViewModel?.temperature ?: MutableStateFlow(PushToTalkPreferences.temperatureDefault).asStateFlow()).collectAsState() + + var editedAutoConnect by remember { mutableStateOf(initialAutoConnect) } + var editedApiKey by remember { mutableStateOf(initialApiKey) } + var editedModel by remember { mutableStateOf(initialModel) } + var editedInstructions by remember { mutableStateOf(initialInstructions) } + var editedTemperature by remember { mutableStateOf(initialTemperature) } val saveOperation: () -> Unit = { - pushToTalkViewModel2?.updatePreferences( + pushToTalkViewModel?.updatePreferences( editedAutoConnect, editedApiKey, + editedModel, editedInstructions, editedTemperature, ) @@ -286,6 +310,38 @@ fun PushToTalkPreferenceScreen( modifier = Modifier.fillMaxWidth(), ) } + item { + var modelExpanded by remember { mutableStateOf(false) } + ExposedDropdownMenuBox( + expanded = modelExpanded, + onExpandedChange = { modelExpanded = !modelExpanded } + ) { + TextField( + readOnly = true, + value = editedModel.name, + onValueChange = { /* read-only; ignore */ }, + label = { Text("Model") }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = modelExpanded) }, + modifier = Modifier + .fillMaxWidth() + .menuAnchor(type = MenuAnchorType.PrimaryNotEditable, enabled = true) + ) + ExposedDropdownMenu( + expanded = modelExpanded, + onDismissRequest = { modelExpanded = false } + ) { + RealtimeSessionModel.entries.forEach { model -> + DropdownMenuItem( + text = { Text(model.name) }, + onClick = { + editedModel = model + modelExpanded = false + } + ) + } + } + } + } item { TextField( diff --git a/mobile/src/main/java/com/swooby/alfredai/PushToTalkViewModel.kt b/mobile/src/main/java/com/swooby/alfredai/PushToTalkViewModel.kt index dbf5cc2..f1247d2 100644 --- a/mobile/src/main/java/com/swooby/alfredai/PushToTalkViewModel.kt +++ b/mobile/src/main/java/com/swooby/alfredai/PushToTalkViewModel.kt @@ -31,6 +31,7 @@ import com.openai.models.RealtimeServerEventResponseTextDone import com.openai.models.RealtimeServerEventSessionCreated import com.openai.models.RealtimeServerEventSessionUpdated import com.openai.models.RealtimeSessionCreateRequest +import com.openai.models.RealtimeSessionModel import com.swooby.alfred.common.openai.realtime.RealtimeClient import com.swooby.alfred.common.openai.realtime.RealtimeClient.RealtimeClientListener import kotlinx.coroutines.flow.MutableStateFlow @@ -51,6 +52,9 @@ class PushToTalkViewModel(private val application: Application) private var _apiKey = MutableStateFlow(prefs.apiKey) val apiKey = _apiKey.asStateFlow() + private var _model = MutableStateFlow(prefs.model) + val model = _model.asStateFlow() + private var _instructions = MutableStateFlow(prefs.instructions) val instructions = _instructions.asStateFlow() @@ -61,7 +65,7 @@ class PushToTalkViewModel(private val application: Application) get() { return RealtimeSessionCreateRequest( modalities = null, - model = PushToTalkPreferences.modelDefault, + model = model.value, instructions = instructions.value, voice = PushToTalkPreferences.voiceDefault, inputAudioFormat = null, @@ -78,6 +82,7 @@ class PushToTalkViewModel(private val application: Application) fun updatePreferences( autoConnect: Boolean, apiKey: String, + model: RealtimeSessionModel, instructions: String, temperature: Float, ) { @@ -93,6 +98,12 @@ class PushToTalkViewModel(private val application: Application) _apiKey.value = apiKey } + if (model != prefs.model) { + updateSession = true + prefs.model = model + _model.value = model + } + if (instructions != prefs.instructions) { updateSession = true prefs.instructions = instructions @@ -105,7 +116,20 @@ class PushToTalkViewModel(private val application: Application) _temperature.value = temperature } - tryInitializeRealtimeClient() + if (realtimeClient == null) { + tryInitializeRealtimeClient() + } else { + if (isConnectingOrConnected) { + if (isConnecting || reconnectSession) { + _realtimeClient?.disconnect() + _realtimeClient?.connect() + } else { + if (isConnected && updateSession) { + realtimeClient?.dataSendSessionUpdate(sessionConfig) + } + } + } + } } val isConfigured: Boolean @@ -120,6 +144,15 @@ class PushToTalkViewModel(private val application: Application) return _realtimeClient } + val isConnectingOrConnected: Boolean + get() = realtimeClient?.isConnectingOrConnected ?: false + + val isConnected: Boolean + get() = realtimeClient?.isConnected ?: false + + val isConnecting: Boolean + get() = realtimeClient?.isConnecting ?: false + private fun tryInitializeRealtimeClient(): Boolean { if (!isConfigured) { return false