Skip to content

Commit 5e7afa9

Browse files
committed
Implemented context-aware LLM API backend
By double clicking, the voice only mode activates and now interfaces with the context-aware LLM. TTS and STT back and forth works. Only a concept of the grocery list is implemented as of now.
1 parent abb1a9f commit 5e7afa9

8 files changed

Lines changed: 142 additions & 15 deletions

File tree

0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
300 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
646 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

app/src/main/java/com/nlinterface/activities/GroceryListActivity.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,21 @@ class GroceryListActivity : AppCompatActivity(), GroceryListCallback {
114114
viewPagerSetUp()
115115
configureTTS()
116116
configureSTT()
117+
118+
// Check if we were launched from an LLM voice command
119+
if (intent.getBooleanExtra("FROM_VOICE_COMMAND", false)) {
120+
// Process items to add
121+
val itemsToAdd = intent.getStringArrayListExtra("ITEMS_TO_ADD")
122+
itemsToAdd?.forEach { itemName ->
123+
addGroceryItem(itemName)
124+
}
125+
126+
// Process items to remove
127+
val itemsToRemove = intent.getStringArrayListExtra("ITEMS_TO_REMOVE")
128+
itemsToRemove?.forEach { itemName ->
129+
deleteGroceryItem(itemName)
130+
}
131+
}
117132
}
118133

