feat(chat): 集成意图识别功能并优化聊天界面交互

- 添加 IntentRecognizeHelper 工具类实现意图识别功能
- 修改 RecognizeData 数据类默认添加 goToTaskCenter 和 goToDecisionCenter 动作
- 在 RecognizeModel 中增加 error 字段支持错误信息处理
- 更新 ChatActivity 实现语音识别驱动的聊天功能
- 将 WelcomeActivity 的点击事件改为意图识别后跳转到决策中心
- 修改 ChatVM 支持传入问题参数并优化加载逻辑
- 切换到公网测试服务器地址
- 添加必要的导入和清理无用导入
- 实现页面销毁时清理资源防止内存泄漏
这个提交包含在:
徐勤民 2026-04-16 17:55:38 +08:00
父节点 c017470ce7
当前提交 57c505d4b5
共有 7 个文件被更改,包括 137 次插入36 次删除

查看文件

@ -6,6 +6,7 @@ import com.nova.brain.glass.repository.HeaderInterceptor;
import com.rokid.security.glass3.open.sdk.GlassSdk; import com.rokid.security.glass3.open.sdk.GlassSdk;
import com.rokid.security.glass3.open.sdk.client.IServiceConnectionCallback; import com.rokid.security.glass3.open.sdk.client.IServiceConnectionCallback;
import com.xuqm.base.App; import com.xuqm.base.App;
import com.xuqm.base.di.component.AppComponent;
import com.xuqm.base.di.manager.HttpManager; import com.xuqm.base.di.manager.HttpManager;
/** /**
@ -13,8 +14,8 @@ import com.xuqm.base.di.manager.HttpManager;
*/ */
public class MyApplication extends App { public class MyApplication extends App {
public static String baseUrl = "http://192.168.6.20"; // public static String baseUrl = "http://192.168.6.20";
// public static String baseUrl = "http://22fs132201.imwork.net"; public static String baseUrl = "http://22fs132201.imwork.net";
@Override @Override
public void onCreate() { public void onCreate() {

查看文件

@ -0,0 +1,79 @@
package com.nova.brain.glass.helper
import android.content.Context
import android.widget.Toast
import com.nova.brain.glass.helper.IntentRecognizeHelper.dispose
import com.nova.brain.glass.helper.IntentRecognizeHelper.recognize
import com.nova.brain.glass.model.RecognizeAction
import com.nova.brain.glass.model.data.RecognizeData
import com.nova.brain.glass.repository.HeaderInterceptor
import com.nova.brain.glass.repository.Service
import com.rokid.utils.ContextUtil.getApplicationContext
import com.xuqm.base.di.manager.HttpManager
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
/**
* 意图识别全局工具
*
* - [recognize] 发起识别请求自动轮换内置问题作为 text Welcome 页调用
* - [dispose] 在页面销毁时调用取消进行中的请求
*/
object IntentRecognizeHelper {
private val builtInQuestions = listOf(
"C大脑V2.24版本进行到什么阶段了",
"我的任务有哪些",
"个人任务系统功能的需求来自哪个项目",
"浙江华瑞项目有哪些进行中的采购流程"
)
private var questionIndex = 0
private var disposable: Disposable? = null
private val baseUrl: String = "https://22v1322u01.vicp.fun"
/**
* @param context 用于显示 Toast
* @param text 指定问题文本 null 时自动轮换内置问题
* @param scence 场景标识默认 "home"
* @param onSuccess 识别成功且 code=="0" 时回调参数为 [RecognizeAction]
*/
fun recognize(
context: Context,
text: String? = null,
scence: String = "home",
onSuccess: (action: RecognizeAction) -> Unit
) {
disposable?.dispose()
val question = text ?: nextQuestion()
disposable = HttpManager.getApi(
HttpManager.getAppComponent(
baseUrl,
HeaderInterceptor(getApplicationContext())
), Service::class.java)
.recognize(RecognizeData(text = question, scence = scence))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ model ->
if (model.code == "0") {
onSuccess(model.data.action)
} else {
Toast.makeText(context, model.data.error, Toast.LENGTH_SHORT).show()
}
}, { e ->
Toast.makeText(context, "请求失败: ${e.message}", Toast.LENGTH_SHORT).show()
})
}
fun dispose() {
disposable?.dispose()
disposable = null
}
private fun nextQuestion(): String {
val q = builtInQuestions[questionIndex % builtInQuestions.size]
questionIndex++
return q
}
}

查看文件

@ -29,6 +29,7 @@ data class RecognizeModel(
// } // }
data class RecognizeModelData( data class RecognizeModelData(
val action: RecognizeAction, val action: RecognizeAction,
val error: String?,
) )
//{ //{

查看文件

@ -12,5 +12,5 @@ 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<String> = emptyList(),
val actions: List<String> = emptyList() val actions: List<String> = listOf("goToTaskCenter","goToDecisionCenter")
) )

查看文件

@ -3,8 +3,9 @@ package com.nova.brain.glass.ui
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import com.nova.brain.glass.R import com.nova.brain.glass.R
import com.nova.brain.glass.helper.BgChatDrawable
import com.nova.brain.glass.databinding.ActivityChatBinding import com.nova.brain.glass.databinding.ActivityChatBinding
import com.nova.brain.glass.helper.BgChatDrawable
import com.nova.brain.glass.helper.IntentRecognizeHelper
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.ChatItem import com.nova.brain.glass.model.ChatItem
@ -25,7 +26,7 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
runOnUiThread { runOnUiThread {
when (cmd) { when (cmd) {
"退出", "返回", "退回" -> finish() "退出", "返回", "退回" -> finish()
"继续" -> viewModel.demoPostSse() "继续" -> recognizeAndChat()
} }
} }
} }
@ -40,24 +41,38 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
} }
viewModel.loading.observe(this) { loading -> viewModel.loading.observe(this) { loading ->
binding.pb.visibility = if (loading) View.VISIBLE else View.GONE binding.pb.isIndeterminate = loading
} }
viewModel.demoPostSse() val question = intent.getStringExtra("question") ?: ""
if (question.isNotEmpty()) {
viewModel.demoPostSse(question)
}
}
private fun recognizeAndChat() {
IntentRecognizeHelper.recognize(
context = this,
scence = "decision",
onSuccess = { action ->
if (action.name == "goToDecisionCenter") {
viewModel.demoPostSse(action.params.question)
}
}
)
} }
override fun adapter() = object : CommonPagedAdapter<ChatItem>(R.layout.item_chat) { override fun adapter() = object : CommonPagedAdapter<ChatItem>(R.layout.item_chat) {
override fun convert(holder: ViewHolder, item: ChatItem, position: Int) { override fun convert(holder: ViewHolder, item: ChatItem, position: Int) {
holder.setVisibility(R.id.line, position!=0) holder.setVisibility(R.id.line, position != 0)
val chatItems = viewModel.chatItems val chatItems = viewModel.chatItems
val liveItem = if (position < chatItems.size) chatItems[position] else item val liveItem = if (position < chatItems.size) chatItems[position] else item
holder.setText(R.id.title, liveItem.title) holder.setText(R.id.title, liveItem.title)
val tv = holder.getView<TextView>(R.id.content) val tv = holder.getView<TextView>(R.id.content)
markwon.setMarkdown(tv, liveItem.content) markwon.setMarkdown(tv, liveItem.content)
holder.setClickListener(R.id.root, { holder.setClickListener(R.id.root) {
viewModel.demoPostSse() recognizeAndChat()
}) }
} }
} }
@ -74,4 +89,9 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
super.onPause() super.onPause()
OfflineCmdServiceHelper.removeOnLineListener(listener) OfflineCmdServiceHelper.removeOnLineListener(listener)
} }
override fun onDestroy() {
super.onDestroy()
IntentRecognizeHelper.dispose()
}
} }

