Skip to content

Commit 30ed29c

Browse files
Merge pull request #205 from Ayush0Chaudhary/intents
Intents
2 parents b6a7761 + 051fc49 commit 30ed29c

File tree

9 files changed

+375
-29
lines changed

9 files changed

+375
-29
lines changed

.idea/deploymentTargetSelector.xml

Lines changed: 0 additions & 18 deletions
This file was deleted.

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ android {
2626
applicationId = "com.blurr.voice"
2727
minSdk = 24
2828
targetSdk = 35
29-
versionCode = 12
30-
versionName = "1.0.12" +
29+
versionCode = 13
30+
versionName = "1.0.13" +
3131
""
3232

3333
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

app/src/main/java/com/blurr/voice/ConversationalAgentService.kt

Lines changed: 159 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,19 @@ import com.blurr.voice.v2.AgentService
4343
import com.blurr.voice.v2.llm.GeminiApi
4444
import com.google.ai.client.generativeai.type.TextPart
4545
import com.google.firebase.Firebase
46+
import com.google.firebase.Timestamp
4647
import com.google.firebase.analytics.FirebaseAnalytics
4748
import com.google.firebase.analytics.analytics
49+
import com.google.firebase.auth.auth
50+
import com.google.firebase.firestore.FieldValue
51+
import com.google.firebase.firestore.firestore
4852
import kotlinx.coroutines.CoroutineScope
4953
import kotlinx.coroutines.Dispatchers
5054
import kotlinx.coroutines.SupervisorJob
5155
import kotlinx.coroutines.cancel
5256
import kotlinx.coroutines.delay
5357
import kotlinx.coroutines.launch
58+
import kotlinx.coroutines.tasks.await
5459
import kotlinx.coroutines.withContext
5560

5661
data class ModelDecision(
@@ -85,6 +90,11 @@ class ConversationalAgentService : Service() {
8590
private val usedMemories = mutableSetOf<String>() // Track memories already used in this conversation
8691
private lateinit var firebaseAnalytics: FirebaseAnalytics
8792
private val eyes by lazy { Eyes(this) }
93+
94+
// Firebase instances for conversation tracking
95+
private val db = Firebase.firestore
96+
private val auth = Firebase.auth
97+
private var conversationId: String? = null // Track current conversation session
8898

8999

90100
companion object {
@@ -165,11 +175,13 @@ class ConversationalAgentService : Service() {
165175

166176
// Track conversation initiation
167177
firebaseAnalytics.logEvent("conversation_initiated", null)
178+
trackConversationStart()
168179

169180
serviceScope.launch {
170181
if (conversationHistory.size == 1) {
171182
val greeting = getPersonalizedGreeting()
172183
conversationHistory = addResponse("model", greeting, conversationHistory)
184+
trackMessage("model", greeting, "greeting")
173185
speakAndThenListen(greeting)
174186
}
175187
}
@@ -236,7 +248,8 @@ class ConversationalAgentService : Service() {
236248
if (sttErrorAttempts >= maxSttErrorAttempts) {
237249
firebaseAnalytics.logEvent("conversation_ended_stt_errors", null)
238250
val exitMessage = "I'm having trouble understanding you clearly. Please try calling later!"
239-
gracefulShutdown(exitMessage)
251+
trackMessage("model", exitMessage, "error_message")
252+
gracefulShutdown(exitMessage, "stt_errors")
240253
} else {
241254
speakAndThenListen("I'm sorry, I didn't catch that. Could you please repeat?")
242255
}
@@ -326,6 +339,9 @@ class ConversationalAgentService : Service() {
326339
updateSystemPromptWithAgentStatus()
327340

328341
conversationHistory = addResponse("user", userInput, conversationHistory)
342+
343+
// Track user message in Firebase
344+
trackMessage("user", userInput, "input")
329345

330346
// Track user input
331347
val inputBundle = android.os.Bundle().apply {
@@ -338,7 +354,8 @@ class ConversationalAgentService : Service() {
338354
try {
339355
if (userInput.equals("stop", ignoreCase = true) || userInput.equals("exit", ignoreCase = true)) {
340356
firebaseAnalytics.logEvent("conversation_ended_by_command", null)
341-
gracefulShutdown("Goodbye!")
357+
trackMessage("model", "Goodbye!", "farewell")
358+
gracefulShutdown("Goodbye!", "command")
342359
return@launch
343360
}
344361

@@ -395,6 +412,7 @@ class ConversationalAgentService : Service() {
395412
"Clarification needed for task: ${decision.instruction}",
396413
conversationHistory
397414
)
415+
trackMessage("model", questionToAsk, "clarification")
398416
speakAndThenListen(questionToAsk, false)
399417
} else {
400418
Log.d(
@@ -407,7 +425,8 @@ class ConversationalAgentService : Service() {
407425

408426
val originalInstruction = decision.instruction
409427
AgentService.start(applicationContext, originalInstruction)
410-
gracefulShutdown(decision.reply)
428+
trackMessage("model", decision.reply, "task_confirmation")
429+
gracefulShutdown(decision.reply, "task_executed")
411430
}
412431
} else {
413432
Log.d(
@@ -419,7 +438,8 @@ class ConversationalAgentService : Service() {
419438
firebaseAnalytics.logEvent("task_executed_max_clarification", taskBundle)
420439

421440
AgentService.start(applicationContext, decision.instruction)
422-
gracefulShutdown(decision.reply)
441+
trackMessage("model", decision.reply, "task_confirmation")
442+
gracefulShutdown(decision.reply, "task_executed")
423443
}
424444
}else{
425445
Log.w("ConvAgent", "User has no tasks remaining. Denying request.")
@@ -429,6 +449,7 @@ class ConversationalAgentService : Service() {
429449

430450
val upgradeMessage = "${getPersonalizedGreeting()} You've used all your free tasks for the month. Please upgrade in the app to unlock more. We can still talk in voice mode."
431451
conversationHistory = addResponse("model", upgradeMessage, conversationHistory)
452+
trackMessage("model", upgradeMessage, "freemium_limit")
432453
speakAndThenListen(upgradeMessage)
433454
}
434455
}
@@ -443,9 +464,12 @@ class ConversationalAgentService : Service() {
443464

444465
if (AgentService.isRunning) {
445466
AgentService.stop(applicationContext)
446-
gracefulShutdown(decision.reply)
467+
trackMessage("model", decision.reply, "kill_task_response")
468+
gracefulShutdown(decision.reply, "task_killed")
447469
} else {
448-
speakAndThenListen("There was no automation running, but I can help with something else.")
470+
val noTaskMessage = "There was no automation running, but I can help with something else."
471+
trackMessage("model", noTaskMessage, "kill_task_response")
472+
speakAndThenListen(noTaskMessage)
449473
}
450474
}
451475
else -> { // Default to "Reply"
@@ -459,9 +483,11 @@ class ConversationalAgentService : Service() {
459483
if (decision.shouldEnd) {
460484
Log.d("ConvAgent", "Model decided to end the conversation.")
461485
firebaseAnalytics.logEvent("conversation_ended_by_model", null)
462-
gracefulShutdown(decision.reply)
486+
trackMessage("model", decision.reply, "farewell")
487+
gracefulShutdown(decision.reply, "model_ended")
463488
} else {
464489
conversationHistory = addResponse("model", rawModelResponse, conversationHistory)
490+
trackMessage("model", decision.reply, "reply")
465491
speakAndThenListen(decision.reply)
466492
}
467493
}
@@ -909,7 +935,7 @@ class ConversationalAgentService : Service() {
909935
}
910936
}
911937

912-
private suspend fun gracefulShutdown(exitMessage: String? = null) {
938+
private suspend fun gracefulShutdown(exitMessage: String? = null, endReason: String = "graceful") {
913939
// Track graceful shutdown
914940
val shutdownBundle = android.os.Bundle().apply {
915941
putBoolean("had_exit_message", exitMessage != null)
@@ -920,6 +946,10 @@ class ConversationalAgentService : Service() {
920946
}
921947
firebaseAnalytics.logEvent("conversation_ended_gracefully", shutdownBundle)
922948

949+
// Track conversation end in Firebase
950+
951+
trackConversationEnd(endReason)
952+
923953
visualFeedbackManager.hideTtsWave()
924954
visualFeedbackManager.hideTranscription()
925955
visualFeedbackManager.hideSpeakingOverlay()
@@ -953,6 +983,9 @@ class ConversationalAgentService : Service() {
953983
}
954984
firebaseAnalytics.logEvent("conversation_ended_instantly", instantShutdownBundle)
955985

986+
// Track conversation end in Firebase
987+
trackConversationEnd("instant")
988+
956989
Log.d("ConvAgent", "Instant shutdown triggered by user.")
957990
speechCoordinator.stopSpeaking()
958991
speechCoordinator.stopListening()
@@ -971,13 +1004,131 @@ class ConversationalAgentService : Service() {
9711004

9721005
stopSelf()
9731006
}
1007+
1008+
/**
1009+
* Tracks the conversation start in Firebase by creating a new conversation entry.
1010+
* This method is inspired by AgentService's Firebase operations.
1011+
*/
1012+
private fun trackConversationStart() {
1013+
val currentUser = auth.currentUser
1014+
if (currentUser == null) {
1015+
Log.w("ConvAgent", "Cannot track conversation, user is not logged in.")
1016+
return
1017+
}
1018+
1019+
// Generate a unique conversation ID
1020+
conversationId = "${System.currentTimeMillis()}_${currentUser.uid.take(8)}"
1021+
1022+
serviceScope.launch {
1023+
try {
1024+
val conversationEntry = hashMapOf(
1025+
"conversationId" to conversationId,
1026+
"startedAt" to Timestamp.now(),
1027+
"endedAt" to null,
1028+
"messageCount" to 0,
1029+
"textModeUsed" to false,
1030+
"clarificationAttempts" to 0,
1031+
"sttErrorAttempts" to 0,
1032+
"endReason" to null, // "graceful", "instant", "command", "model", "stt_errors"
1033+
"tasksRequested" to 0,
1034+
"tasksExecuted" to 0
1035+
)
1036+
1037+
// Append the conversation to the user's conversationHistory array
1038+
db.collection("users").document(currentUser.uid)
1039+
.update("conversationHistory", FieldValue.arrayUnion(conversationEntry))
1040+
.await()
1041+
1042+
Log.d("ConvAgent", "Successfully tracked conversation start in Firebase for user ${currentUser.uid}: $conversationId")
1043+
} catch (e: Exception) {
1044+
Log.e("ConvAgent", "Failed to track conversation start in Firebase", e)
1045+
// Don't fail the conversation if Firebase tracking fails
1046+
}
1047+
}
1048+
}
1049+
1050+
/**
1051+
* Tracks individual messages in the conversation.
1052+
* Fire and forget operation.
1053+
*/
1054+
private fun trackMessage(role: String, message: String, messageType: String = "text") {
1055+
val currentUser = auth.currentUser
1056+
if (currentUser == null || conversationId == null) {
1057+
return
1058+
}
1059+
1060+
serviceScope.launch {
1061+
try {
1062+
val messageEntry = hashMapOf(
1063+
"conversationId" to conversationId,
1064+
"role" to role, // "user" or "model"
1065+
"message" to message.take(500), // Limit message length for storage
1066+
"messageType" to messageType, // "text", "task", "clarification"
1067+
"timestamp" to Timestamp.now(),
1068+
"inputMode" to if (isTextModeActive) "text" else "voice"
1069+
)
1070+
1071+
// Append the message to the user's messageHistory array
1072+
db.collection("users").document(currentUser.uid)
1073+
.update("messageHistory", FieldValue.arrayUnion(messageEntry))
1074+
.await()
1075+
1076+
Log.d("ConvAgent", "Successfully tracked message in Firebase: $role - ${message.take(50)}...")
1077+
} catch (e: Exception) {
1078+
Log.e("ConvAgent", "Failed to track message in Firebase", e)
1079+
}
1080+
}
1081+
}
1082+
1083+
/**
1084+
* Updates the conversation completion status in Firebase.
1085+
* Fire and forget operation.
1086+
*/
1087+
private fun trackConversationEnd(endReason: String, tasksRequested: Int = 0, tasksExecuted: Int = 0) {
1088+
val currentUser = auth.currentUser
1089+
if (currentUser == null || conversationId == null) {
1090+
return
1091+
}
1092+
1093+
serviceScope.launch {
1094+
try {
1095+
val completionEntry = hashMapOf(
1096+
"conversationId" to conversationId,
1097+
"endedAt" to Timestamp.now(),
1098+
"messageCount" to conversationHistory.size,
1099+
"textModeUsed" to isTextModeActive,
1100+
"clarificationAttempts" to clarificationAttempts,
1101+
"sttErrorAttempts" to sttErrorAttempts,
1102+
"endReason" to endReason,
1103+
"tasksRequested" to tasksRequested,
1104+
"tasksExecuted" to tasksExecuted,
1105+
"status" to "completed"
1106+
)
1107+
1108+
// Append the completion status to the user's conversationHistory array
1109+
db.collection("users").document(currentUser.uid)
1110+
.update("conversationHistory", FieldValue.arrayUnion(completionEntry))
1111+
.await()
1112+
1113+
Log.d("ConvAgent", "Successfully tracked conversation end in Firebase: $conversationId ($endReason)")
1114+
} catch (e: Exception) {
1115+
Log.e("ConvAgent", "Failed to track conversation end in Firebase", e)
1116+
}
1117+
}
1118+
}
1119+
9741120
override fun onDestroy() {
9751121
super.onDestroy()
9761122
Log.d("ConvAgent", "Service onDestroy")
9771123

9781124
// Track service destruction
9791125
firebaseAnalytics.logEvent("conversational_agent_destroyed", null)
9801126

1127+
// Track conversation end if not already tracked
1128+
if (conversationId != null) {
1129+
trackConversationEnd("service_destroyed")
1130+
}
1131+
9811132
removeClarificationQuestions()
9821133
serviceScope.cancel()
9831134
ttsManager.setCaptionsEnabled(false)

app/src/main/java/com/blurr/voice/MyApplication.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import android.app.Application
44
import android.content.Context
55
import com.blurr.voice.intents.IntentRegistry
66
import com.blurr.voice.intents.impl.DialIntent
7+
import com.blurr.voice.intents.impl.EmailComposeIntent
8+
import com.blurr.voice.intents.impl.ShareTextIntent
9+
import com.blurr.voice.intents.impl.ViewUrlIntent
710

811
class MyApplication : Application() {
912

@@ -18,6 +21,9 @@ class MyApplication : Application() {
1821

1922
// Register built-in app intents (plug-and-play extensions can add their own here)
2023
IntentRegistry.register(DialIntent())
24+
IntentRegistry.register(ViewUrlIntent())
25+
IntentRegistry.register(ShareTextIntent())
26+
IntentRegistry.register(EmailComposeIntent())
2127
// Optional: initialize registry scanning for additional implementations
2228
IntentRegistry.init(this)
2329
}

app/src/main/java/com/blurr/voice/intents/IntentRegistry.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package com.blurr.voice.intents
22

33
import android.content.Context
44
import android.util.Log
5+
import com.blurr.voice.intents.impl.DialIntent
6+
import com.blurr.voice.intents.impl.EmailComposeIntent
7+
import com.blurr.voice.intents.impl.ShareTextIntent
8+
import com.blurr.voice.intents.impl.ViewUrlIntent
59

610
/**
711
* Discovers and manages AppIntent implementations.
@@ -16,7 +20,10 @@ object IntentRegistry {
1620
@Synchronized
1721
@Suppress("UNUSED_PARAMETER")
1822
fun init(context: Context) {
19-
register(com.blurr.voice.intents.impl.DialIntent())
23+
register(DialIntent())
24+
register(ViewUrlIntent())
25+
register(ShareTextIntent())
26+
register(EmailComposeIntent())
2027
initialized = true
2128
}
2229
fun register(intent: AppIntent) {

0 commit comments

Comments
 (0)