feat(asr): 添加ASR语音识别的列表场景支持

- 新增extraProvider回调用于提供列表数据给服务端
- 实现list场景下的任务定位功能,支持通过taskId或taskIndex跳转
- 扩展RecognizeData模型支持extra和actions参数传递
- 在TaskListActivity中注册ASR回调处理决策中心跳转
- 更新HTTP请求头配置和用户身份信息
- 添加TaskExtraItem数据类用于列表项信息传递
这个提交包含在:
徐勤民 2026-04-18 12:08:00 +08:00
父节点 96671914ab
当前提交 2b3965da67
共有 7 个文件被更改,包括 69 次插入16 次删除

查看文件

@ -65,6 +65,9 @@ object AsrHelper : OfflineCmdListener {
/** goToTaskCenter 命中时的回调,由各 Activity 在 onResume/onPause 中注册/清空 */ /** goToTaskCenter 命中时的回调,由各 Activity 在 onResume/onPause 中注册/清空 */
var onGoToTaskCenter: ((action: RecognizeAction) -> Unit)? = null var onGoToTaskCenter: ((action: RecognizeAction) -> Unit)? = null
/** list 场景下由 TaskListActivity 注册,返回当前列表数据作为 extra 传给服务端 */
var extraProvider: (() -> List<Any>)? = null
/** scene == "decision" 时直接用 ASR 文本发起对话,由 ChatActivity 注册 */ /** scene == "decision" 时直接用 ASR 文本发起对话,由 ChatActivity 注册 */
var onDirectChat: ((text: String) -> Unit)? = null var onDirectChat: ((text: String) -> Unit)? = null
@ -192,16 +195,22 @@ object AsrHelper : OfflineCmdListener {
if (scene == "decision") { if (scene == "decision") {
onDirectChat?.invoke(text) onDirectChat?.invoke(text)
} else { } else {
val listActions = if (scene == "list")
listOf("goToDecisionCenter")
else
listOf("goToTaskCenter", "goToDecisionCenter")
IntentRecognizeHelper.recognize( IntentRecognizeHelper.recognize(
text = text, text = text,
scence = scene, scence = scene,
extra = extraProvider?.invoke() ?: emptyList(),
actions = listActions,
onSuccess = { action -> onSuccess = { action ->
when (action.name) { when (action.name) {
"goToDecisionCenter" -> onGoToDecisionCenter?.invoke(action) "goToDecisionCenter" -> onGoToDecisionCenter?.invoke(action)
"goToTaskCenter" -> onGoToTaskCenter?.invoke(action) "goToTaskCenter" -> onGoToTaskCenter?.invoke(action)
else -> Log.d(TAG, "unhandled action: $action") else -> Log.d(TAG, "unhandled action: $action")
} }
} },
) )
} }
} }

查看文件

