From 83ce6341abd36f13e8f224ff4f8f2b10f6bc5af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=8B=A4=E6=B0=91?= Date: Thu, 16 Apr 2026 18:15:51 +0800 Subject: [PATCH] =?UTF-8?q?fix(chat):=20=E4=BF=AE=E5=A4=8D=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E7=95=8C=E9=9D=A2=E5=8A=A0=E8=BD=BD=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=92=8C=E6=BB=9A=E5=8A=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了 onComplete 回调参数确保识别完成后重置加载状态 - 修复 Toast 显示错误信息时使用正确的 message 字段 - 改进加载指示器显示逻辑,添加第二个进度条 pb1 - 实现智能滚动到底部功能,支持 SSE 流式内容更新 - 优化 recognizeAndChat 方法中的加载状态管理 - 添加 UUID 随机数触发结果更新,过滤 SSE 非数据行 --- .../glass/helper/IntentRecognizeHelper.kt | 11 +++++- .../com/nova/brain/glass/ui/ChatActivity.kt | 39 +++++++++++++++++-- .../com/nova/brain/glass/viewmodel/ChatVM.kt | 6 ++- app/src/main/res/layout/activity_chat.xml | 9 +++++ 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/nova/brain/glass/helper/IntentRecognizeHelper.kt b/app/src/main/java/com/nova/brain/glass/helper/IntentRecognizeHelper.kt index 0659e6e..c6330a0 100644 --- a/app/src/main/java/com/nova/brain/glass/helper/IntentRecognizeHelper.kt +++ b/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() }) } diff --git a/app/src/main/java/com/nova/brain/glass/ui/ChatActivity.kt b/app/src/main/java/com/nova/brain/glass/ui/ChatActivity.kt index aff3ab9..d8b62f9 100644 --- a/app/src/main/java/com/nova/brain/glass/ui/ChatActivity.kt +++ b/app/src/main/java/com/nova/brain/glass/ui/ChatActivity.kt @@ -35,13 +35,17 @@ class ChatActivity : BaseListFormLayoutNormalActivity= 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 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(R.layout.item_chat) { override fun convert(holder: ViewHolder, item: ChatItem, position: Int) { holder.setVisibility(R.id.line, position != 0) diff --git a/app/src/main/java/com/nova/brain/glass/viewmodel/ChatVM.kt b/app/src/main/java/com/nova/brain/glass/viewmodel/ChatVM.kt index a2ef33b..71b2528 100644 --- a/app/src/main/java/com/nova/brain/glass/viewmodel/ChatVM.kt +++ b/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() { val result = MutableLiveData() @@ -42,6 +43,7 @@ class ChatVM : BaseListViewModel() { 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() { 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) diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index b15ffdd..d4f48c4 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/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" /> +