Ver Fonte

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

- 添加 IntentRecognizeHelper 工具类实现意图识别功能
- 修改 RecognizeData 数据类默认添加 goToTaskCenter 和 goToDecisionCenter 动作
- 在 RecognizeModel 中增加 error 字段支持错误信息处理
- 更新 ChatActivity 实现语音识别驱动的聊天功能
- 将 WelcomeActivity 的点击事件改为意图识别后跳转到决策中心
- 修改 ChatVM 支持传入问题参数并优化加载逻辑
- 切换到公网测试服务器地址
- 添加必要的导入和清理无用导入
- 实现页面销毁时清理资源防止内存泄漏
徐勤民 há 22 horas atrás
pai
commit
57c505d4b5

+ 3 - 2
app/src/main/java/com/nova/brain/glass/MyApplication.java

@@ -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() {

+ 79 - 0
app/src/main/java/com/nova/brain/glass/helper/IntentRecognizeHelper.kt

@@ -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
+    }
+}

+ 1 - 0
app/src/main/java/com/nova/brain/glass/model/RecognizeModel.kt

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

+ 1 - 1
app/src/main/java/com/nova/brain/glass/model/data/RecognizeData.kt

@@ -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")
 )

+ 28 - 8
app/src/main/java/com/nova/brain/glass/ui/ChatActivity.kt

@@ -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()
+    }
 }

+ 15 - 1
app/src/main/java/com/nova/brain/glass/ui/WelcomeActivity.kt

@@ -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() {

+ 10 - 24
app/src/main/java/com/nova/brain/glass/viewmodel/ChatVM.kt

@@ -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}")
             })