查看文件

@ -5,6 +5,7 @@ import android.os.Bundle
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.nova.brain.glass.R import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivityWelcomeBinding import com.nova.brain.glass.databinding.ActivityWelcomeBinding
import com.nova.brain.glass.helper.IntentRecognizeHelper
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.viewmodel.WelcomeVM import com.nova.brain.glass.viewmodel.WelcomeVM
@ -27,7 +28,20 @@ class WelcomeActivity : BaseActivity<ActivityWelcomeBinding>() {
} }
binding.btnGet.setOnClickListener { vm.demoGet() } binding.btnGet.setOnClickListener { vm.demoGet() }
binding.btnPost.setOnClickListener { vm.demoPost() } binding.btnPost.setOnClickListener { vm.demoPost() }
binding.md.setOnClickListener { startActivity(Intent(this@WelcomeActivity, ChatActivity::class.java)) } binding.md.setOnClickListener {
IntentRecognizeHelper.recognize(
context = this,
text = "当前阶段,最紧急的任务是什么?",
onSuccess = { action ->
if (action.name == "goToDecisionCenter") {
startActivity(
Intent(this, ChatActivity::class.java)
.putExtra("question", action.params.question)
)
}
}
)
}
} }
override fun initData() { override fun initData() {

查看文件

@ -1,5 +1,7 @@
package com.nova.brain.glass.viewmodel package com.nova.brain.glass.viewmodel
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.nova.brain.glass.model.ChatItem import com.nova.brain.glass.model.ChatItem
import com.nova.brain.glass.model.ChatModel import com.nova.brain.glass.model.ChatModel
@ -7,11 +9,10 @@ import com.nova.brain.glass.model.ChatModel1
import com.nova.brain.glass.model.data.ChatData import com.nova.brain.glass.model.data.ChatData
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.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 android.os.Handler
import android.os.Looper
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
@ -21,20 +22,7 @@ class ChatVM : BaseListViewModel<ChatItem>() {
val chatItems: MutableList<ChatItem> = mutableListOf() val chatItems: MutableList<ChatItem> = mutableListOf()
private var currentTask: Disposable? = null private var currentTask: Disposable? = null
private var itemIdCounter = 0
private val questions = listOf(
"我的代办任务有哪些?",
"今天天气情况如何?",
"当前设备状态如何?",
"今日巡检计划是什么?",
"有哪些待处理的告警?",
"最近的维修记录有哪些?",
"当前区域的安全评分是多少?",
"有哪些设备需要维护?",
"今天有哪些会议安排?",
"当前库存状态如何?"
)
private var questionIndex = 0
private var dataSourceReady = false private var dataSourceReady = false
private val mainHandler = Handler(Looper.getMainLooper()) private val mainHandler = Handler(Looper.getMainLooper())
@ -47,14 +35,11 @@ class ChatVM : BaseListViewModel<ChatItem>() {
} }
} }
fun demoPostSse() { fun demoPostSse(question: String) {
currentTask?.dispose() currentTask?.dispose()
currentTask = null currentTask = null
val question = questions[questionIndex % questions.size] val itemId = itemIdCounter++
val itemId = questionIndex
questionIndex++
chatItems.add(ChatItem(itemId, question, "")) chatItems.add(ChatItem(itemId, question, ""))
if (dataSourceReady) invalidate() if (dataSourceReady) invalidate()
loading.postValue(true) loading.postValue(true)
@ -70,11 +55,10 @@ class ChatVM : BaseListViewModel<ChatItem>() {
val l = line!! val l = line!!
if (l.isNotEmpty()) { if (l.isNotEmpty()) {
if (l.trimStart().startsWith("<")) { if (l.trimStart().startsWith("<")) {
// HTML响应(隧道断开),回退问题索引并在主线程重试 // HTML 响应(隧道断开),移除占位项并在主线程用相同问题重试
mainHandler.post { mainHandler.post {
questionIndex--
if (chatItems.isNotEmpty()) chatItems.removeAt(chatItems.size - 1) if (chatItems.isNotEmpty()) chatItems.removeAt(chatItems.size - 1)
demoPostSse() demoPostSse(question)
} }
loading.postValue(false) loading.postValue(false)
return@use return@use
@ -104,6 +88,7 @@ class ChatVM : BaseListViewModel<ChatItem>() {
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
LogHelper.e(">>>>11", e)
loading.postValue(false) loading.postValue(false)
val errMsg = "AI反馈异常: ${e.message}" val errMsg = "AI反馈异常: ${e.message}"
val lastIndex = chatItems.size - 1 val lastIndex = chatItems.size - 1
@ -116,6 +101,7 @@ class ChatVM : BaseListViewModel<ChatItem>() {
} }
loading.postValue(false) loading.postValue(false)
}, { e -> }, { e ->
LogHelper.e(">>>>22", e)
loading.postValue(false) loading.postValue(false)
result.postValue("AI反馈异常: ${e.message}") result.postValue("AI反馈异常: ${e.message}")
}) })