119134
/**

app/src/main/java/com/nlinterface/activities/VoiceOnlyActivity.kt

Lines changed: 127 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.nlinterface.activities
22

33
import android.Manifest
4+
import android.content.Context
45
import android.content.Intent
56
import android.content.pm.PackageManager
67
import android.os.Bundle
@@ -184,6 +185,16 @@ class VoiceOnlyActivity: AppCompatActivity() {
184185
}
185186
}
186187

188+
private fun isValidCommand(label: String): Boolean {
189+
val supportedCommands = listOf(
190+
"grocery-list",
191+
"barcode-scanner",
192+
"navigation",
193+
"object-and-hand-recognition"
194+
)
195+
return supportedCommands.contains(label)
196+
}
197+
187198
private fun processVoiceInput(command: String) {
188199
lifecycleScope.launch {
189200
showSpeakingStage()
@@ -194,8 +205,11 @@ class VoiceOnlyActivity: AppCompatActivity() {
194205
// Authenticate with the API
195206
val token = llmConnector.authenticate()
196207

208+
// Add <User> prefix to the command
209+
val prefixedCommand = "<User>$command"
210+
197211
// Send command to the LLM API
198-
val apiResponse = llmConnector.sendCommandToLLM(command, token)
212+
val apiResponse = llmConnector.sendCommandToLLM(prefixedCommand, token)
199213

200214
// Parse the response and get label and additional data requirement
201215
val (label, needsAdditionalData) = llmConnector.parseResponse(apiResponse)
@@ -215,21 +229,40 @@ class VoiceOnlyActivity: AppCompatActivity() {
215229
viewModel.sayAndAwait("Sorry, I encountered an error: ${e.message}")
216230
e.printStackTrace()
217231
}
218-
219-
// Automatically start listening again after processing
220-
//showListeningStage()
221-
//startListening()
222232
}
223233
}
224234

225235
/**
226-
* Checks if the given label is a valid command that the app can handle
236+
* Gets the current grocery list formatted as a string.
237+
* Format: "[quantity]Item; [quantity]Item2; ..."
238+
* If the list is empty, returns "No items on the grocery list"
227239
*/
228-
private fun isValidCommand(label: String): Boolean {
229-
val supportedCommands = listOf(
230-
"grocery-list",
231-
)
232-
return supportedCommands.contains(label)
240+
private fun getCurrentGroceryList(): String {
241+
// Load grocery list from shared preferences
242+
val sharedPreferences = getSharedPreferences("grocery_list", Context.MODE_PRIVATE)
243+
val json = sharedPreferences.getString("grocery_list", null)
244+
245+
if (json.isNullOrEmpty()) {
246+
return "No items on the grocery list"
247+
}
248+
249+
try {
250+
val gson = com.google.gson.Gson()
251+
val type = object : com.google.gson.reflect.TypeToken<ArrayList<com.nlinterface.dataclasses.GroceryItem>>() {}.type
252+
val groceryList: ArrayList<com.nlinterface.dataclasses.GroceryItem> = gson.fromJson(json, type)
253+
254+
if (groceryList.isEmpty()) {
255+
return "No items on the grocery list"
256+
}
257+
258+
// Format as "[1]Item; [1]Item2; ..." (assuming quantity is 1 for all items)
259+
return groceryList.joinToString("; ") { item ->
260+
"[1]${item.itemName}"
261+
}
262+
} catch (e: Exception) {
263+
e.printStackTrace()
264+
return "No items on the grocery list"
265+
}
233266
}
234267

235268
/**
@@ -239,21 +272,100 @@ class VoiceOnlyActivity: AppCompatActivity() {
239272
when (label) {
240273
"grocery-list" -> {
241274
if (needsAdditionalData) {
242-
viewModel.sayAndAwait(getString(R.string.which_item))
243-
// Next voice input will be processed in context of grocery list
275+
// Get the current grocery list
276+
val groceryList = getCurrentGroceryList()
277+
viewModel.say("Checking your grocery list...")
278+
279+
// Send the grocery list to the LLM with <App> prefix
280+
val token = LLMAppConnector.getInstance.authenticate()
281+
val prefixedData = "<App>$groceryList"
282+
val apiResponse = LLMAppConnector.getInstance.sendCommandToLLM(prefixedData, token)
283+
284+
// Parse the response
285+
val (response, _) = LLMAppConnector.getInstance.parseResponse(apiResponse)
286+
287+
if (response != null) {
288+
// Process grocery list updates
289+
processGroceryListUpdates(response)
290+
} else {
291+
viewModel.sayAndAwait("Sorry, I couldn't process your request.")
292+
}
244293
} else {
245294
viewModel.sayAndAwait(getString(R.string.navigate_to_grocery_list))
246295
val intent = Intent(this@VoiceOnlyActivity, GroceryListActivity::class.java)
247296
startActivity(intent)
248297
}
249298
}
250-
// Add more commands as needed based on strings.xml and support of the LLM
299+
"barcode-scanner" -> {
300+
viewModel.sayAndAwait(getString(R.string.barcode_scanner))
301+
val intent = Intent(this@VoiceOnlyActivity, BarcodeSettingsActivity::class.java)
302+
startActivity(intent)
303+
}
304+
"navigation" -> {
305+
viewModel.sayAndAwait(getString(R.string.place_details))
306+
val intent = Intent(this@VoiceOnlyActivity, PlaceDetailsActivity::class.java)
307+
startActivity(intent)
308+
}
309+
"object-and-hand-recognition" -> {
310+
viewModel.sayAndAwait(getString(R.string.classification))
311+
val intent = Intent(this@VoiceOnlyActivity, ClassificationActivity::class.java)
312+
startActivity(intent)
313+
}
251314
else -> {
252-
viewModel.sayAndAwait("I don't know how to handle: $label")
315+
viewModel.sayAndAwait("I don't know how to handle: $label; additional-data-required=$needsAdditionalData")
253316
}
254317
}
255318
}
256319

320+
/**
321+
* Process grocery list updates from LLM response
322+
* Expected format: "[+/-quantity]Item; [quantity]Item2; [-quantity]Item3"
323+
*/
324+
private suspend fun processGroceryListUpdates(response: String) {
325+
viewModel.sayAndAwait("Updating your grocery list...")
326+
327+
val itemsToProcess = response.split(";")
328+
val itemsToAdd = mutableListOf<String>()
329+
val itemsToRemove = mutableListOf<String>()
330+
331+
// Parse the response to identify items to add or remove
332+
for (item in itemsToProcess) {
333+
val trimmedItem = item.trim()
334+
if (trimmedItem.isNotEmpty()) {
335+
// Parse the format [+/-quantity]Item
336+
val regex = """^\[([+\-]?\d+)](.+)$""".toRegex()
337+
val matchResult = regex.find(trimmedItem)
338+
339+
if (matchResult != null) {
340+
val (quantityStr, itemName) = matchResult.destructured
341+
342+
if (quantityStr.startsWith("+") || !quantityStr.startsWith("-")) {
343+
// Add item
344+
itemsToAdd.add(itemName.trim())
345+
} else if (quantityStr.startsWith("-")) {
346+
// Remove item
347+
itemsToRemove.add(itemName.trim())
348+
}
349+
}
350+
}
351+
}
352+
353+
// Launch GroceryListActivity and pass the items to add/remove
354+
val intent = Intent(this, GroceryListActivity::class.java)
355+
intent.putExtra("ITEMS_TO_ADD", ArrayList(itemsToAdd))
356+
intent.putExtra("ITEMS_TO_REMOVE", ArrayList(itemsToRemove))
357+
intent.putExtra("FROM_VOICE_COMMAND", true)
358+
startActivity(intent)
359+
360+
// Give feedback to user
361+
val addMessage = if (itemsToAdd.isNotEmpty())
362+
"Adding ${itemsToAdd.joinToString(", ")} to your grocery list. " else ""
363+
val removeMessage = if (itemsToRemove.isNotEmpty())
364+
"Removing ${itemsToRemove.joinToString(", ")} from your grocery list." else ""
365+
366+
viewModel.sayAndAwait("$addMessage$removeMessage")
367+
}
368+
257369
private fun startListening() {
258370
viewModel.handleSTTSpeechBegin()
259371
}

0 commit comments

Comments
 (0)