feat(asr): 优化语音识别功能并添加话题管理支持
- 添加了自定义语音识别对话框界面,支持黑色背景和绿色圆角边框 - 实现了场景切换功能,decision场景下可直接进行聊天而无需意图识别 - 新增TopicData数据模型用于话题管理 - 集成tbtopic接口实现话题ID获取功能 - 更新ChatActivity在进入时预创建话题 - 修正欢迎界面唤醒词提示文本为"飞宝飞宝" - 重构ASR回调处理逻辑,添加onDirectChat回调支持
这个提交包含在:
父节点
2757854f53
当前提交
5f44916c90
@ -1,10 +1,15 @@
|
||||
package com.nova.brain.glass.helper
|
||||
|
||||
import android.R.attr.action
|
||||
import android.app.AlertDialog
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import com.nova.brain.glass.BuildConfig
|
||||
import com.nova.brain.glass.R
|
||||
import com.nova.brain.glass.model.RecognizeAction
|
||||
import com.rokid.online.speech.AsrClient
|
||||
import com.rokid.online.speech.OnlineSpeechSdk
|
||||
@ -57,6 +62,9 @@ object AsrHelper : OfflineCmdListener {
|
||||
/** goToDecisionCenter 命中时的回调,由各 Activity 在 onResume/onPause 中注册/清空 */
|
||||
var onGoToDecisionCenter: ((action: RecognizeAction) -> Unit)? = null
|
||||
|
||||
/** scene == "decision" 时直接用 ASR 文本发起对话,由 ChatActivity 注册 */
|
||||
var onDirectChat: ((text: String) -> Unit)? = null
|
||||
|
||||
fun init() {
|
||||
val cfg = OnlineSpeechSdkConfig(
|
||||
domain = DOMAIN,
|
||||
@ -121,11 +129,28 @@ object AsrHelper : OfflineCmdListener {
|
||||
val activity = runCatching { AppManager.getInstance().getActivity() }.getOrNull()
|
||||
?: 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)
|
||||
.setMessage("飞宝在呢,您请说")
|
||||
.setView(contentView)
|
||||
.setCancelable(false)
|
||||
.create()
|
||||
.also { it.show() }
|
||||
.also { dialog ->
|
||||
dialog.show()
|
||||
// 让 Dialog 窗口本身透明,只显示自定义 view 的背景
|
||||
dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,6 +187,9 @@ object AsrHelper : OfflineCmdListener {
|
||||
asr?.stopAsrWithMic()
|
||||
Log.d(TAG, "ASR final result: $text")
|
||||
dismissListeningDialog()
|
||||
if (scene == "decision") {
|
||||
onDirectChat?.invoke(text)
|
||||
} else {
|
||||
IntentRecognizeHelper.recognize(
|
||||
text = text,
|
||||
scence = scene,
|
||||
@ -174,6 +202,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinished(taskId: String) {
|
||||
Log.d(TAG, "ASR ended: $taskId")
|
||||
|
||||
@ -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.data.ChatData
|
||||
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 okhttp3.RequestBody
|
||||
import okhttp3.ResponseBody
|
||||
@ -26,4 +28,7 @@ interface Service {
|
||||
@POST("/cbrain-gateway/cbraindep/docqa/chat/qa03")
|
||||
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") ?: ""
|
||||
if (question.isNotEmpty()) {
|
||||
viewModel.demoPostSse(question)
|
||||
viewModel.prepareTopic(question)
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +106,10 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
|
||||
OfflineCmdServiceHelper.addOnLineListener(listener)
|
||||
AsrHelper.scene = "decision"
|
||||
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()
|
||||
OfflineCmdServiceHelper.removeOnLineListener(listener)
|
||||
AsrHelper.onGoToDecisionCenter = null
|
||||
AsrHelper.onDirectChat = null
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
@ -22,7 +22,7 @@ class WelcomeActivity : BaseActivity<ActivityWelcomeBinding>() {
|
||||
private lateinit var vm: WelcomeVM
|
||||
|
||||
// 点动画
|
||||
private val tvOriginalText = "您可以说:Nova,我的任务有哪些?"
|
||||
private val tvOriginalText = "您可以说:飞宝飞宝,我的任务有哪些?"
|
||||
private val dotsHandler = Handler(Looper.getMainLooper())
|
||||
private var dotCount = 0
|
||||
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.ChatModel2
|
||||
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.xuqm.base.common.GsonImplHelp
|
||||
import com.xuqm.base.common.LogHelper
|
||||
import com.xuqm.base.di.manager.HttpManager
|
||||
import com.xuqm.base.viewmodel.BaseListViewModel
|
||||
import com.xuqm.base.viewmodel.callback.Response
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.json.JSONObject
|
||||
@ -24,6 +26,7 @@ class ChatVM : BaseListViewModel<ChatItem>() {
|
||||
|
||||
companion object {
|
||||
const val SPACER_ID = -1
|
||||
private const val DEFAULT_TOPIC_ID = 14478
|
||||
}
|
||||
|
||||
val result = MutableLiveData<String>()
|
||||
@ -74,7 +77,24 @@ class ChatVM : BaseListViewModel<ChatItem>() {
|
||||
}
|
||||
}
|
||||
|
||||
fun demoPostSse(question: String) {
|
||||
/**
|
||||
* 先请求 tbtopic 接口拿 topicId,成功则使用返回值,失败/code≠0 则用默认值,再发起 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 = null
|
||||
stopThinkingAnimation()
|
||||
@ -86,7 +106,7 @@ class ChatVM : BaseListViewModel<ChatItem>() {
|
||||
result.postValue(UUID.randomUUID().toString())
|
||||
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())
|
||||
.subscribe({ body ->
|
||||
var content = ""
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#ff3FFF5F"
|
||||
android:textSize="19sp"
|
||||
android:text="您可以说:Nova,我的任务有哪些?"
|
||||
android:text="您可以说:飞宝飞宝,我的任务有哪些?"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
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" />
|
||||
正在加载...
在新工单中引用
屏蔽一个用户