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.client.IServiceConnectionCallback;
import com.xuqm.base.App;
import com.xuqm.base.di.component.AppComponent;
import com.xuqm.base.di.manager.HttpManager;
/**
@ -13,8 +14,8 @@ import com.xuqm.base.di.manager.HttpManager;
*/
public class MyApplication extends App {
public static String baseUrl = "http://192.168.6.20";
// public static String baseUrl = "http://22fs132201.imwork.net";
// public static String baseUrl = "http://192.168.6.20";
public static String baseUrl = "http://22fs132201.imwork.net";
@Override
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(
val action: RecognizeAction,
val error: String?,
)
//{

查看文件

@ -12,5 +12,5 @@ data class RecognizeData(
val text: String,
val scence: String = "home",
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.widget.TextView
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.helper.BgChatDrawable
import com.nova.brain.glass.helper.IntentRecognizeHelper
import com.nova.brain.glass.helper.OfflineCmdListener
import com.nova.brain.glass.helper.OfflineCmdServiceHelper
import com.nova.brain.glass.model.ChatItem
@ -25,7 +26,7 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
runOnUiThread {
when (cmd) {
"退出", "返回", "退回" -> finish()
"继续" -> viewModel.demoPostSse()
"继续" -> recognizeAndChat()
}
}
}
@ -40,24 +41,38 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
}
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 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 liveItem = if (position < chatItems.size) chatItems[position] else item
holder.setText(R.id.title, liveItem.title)
val tv = holder.getView<TextView>(R.id.content)
markwon.setMarkdown(tv, liveItem.content)
holder.setClickListener(R.id.root, {
viewModel.demoPostSse()
})
holder.setClickListener(R.id.root) {
recognizeAndChat()
}
}
}
@ -74,4 +89,9 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
super.onPause()
OfflineCmdServiceHelper.removeOnLineListener(listener)
}
override fun onDestroy() {
super.onDestroy()
IntentRecognizeHelper.dispose()
}
}

查看文件

@ -5,6 +5,7 @@ import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import com.nova.brain.glass.R
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.OfflineCmdServiceHelper
import com.nova.brain.glass.viewmodel.WelcomeVM
@ -27,7 +28,20 @@ class WelcomeActivity : BaseActivity<ActivityWelcomeBinding>() {
}
binding.btnGet.setOnClickListener { vm.demoGet() }
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() {

查看文件

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