@ -29,19 +29,19 @@ object IntentRecognizeHelper {
private var questionIndex = 0 private var questionIndex = 0
private var disposable: Disposable? = null private var disposable: Disposable? = null
/** /**
* @param context 用于显示 Toast
* @param text 指定问题文本 null 时自动轮换内置问题 * @param text 指定问题文本 null 时自动轮换内置问题
* @param scence 场景标识默认 "home" * @param scence 场景标识默认 "home"
* @param extra 额外上下文数据 list 场景下的任务列表原样传给服务端
* @param actions 允许的 action 列表默认全部
* @param onSuccess 识别成功且 code=="0" 时回调参数为 [RecognizeAction] * @param onSuccess 识别成功且 code=="0" 时回调参数为 [RecognizeAction]
*/
/**
* @param onComplete 无论成功失败都会回调用于调用方重置 loading 状态 * @param onComplete 无论成功失败都会回调用于调用方重置 loading 状态
*/ */
fun recognize( fun recognize(
text: String? = null, text: String? = null,
scence: String = "home", scence: String = "home",
extra: List<Any> = emptyList(),
actions: List<String> = listOf("goToTaskCenter", "goToDecisionCenter"),
onSuccess: (action: RecognizeAction) -> Unit, onSuccess: (action: RecognizeAction) -> Unit,
onComplete: () -> Unit = {} onComplete: () -> Unit = {}
) { ) {
@ -50,7 +50,7 @@ object IntentRecognizeHelper {
disposable = HttpManager.getApi( disposable = HttpManager.getApi(
MyApplication.appComponent1, Service::class.java MyApplication.appComponent1, Service::class.java
) )
.recognize(RecognizeData(text = question, scence = scence)) .recognize(RecognizeData(text = question, scence = scence, extra = extra, actions = actions))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ model -> .subscribe({ model ->
@ -59,7 +59,7 @@ object IntentRecognizeHelper {
} else { } else {
model.message.showMessage() model.message.showMessage()
} }
onComplete() // 无论成功失败都执行 onComplete()
}, { e -> }, { e ->
"请求失败: ${e.message}".showMessage() "请求失败: ${e.message}".showMessage()
onComplete() onComplete()

查看文件

@ -53,4 +53,7 @@ data class RecognizeParams(
val taskType: String? = null, val taskType: String? = null,
val tenantId: Int? = null, val tenantId: Int? = null,
val userId: String? = null, val userId: String? = null,
// list 场景下服务端返回的任务定位字段
val taskId: String? = null,
val taskIndex: Int? = null,
) )

查看文件

@ -3,14 +3,23 @@ package com.nova.brain.glass.model.data
//{ //{
// "text": "查看下C919第45架机的零部件采购情况", // "text": "查看下C919第45架机的零部件采购情况",
// "scence": "home", // "scence": "home",
// "extra": [], // "extra": [
// "actions": [ // { "index": 1, "id": "xxx", "taskType": "审核任务", "processStatus": "进行中", "aiDescription": "..." }
// "goToTaskCenter","goToDecisionCenter" // ],
// ] // "actions": ["goToDecisionCenter"]
//} //}
data class RecognizeData( data class RecognizeData(
val text: String, val text: String,
val scence: String = "home", val scence: String = "home",
val extra: List<String> = emptyList(), val extra: List<Any> = emptyList(),
val actions: List<String> = listOf("goToTaskCenter","goToDecisionCenter") val actions: List<String> = listOf("goToTaskCenter", "goToDecisionCenter")
) )
// list 场景下传给服务端,帮助 NLP 定位用户所说的任务
data class TaskExtraItem(
val index: Int, // 列表中的显示序号1-based
val id: String,
val taskType: String,
val processStatus: String,
val aiDescription: String
)

查看文件

@ -23,11 +23,11 @@ class HeaderInterceptor(val context: Context) : Interceptor {
//请求定制:添加请求头 //请求定制:添加请求头
val requestBuilder = original.newBuilder() val requestBuilder = original.newBuilder()
.header("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJBSTAwMjIiLCJyblN0ciI6Im1OWUxobGZqWDJ2bEJMc21RdnFxSUE4dU9ZN0NOY3AzIiwidXNlckluZm8iOnsic3RhZmZObyI6IkFJMDAyMiJ9fQ.4BHcVpdkznqaQwPsyTn1my3_Zo0AliOILj_PjbCIK3k") .header("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJyb2tpZCIsInJuU3RyIjoiQTU3NlV1VVV3V2s5NEpxSjlQcU9keDE1bE1tdU5JYmMiLCJ1c2VySW5mbyI6eyJzdGFmZk5vIjoicm9raWQifX0.qp1zWjYjqtmFS7Udo7Ga-a_XhVNwRUByXYomu6P6yxg")
.addHeader("Content-Type", "application/json;charset=UTF-8") .addHeader("Content-Type", "application/json;charset=UTF-8")
.addHeader("Cookie", "__itrace_wid=87125211-8742-4f12-b5ca-32b9b6c860e4; locale=zh-Hans; _webtracing_device_id=t_13501877-b9b303fc-d3f52eb530e026b0") .addHeader("Cookie", "__itrace_wid=87125211-8742-4f12-b5ca-32b9b6c860e4; locale=zh-Hans; _webtracing_device_id=t_13501877-b9b303fc-d3f52eb530e026b0")
.addHeader("Environment", "1") .addHeader("Environment", "1")
.addHeader("currentUserId", "AI0022") .addHeader("currentUserId", "rokid1")
.addHeader("terminal", "1") .addHeader("terminal", "1")
.addHeader("modulename", "web") .addHeader("modulename", "web")
.addHeader("currentUserName", "") .addHeader("currentUserName", "")

查看文件

@ -6,9 +6,11 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.nova.brain.glass.R import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivityTaskListBinding import com.nova.brain.glass.databinding.ActivityTaskListBinding
import com.nova.brain.glass.helper.AsrHelper
import com.nova.brain.glass.helper.OfflineCmdListener import com.nova.brain.glass.helper.OfflineCmdListener
import com.nova.brain.glass.helper.OfflineCmdServiceHelper import com.nova.brain.glass.helper.OfflineCmdServiceHelper
import com.nova.brain.glass.model.TaskItem import com.nova.brain.glass.model.TaskItem
import com.nova.brain.glass.model.data.TaskExtraItem
import com.nova.brain.glass.model.data.TaskListData import com.nova.brain.glass.model.data.TaskListData
import com.nova.brain.glass.viewmodel.TaskListVM import com.nova.brain.glass.viewmodel.TaskListVM
import com.xuqm.base.adapter.BasePagedAdapter import com.xuqm.base.adapter.BasePagedAdapter
@ -190,12 +192,38 @@ class TaskListActivity :
OfflineCmdServiceHelper.addListenerList() OfflineCmdServiceHelper.addListenerList()
LogHelper.d(">>>>>>>>>>>>--------------------") LogHelper.d(">>>>>>>>>>>>--------------------")
OfflineCmdServiceHelper.addOnLineListener(offlineCmdListener) OfflineCmdServiceHelper.addOnLineListener(offlineCmdListener)
AsrHelper.scene = "list"
AsrHelper.extraProvider = {
viewModel.currentItems.mapIndexed { i, item ->
TaskExtraItem(
index = i + 1,
id = item.id,
taskType = item.taskType,
processStatus = item.processStatus,
aiDescription = item.aiDescription
)
}
}
AsrHelper.onGoToDecisionCenter = { action ->
val taskId = action.params.taskId
val taskIndex = action.params.taskIndex
when {
taskId != null -> {
val position = viewModel.currentItems.indexOfFirst { it.id == taskId }
if (position >= 0) openTask(position)
}
taskIndex != null -> openVisibleTask(taskIndex - 1)
}
}
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
OfflineCmdServiceHelper.removeListenerList() OfflineCmdServiceHelper.removeListenerList()
OfflineCmdServiceHelper.removeOnLineListener(offlineCmdListener) OfflineCmdServiceHelper.removeOnLineListener(offlineCmdListener)
AsrHelper.scene = "home"
AsrHelper.extraProvider = null
AsrHelper.onGoToDecisionCenter = null
} }
private val adapter = object : CommonPagedAdapter<TaskItem>(R.layout.item_task_list) { private val adapter = object : CommonPagedAdapter<TaskItem>(R.layout.item_task_list) {

查看文件

@ -18,9 +18,12 @@ class TaskListVM : BaseListViewModel<TaskItem>() {
val taskCount = MutableLiveData<Int>() val taskCount = MutableLiveData<Int>()
var currentItems: List<TaskItem> = emptyList()
private set
override fun loadData(page: Int, onResponse: Response<TaskItem>) { override fun loadData(page: Int, onResponse: Response<TaskItem>) {
add( add(
HttpManager.getApi(MyApplication.appComponent1, Service::class.java) HttpManager.getApi(Service::class.java)
.glassesTaskSearch(searchParams) .glassesTaskSearch(searchParams)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -34,6 +37,7 @@ class TaskListVM : BaseListViewModel<TaskItem>() {
aiDescription = it.aiDescription aiDescription = it.aiDescription
) )
} ?: emptyList() } ?: emptyList()
currentItems = items
taskCount.value = response.data?.total ?: items.size taskCount.value = response.data?.total ?: items.size
onResponse.onResponse(ArrayList(items)) onResponse.onResponse(ArrayList(items))
}, { }, {