feat(chat): 集成意图识别功能并优化聊天界面交互
- 添加 IntentRecognizeHelper 工具类实现意图识别功能 - 修改 RecognizeData 数据类默认添加 goToTaskCenter 和 goToDecisionCenter 动作 - 在 RecognizeModel 中增加 error 字段支持错误信息处理 - 更新 ChatActivity 实现语音识别驱动的聊天功能 - 将 WelcomeActivity 的点击事件改为意图识别后跳转到决策中心 - 修改 ChatVM 支持传入问题参数并优化加载逻辑 - 切换到公网测试服务器地址 - 添加必要的导入和清理无用导入 - 实现页面销毁时清理资源防止内存泄漏
这个提交包含在:
父节点
c017470ce7
当前提交
57c505d4b5
@ -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}")
|
||||||
})
|
})
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户