Explorar o código

fix(chat): 修复聊天界面加载状态和滚动问题

- 添加了 onComplete 回调参数确保识别完成后重置加载状态
- 修复 Toast 显示错误信息时使用正确的 message 字段
- 改进加载指示器显示逻辑,添加第二个进度条 pb1
- 实现智能滚动到底部功能,支持 SSE 流式内容更新
- 优化 recognizeAndChat 方法中的加载状态管理
- 添加 UUID 随机数触发结果更新,过滤 SSE 非数据行
徐勤民 hai 21 horas
pai
achega
83ce6341ab

+ 9 - 2
app/src/main/java/com/nova/brain/glass/helper/IntentRecognizeHelper.kt

@@ -32,6 +32,7 @@ object IntentRecognizeHelper {
     private var disposable: Disposable? = null
 
     private val baseUrl: String = "https://22v1322u01.vicp.fun"
+//    private val baseUrl: String = "http://192.168.6.20:12119"
 
     /**
      * @param context  用于显示 Toast
@@ -39,11 +40,15 @@ object IntentRecognizeHelper {
      * @param scence   场景标识,默认 "home"
      * @param onSuccess 识别成功且 code=="0" 时回调,参数为 [RecognizeAction]
      */
+    /**
+     * @param onComplete 无论成功失败都会回调,用于调用方重置 loading 状态
+     */
     fun recognize(
         context: Context,
         text: String? = null,
         scence: String = "home",
-        onSuccess: (action: RecognizeAction) -> Unit
+        onSuccess: (action: RecognizeAction) -> Unit,
+        onComplete: () -> Unit = {}
     ) {
         disposable?.dispose()
         val question = text ?: nextQuestion()
@@ -59,10 +64,12 @@ object IntentRecognizeHelper {
                 if (model.code == "0") {
                     onSuccess(model.data.action)
                 } else {
-                    Toast.makeText(context, model.data.error, Toast.LENGTH_SHORT).show()
+                    Toast.makeText(context, model.message, Toast.LENGTH_SHORT).show()
+                    onComplete()
                 }
             }, { e ->
                 Toast.makeText(context, "请求失败: ${e.message}", Toast.LENGTH_SHORT).show()
+                onComplete()
             })
     }
 

+ 36 - 3
app/src/main/java/com/nova/brain/glass/ui/ChatActivity.kt

@@ -35,13 +35,17 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
     override fun initData() {
         super.initData()
 
+        // SSE 流式内容更新时持续滚底
         viewModel.result.observe(this) {
-            val lastIndex = (recyclerView.adapter?.itemCount ?: 1) - 1
-            if (lastIndex >= 0) recyclerView.scrollToPosition(lastIndex)
+            scrollToBottom()
         }
 
+        // loading=true:pb 显示旋转 + 延一帧滚底(等 PagedList 提交到 adapter)
+        // loading=false:pb 停转并隐藏
         viewModel.loading.observe(this) { loading ->
-            binding.pb.isIndeterminate = loading
+            binding.pb.visibility = if (loading) View.VISIBLE else View.INVISIBLE
+            binding.pb1.visibility = if (!loading) View.VISIBLE else View.INVISIBLE
+            recyclerView.post { scrollToBottom() }
         }
 
         val question = intent.getStringExtra("question") ?: ""
@@ -50,18 +54,47 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
         }
     }
 
+    /**
+     * 调用意图识别,识别成功后发起 SSE 请求。
+     * 立即启动 pb 旋转,不等 recognize 接口响应。
+     */
     private fun recognizeAndChat() {
+        binding.pb.visibility = View.VISIBLE
+        binding.pb1.visibility = View.INVISIBLE
         IntentRecognizeHelper.recognize(
             context = this,
             scence = "decision",
             onSuccess = { action ->
                 if (action.name == "goToDecisionCenter") {
                     viewModel.demoPostSse(action.params.question)
+                } else {
+                    // 识别成功但没有匹配动作,停止旋转
+                    binding.pb.isIndeterminate = false
+                    binding.pb.visibility = View.INVISIBLE
                 }
+            },
+            onComplete = {
+                // recognize 失败时兜底停止旋转(成功路径由 loading LiveData 接管)
+                binding.pb.isIndeterminate = false
+                binding.pb.visibility = View.INVISIBLE
             }
         )
     }
 
+    private fun scrollToBottom() {
+        val lastIndex = (recyclerView.adapter?.itemCount ?: 1) - 1
+        if (lastIndex < 0) return
+        val lm = recyclerView.layoutManager as? androidx.recyclerview.widget.LinearLayoutManager ?: return
+        // 第一帧:确保 lastIndex 进入可视区域
+        lm.scrollToPosition(lastIndex)
+        // 第二帧:layout 完成后,把 lastView 的底部对齐到 RecyclerView 底部
+        recyclerView.post {
+            val lastView = lm.findViewByPosition(lastIndex) ?: return@post
+            val gap = lastView.bottom - (recyclerView.height - recyclerView.paddingBottom)
+            if (gap > 0) recyclerView.scrollBy(0, gap)
+        }
+    }
+
     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)

+ 5 - 1
app/src/main/java/com/nova/brain/glass/viewmodel/ChatVM.kt

@@ -15,6 +15,7 @@ import com.xuqm.base.viewmodel.BaseListViewModel
 import com.xuqm.base.viewmodel.callback.Response
 import io.reactivex.disposables.Disposable
 import io.reactivex.schedulers.Schedulers
+import java.util.UUID
 
 class ChatVM : BaseListViewModel<ChatItem>() {
     val result = MutableLiveData<String>()
@@ -42,6 +43,7 @@ class ChatVM : BaseListViewModel<ChatItem>() {
         val itemId = itemIdCounter++
         chatItems.add(ChatItem(itemId, question, ""))
         if (dataSourceReady) invalidate()
+        result.postValue(UUID.randomUUID().toString())
         loading.postValue(true)
 
         currentTask = HttpManager.getApi(Service::class.java).chat(ChatData(question))
@@ -63,7 +65,9 @@ class ChatVM : BaseListViewModel<ChatItem>() {
                                     loading.postValue(false)
                                     return@use
                                 }
-                                val json = if (l.startsWith("data:")) l.removePrefix("data:").trim() else l
+                                // 只处理 data:{...} 格式,其他行(event/id/注释等)跳过
+                                if (!l.startsWith("data:{")) continue
+                                val json = l.removePrefix("data:").trim()
                                 val model = GsonImplHelp.get().toObject(json, ChatModel::class.java)
                                 if (model.type == null) {
                                     loading.postValue(false)

+ 9 - 0
app/src/main/res/layout/activity_chat.xml

@@ -21,6 +21,15 @@
             android:layout_width="40dp"
             android:layout_height="40dp"
             android:indeterminateDrawable="@drawable/load_progress" />
+        <ImageView
+            android:id="@+id/pb1"
+            app:layout_constraintTop_toBottomOf="@+id/baseRecyclerView"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintBottom_toBottomOf="@+id/baseRecyclerView"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:src="@drawable/loading"/>
 
         <TextView
             android:id="@+id/hint1"