feat(asr): 优化语音识别功能并添加话题管理支持

- 添加了自定义语音识别对话框界面,支持黑色背景和绿色圆角边框
- 实现了场景切换功能,decision场景下可直接进行聊天而无需意图识别
- 新增TopicData数据模型用于话题管理
- 集成tbtopic接口实现话题ID获取功能
- 更新ChatActivity在进入时预创建话题
- 修正欢迎界面唤醒词提示文本为"飞宝飞宝"
- 重构ASR回调处理逻辑,添加onDirectChat回调支持
这个提交包含在:
徐勤民 2026-04-17 00:13:48 +08:00
父节点 2757854f53
当前提交 5f44916c90
共有 8 个文件被更改,包括 103 次插入18 次删除

查看文件

@ -1,10 +1,15 @@
package com.nova.brain.glass.helper package com.nova.brain.glass.helper
import android.R.attr.action
import android.app.AlertDialog import android.app.AlertDialog
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import com.nova.brain.glass.BuildConfig import com.nova.brain.glass.BuildConfig
import com.nova.brain.glass.R
import com.nova.brain.glass.model.RecognizeAction import com.nova.brain.glass.model.RecognizeAction
import com.rokid.online.speech.AsrClient import com.rokid.online.speech.AsrClient
import com.rokid.online.speech.OnlineSpeechSdk import com.rokid.online.speech.OnlineSpeechSdk
@ -57,6 +62,9 @@ object AsrHelper : OfflineCmdListener {
/** goToDecisionCenter 命中时的回调,由各 Activity 在 onResume/onPause 中注册/清空 */ /** goToDecisionCenter 命中时的回调,由各 Activity 在 onResume/onPause 中注册/清空 */
var onGoToDecisionCenter: ((action: RecognizeAction) -> Unit)? = null var onGoToDecisionCenter: ((action: RecognizeAction) -> Unit)? = null
/** scene == "decision" 时直接用 ASR 文本发起对话,由 ChatActivity 注册 */
var onDirectChat: ((text: String) -> Unit)? = null
fun init() { fun init() {
val cfg = OnlineSpeechSdkConfig( val cfg = OnlineSpeechSdkConfig(
domain = DOMAIN, domain = DOMAIN,
@ -121,11 +129,28 @@ object AsrHelper : OfflineCmdListener {
val activity = runCatching { AppManager.getInstance().getActivity() }.getOrNull() val activity = runCatching { AppManager.getInstance().getActivity() }.getOrNull()
?: return@post ?: return@post
if (activity.isFinishing || activity.isDestroyed) return@post if (activity.isFinishing || activity.isDestroyed) return@post
val contentView = LayoutInflater.from(activity)
.inflate(R.layout.dialog_listening, null)
// 黑底 + 绿色圆角线框
val density = activity.resources.displayMetrics.density
contentView.background = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 12f * density
setColor(Color.BLACK)
setStroke((2f * density).toInt(), Color.parseColor("#FF40FF5E"))
}
listeningDialog = AlertDialog.Builder(activity) listeningDialog = AlertDialog.Builder(activity)
.setMessage("飞宝在呢,您请说") .setView(contentView)
.setCancelable(false) .setCancelable(false)
.create() .create()
.also { it.show() } .also { dialog ->
dialog.show()
// 让 Dialog 窗口本身透明,只显示自定义 view 的背景
dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
}
} }
} }
@ -162,17 +187,21 @@ object AsrHelper : OfflineCmdListener {
asr?.stopAsrWithMic() asr?.stopAsrWithMic()
Log.d(TAG, "ASR final result: $text") Log.d(TAG, "ASR final result: $text")
dismissListeningDialog() dismissListeningDialog()
IntentRecognizeHelper.recognize( if (scene == "decision") {
text = text, onDirectChat?.invoke(text)
scence = scene, } else {
onSuccess = { action -> IntentRecognizeHelper.recognize(
if (action.name == "goToDecisionCenter") { text = text,
onGoToDecisionCenter?.invoke(action) scence = scene,
} else { onSuccess = { action ->
"需要跳转任务列表".showMessage() if (action.name == "goToDecisionCenter") {
onGoToDecisionCenter?.invoke(action)
} else {
"需要跳转任务列表".showMessage()
}
} }
} )
) }
} }
override fun onFinished(taskId: String) { override fun onFinished(taskId: String) {

查看文件

@ -0,0 +1,16 @@
package com.nova.brain.glass.model.data
//{"question":"我的任务哪个更紧急","topicId":14478,"knowledgeBaseId":"AI_ASSISTANT_MODE_FOR_GLASSES","model":"AI助手模式"}
data class TopicData(
val topicName: String,
val knowledgeBaseId: String = "AI_ASSISTANT_MODE_FOR_GLASSES",
)
// "date":"2026-04-16T15:48:13.550Z",
//"msg":"success",
//"code":0,
//"data": 31013,
//"status":200
data class TopicModel(
val code: Int,
val data: Int
)

查看文件

@ -3,6 +3,8 @@ package com.nova.brain.glass.repository
import com.nova.brain.glass.model.RecognizeModel import com.nova.brain.glass.model.RecognizeModel
import com.nova.brain.glass.model.data.ChatData import com.nova.brain.glass.model.data.ChatData
import com.nova.brain.glass.model.data.RecognizeData import com.nova.brain.glass.model.data.RecognizeData
import com.nova.brain.glass.model.data.TopicData
import com.nova.brain.glass.model.data.TopicModel
import io.reactivex.Observable import io.reactivex.Observable
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.ResponseBody import okhttp3.ResponseBody
@ -26,4 +28,7 @@ interface Service {
@POST("/cbrain-gateway/cbraindep/docqa/chat/qa03") @POST("/cbrain-gateway/cbraindep/docqa/chat/qa03")
fun chat(@Body body: ChatData): Observable<ResponseBody> fun chat(@Body body: ChatData): Observable<ResponseBody>
@POST("/cbrain-gateway/cbraindep/docqa/tbtopic/save")
fun tbtopic(@Body body: TopicData): Observable<TopicModel>
} }

查看文件

@ -51,7 +51,7 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
val question = intent.getStringExtra("question") ?: "" val question = intent.getStringExtra("question") ?: ""
if (question.isNotEmpty()) { if (question.isNotEmpty()) {
viewModel.demoPostSse(question) viewModel.prepareTopic(question)
} }
} }
@ -106,7 +106,10 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
OfflineCmdServiceHelper.addOnLineListener(listener) OfflineCmdServiceHelper.addOnLineListener(listener)
AsrHelper.scene = "decision" AsrHelper.scene = "decision"
AsrHelper.onGoToDecisionCenter = { action -> AsrHelper.onGoToDecisionCenter = { action ->
viewModel.demoPostSse(action.params.question) viewModel.prepareTopic(action.params.question)
}
AsrHelper.onDirectChat = { text ->
viewModel.prepareTopic(text)
} }
} }
@ -114,6 +117,7 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
super.onPause() super.onPause()
OfflineCmdServiceHelper.removeOnLineListener(listener) OfflineCmdServiceHelper.removeOnLineListener(listener)
AsrHelper.onGoToDecisionCenter = null AsrHelper.onGoToDecisionCenter = null
AsrHelper.onDirectChat = null
} }
override fun onDestroy() { override fun onDestroy() {

查看文件

@ -22,7 +22,7 @@ class WelcomeActivity : BaseActivity<ActivityWelcomeBinding>() {
private lateinit var vm: WelcomeVM private lateinit var vm: WelcomeVM
// 点动画 // 点动画
private val tvOriginalText = "您可以说:Nova,我的任务有哪些?" private val tvOriginalText = "您可以说:飞宝飞宝,我的任务有哪些?"
private val dotsHandler = Handler(Looper.getMainLooper()) private val dotsHandler = Handler(Looper.getMainLooper())
private var dotCount = 0 private var dotCount = 0
private val dotsRunnable = object : Runnable { private val dotsRunnable = object : Runnable {

查看文件

@ -8,12 +8,14 @@ import com.nova.brain.glass.model.ChatModel
import com.nova.brain.glass.model.ChatModel1 import com.nova.brain.glass.model.ChatModel1
import com.nova.brain.glass.model.ChatModel2 import com.nova.brain.glass.model.ChatModel2
import com.nova.brain.glass.model.data.ChatData import com.nova.brain.glass.model.data.ChatData
import com.nova.brain.glass.model.data.TopicData
import com.nova.brain.glass.repository.Service import com.nova.brain.glass.repository.Service
import com.xuqm.base.common.GsonImplHelp import com.xuqm.base.common.GsonImplHelp
import com.xuqm.base.common.LogHelper import com.xuqm.base.common.LogHelper
import com.xuqm.base.di.manager.HttpManager import com.xuqm.base.di.manager.HttpManager
import com.xuqm.base.viewmodel.BaseListViewModel import com.xuqm.base.viewmodel.BaseListViewModel
import com.xuqm.base.viewmodel.callback.Response import com.xuqm.base.viewmodel.callback.Response
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import org.json.JSONObject import org.json.JSONObject
@ -24,6 +26,7 @@ class ChatVM : BaseListViewModel<ChatItem>() {
companion object { companion object {
const val SPACER_ID = -1 const val SPACER_ID = -1
private const val DEFAULT_TOPIC_ID = 14478
} }
val result = MutableLiveData<String>() val result = MutableLiveData<String>()
@ -74,7 +77,24 @@ class ChatVM : BaseListViewModel<ChatItem>() {
} }
} }
fun demoPostSse(question: String) { /**
* 先请求 tbtopic 接口拿 topicId成功则使用返回值失败/code0 则用默认值再发起 SSE
* 进入 Chat 界面时用此方法替代直接调 demoPostSse
*/
fun prepareTopic(question: String) {
HttpManager.getApi(Service::class.java)
.tbtopic(TopicData(topicName = question))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ model ->
val topicId = if (model.code == 0) model.data else DEFAULT_TOPIC_ID
demoPostSse(question, topicId)
}, { _ ->
demoPostSse(question, DEFAULT_TOPIC_ID)
}).also { add(it) }
}
fun demoPostSse(question: String, topicId: Int = DEFAULT_TOPIC_ID) {
currentTask?.dispose() currentTask?.dispose()
currentTask = null currentTask = null
stopThinkingAnimation() stopThinkingAnimation()
@ -86,7 +106,7 @@ class ChatVM : BaseListViewModel<ChatItem>() {
result.postValue(UUID.randomUUID().toString()) result.postValue(UUID.randomUUID().toString())
loading.postValue(true) loading.postValue(true)
currentTask = HttpManager.getApi(Service::class.java).chat(ChatData(question)) currentTask = HttpManager.getApi(Service::class.java).chat(ChatData(question, topicId))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe({ body -> .subscribe({ body ->
var content = "" var content = ""

查看文件

@ -11,7 +11,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="#ff3FFF5F" android:textColor="#ff3FFF5F"
android:textSize="19sp" android:textSize="19sp"
android:text="您可以说:Nova,我的任务有哪些?" android:text="您可以说:飞宝飞宝,我的任务有哪些?"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginBottom="100dp" android:layout_marginBottom="100dp"

查看文件

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="32dp"
android:background="@color/app_color_black"
android:paddingVertical="20dp"
android:text="飞宝在呢,您请说"
android:textColor="#FF40FF5E"
android:textSize="16sp" />