Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
accompanistPermissions = "0.34.0"
agp = "8.7.3"
constraintlayoutCompose = "1.1.0"
pipecatClient = "0.3.7"
pipecatClient = "1.1.0"
kotlin = "2.0.20"
coreKtx = "1.15.0"
lifecycleRuntimeKtx = "2.8.7"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ android {
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "11"
}

buildFeatures {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
Expand Down Expand Up @@ -123,17 +122,20 @@ class MainActivity : ComponentActivity() {
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ConnectSettings(
voiceClientManager: VoiceClientManager,
) {
val scrollState = rememberScrollState()

val start = {
val backendUrl = Preferences.backendUrl.value
val backendUrl = Preferences.backendUrl.value ?: ""
val apiKey = Preferences.apiKey.value ?: ""

voiceClientManager.start(baseUrl = backendUrl!!)
voiceClientManager.start(
baseUrl = backendUrl,
apiKey = apiKey
)
}

Box(
Expand Down Expand Up @@ -196,6 +198,31 @@ fun ConnectSettings(

Spacer(modifier = Modifier.height(36.dp))

Text(
text = "API key",
fontSize = 16.sp,
fontWeight = FontWeight.W400,
style = TextStyles.base
)

Spacer(modifier = Modifier.height(12.dp))

TextField(
modifier = Modifier
.fillMaxWidth()
.border(1.dp, Colors.textFieldBorder, RoundedCornerShape(12.dp)),
value = Preferences.apiKey.value ?: "",
onValueChange = { Preferences.apiKey.value = it },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Unspecified,
imeAction = ImeAction.Next
),
colors = textFieldColors(),
shape = RoundedCornerShape(12.dp)
)

Spacer(modifier = Modifier.height(36.dp))

Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ private val JSON_INSTANCE = Json { ignoreUnknownKeys = true }
object Preferences {

private const val PREF_BACKEND_URL = "backend_url"
private const val PREF_API_KEY = "api_key"

private lateinit var prefs: SharedPreferences

fun initAppStart(context: Context) {
prefs = context.applicationContext.getSharedPreferences("prefs", Context.MODE_PRIVATE)

listOf(backendUrl).forEach { it.init() }
listOf(backendUrl, apiKey).forEach { it.init() }
}

private fun getString(key: String): String? = prefs.getString(key, null)
Expand Down Expand Up @@ -72,4 +73,5 @@ object Preferences {
}

val backendUrl = StringPref(PREF_BACKEND_URL)
val apiKey = StringPref(PREF_API_KEY)
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package ai.pipecat.small_webrtc_client

import ai.pipecat.client.RTVIClient
import ai.pipecat.client.RTVIClientOptions
import ai.pipecat.client.RTVIClientParams
import ai.pipecat.client.RTVIEventCallbacks
import ai.pipecat.client.PipecatClient
import ai.pipecat.client.PipecatClientOptions
import ai.pipecat.client.PipecatEventCallbacks
import ai.pipecat.client.result.Future
import ai.pipecat.client.result.RTVIError
import ai.pipecat.client.result.Result
import ai.pipecat.client.small_webrtc_transport.PipecatClientSmallWebRTC
import ai.pipecat.client.small_webrtc_transport.SmallWebRTCTransport
import ai.pipecat.client.types.ActionDescription
import ai.pipecat.client.types.APIRequest
import ai.pipecat.client.types.BotOutputData
import ai.pipecat.client.types.BotReadyData
import ai.pipecat.client.types.Participant
import ai.pipecat.client.types.PipecatMetrics
import ai.pipecat.client.types.ServiceConfig
import ai.pipecat.client.types.Tracks
import ai.pipecat.client.types.Transcript
import ai.pipecat.client.types.TransportState
import ai.pipecat.client.types.Value
import ai.pipecat.small_webrtc_client.utils.Timestamp
import android.content.Context
import android.util.Log
Expand All @@ -34,14 +35,12 @@ class VoiceClientManager(private val context: Context) {
private const val TAG = "VoiceClientManager"
}

private val client = mutableStateOf<RTVIClient?>(null)
private val client = mutableStateOf<PipecatClientSmallWebRTC?>(null)

val state = mutableStateOf<TransportState?>(null)

val errors = mutableStateListOf<Error>()

val actionDescriptions =
mutableStateOf<Result<List<ActionDescription>, RTVIError>?>(null)

val expiryTime = mutableStateOf<Timestamp?>(null)

Expand All @@ -60,21 +59,15 @@ class VoiceClientManager(private val context: Context) {
errors.add(Error(it.description))
}

fun start(baseUrl: String) {
fun start(baseUrl: String, apiKey: String) {

if (client.value != null) {
return
}

val options = RTVIClientOptions(
params = RTVIClientParams(baseUrl = null),
enableMic = true,
enableCam = true
)

state.value = TransportState.Disconnected

val callbacks = object : RTVIEventCallbacks() {
val callbacks = object : PipecatEventCallbacks() {
override fun onTransportStateChanged(state: TransportState) {
this@VoiceClientManager.state.value = state
}
Expand All @@ -86,27 +79,21 @@ class VoiceClientManager(private val context: Context) {
}
}

override fun onBotReady(version: String, config: List<ServiceConfig>) {

Log.i(TAG, "Bot ready. Version $version, config: $config")

override fun onBotReady(data: BotReadyData) {
Log.i(TAG, "Bot ready: $data")
botReady.value = true

client.value?.describeActions()?.withCallback {
actionDescriptions.value = it
}
}

override fun onPipecatMetrics(data: PipecatMetrics) {
override fun onMetrics(data: PipecatMetrics) {
Log.i(TAG, "Pipecat metrics: $data")
}

override fun onUserTranscript(data: Transcript) {
Log.i(TAG, "User transcript: $data")
override fun onBotOutput(data: BotOutputData) {
Log.i(TAG, "Bot output: $data")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can't comment on unchanged lines... but i'd remove the onBotTranscript handler.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, will do.

override fun onBotTranscript(text: String) {
Log.i(TAG, "Bot transcript: $text")
override fun onUserTranscript(data: Transcript) {
Log.i(TAG, "User transcript: $data")
}

override fun onBotStartedSpeaking() {
Expand Down Expand Up @@ -138,17 +125,11 @@ class VoiceClientManager(private val context: Context) {
this@VoiceClientManager.mic.value = mic
}

override fun onConnected() {
expiryTime.value = client.value?.expiry?.let(Timestamp::ofEpochSecs)
}

override fun onDisconnected() {
expiryTime.value = null
actionDescriptions.value = null
botIsTalking.value = false
userIsTalking.value = false
state.value = null
actionDescriptions.value = null
botReady.value = false
tracks.value = null

Expand All @@ -165,9 +146,24 @@ class VoiceClientManager(private val context: Context) {
}
}

val client = RTVIClient(SmallWebRTCTransport.Factory(context, baseUrl), callbacks, options)
val options = PipecatClientOptions(
enableMic = true,
enableCam = true,
callbacks = callbacks
)

val client = PipecatClient(
transport = SmallWebRTCTransport(context),
options = options
)

client.connect().displayErrors().withErrorCallback {
client.startBotAndConnect(APIRequest(
endpoint = baseUrl,
requestData = Value.Object(),
headers = listOfNotNull(
apiKey.trim().takeIf { it.isNotEmpty() }?.let {"Authorization" to "Bearer $it"}
).toMap()
)).displayErrors().withErrorCallback {
callbacks.onDisconnected()
}

Expand Down