feat(chat): 实现聊天功能并集成AI助手接口

- 添加 ChatData 和 ChatModel 数据类用于处理聊天请求和响应
- 修改 HeaderInterceptor 配置请求头,添加认证和环境相关参数
- 更新 Service 接口,将 SSE 流改为 POST 请求到 AI 助手端点
- 重构 ChatActivity,移除分页列表结构,实现单次问答界面
- 在 ChatVM 中实现 SSE 流式响应处理逻辑,支持不同消息类型解析
- 移除 WelcomeActivity 中的 SSE 按钮,调整基地址配置
- 修改布局文件,从 RecyclerView 改为 TextView 显示问答内容
这个提交包含在:
徐勤民 2026-04-16 15:01:03 +08:00
父节点 13e3afa8cb
当前提交 6801b998e2
共有 10 个文件被更改,包括 156 次插入94 次删除

查看文件

@ -13,7 +13,7 @@ import com.xuqm.base.di.manager.HttpManager;
*/
public class MyApplication extends App {
public static String baseUrl = "http://192.168.27.248:8080";
public static String baseUrl = "http://22fs132201.imwork.net";
@Override
public void onCreate() {

查看文件

@ -0,0 +1,44 @@
package com.nova.brain.glass.model
//{
// "id": 74216,
// "role": "assistant",
// "createTime": "2026-04-16T06:13:32.678Z",
// "type": "reason",
// "data": {
// "content": "数据。",
// "duration": 0.0
// },
// "metadata": {}
//}
//{
// "id": 74216,
// "role": "assistant",
// "createTime": "2026-04-16T06:13:32.678Z",
// "type": "string",
// "data": "最",
// "metadata": {}
//}
//{"date":"2026-04-16T06:49:19.790Z","msg":"当前话题存在进行中的请求,请稍后重试","code":409,"success":false,"uri":"/docqa/chat/qa03","status":409}
data class ChatModel(
val type: String,
val msg: String,
)
data class ChatModel1(
val id: Int,
val role: String,
val createTime: String,
val type: String,
val data: String,
)
data class ChatModel2Data(
val content: String,
)
data class ChatModel2(
val id: Int,
val role: String,
val createTime: String,
val type: String,
val data: ChatModel2Data,
)

查看文件

@ -0,0 +1,9 @@
package com.nova.brain.glass.model.data
//{"question":"我的任务哪个更紧急","topicId":14478,"knowledgeBaseId":"AI_ASSISTANT_MODE_FOR_GLASSES","model":"AI助手模式"}
data class ChatData(
val question: String,
val topicId: Int = 14478,
val knowledgeBaseId: String = "AI_ASSISTANT_MODE_FOR_GLASSES",
val model: String = "AI助手模式"
)

查看文件

@ -25,8 +25,15 @@ class HeaderInterceptor(val context: Context) : Interceptor {
//请求定制:添加请求头
val requestBuilder = original.newBuilder()
.header("Authentication", context.getStringForPreferences(SHARE_UESR_TOKEN))
.header("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJBSTAwMjIiLCJyblN0ciI6Im1OWUxobGZqWDJ2bEJMc21RdnFxSUE4dU9ZN0NOY3AzIiwidXNlckluZm8iOnsic3RhZmZObyI6IkFJMDAyMiJ9fQ.4BHcVpdkznqaQwPsyTn1my3_Zo0AliOILj_PjbCIK3k")
.addHeader("Content-Type", "application/json;charset=UTF-8")
.addHeader("Cookie", "__itrace_wid=87125211-8742-4f12-b5ca-32b9b6c860e4; locale=zh-Hans; _webtracing_device_id=t_13501877-b9b303fc-d3f52eb530e026b0")
.addHeader("Environment", "1")
.addHeader("currentUserId", "AI0022")
.addHeader("terminal", "1")
.addHeader("modulename", "web")
.addHeader("currentUserName", "")
// context.getStringForPreferences(SHARE_UESR_TOKEN).loge()

查看文件

@ -1,5 +1,7 @@
package com.nova.brain.glass.repository
import com.nova.brain.glass.model.ChatModel
import com.nova.brain.glass.model.data.ChatData
import io.reactivex.Observable
import okhttp3.RequestBody
import okhttp3.ResponseBody
@ -18,7 +20,7 @@ interface Service {
fun demoPost(@Body body: RequestBody): Observable<ResponseBody>
@Streaming
@GET("stream/{n}")
fun demoStream(@Path("n") n: Int): Observable<ResponseBody>
@POST("/cbrain-gateway/cbraindep/docqa/chat/qa03")
fun chat(@Body body: ChatData): Observable<ResponseBody>
}

查看文件

@ -1,20 +1,20 @@
package com.nova.brain.glass.ui
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivityChatBinding
import com.nova.brain.glass.helper.OfflineCmdListener
import com.nova.brain.glass.helper.OfflineCmdServiceHelper
import com.nova.brain.glass.model.ChatItem
import com.nova.brain.glass.viewmodel.ChatVM
import com.xuqm.base.adapter.BasePagedAdapter
import com.nova.brain.glass.viewmodel.WelcomeVM
import com.xuqm.base.adapter.CommonPagedAdapter
import com.xuqm.base.adapter.ViewHolder
import com.xuqm.base.ui.BaseListActivity
import com.xuqm.base.ui.BaseListFormLayoutNormalActivity
import com.xuqm.base.ui.BaseActivity
import io.noties.markwon.Markwon
class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, ActivityChatBinding>() {
class ChatActivity : BaseActivity<ActivityChatBinding>() {
override fun getLayoutId(): Int = R.layout.activity_chat
override fun fullscreen(): Boolean = true
private val listener = object : OfflineCmdListener {
@ -28,11 +28,20 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
}
}
}
private lateinit var markwon: Markwon
private lateinit var markwon: Markwon
private lateinit var vm: ChatVM
override fun initData() {
super.initData()
markwon = Markwon.create(this);
vm = ViewModelProvider(this)[ChatVM::class.java]
markwon = Markwon.create(this)
binding.title.text = "我的代办任务有哪些?"
vm.result.observe( this){
binding.content.text = it
}
vm.demoPostSse()
}
override fun onResume() {
@ -45,14 +54,13 @@ private lateinit var markwon: Markwon
OfflineCmdServiceHelper.removeOnLineListener(listener)
}
private val adapter = object : CommonPagedAdapter<ChatItem>(R.layout.item_chat) {
override fun convert(holder: ViewHolder, item: ChatItem, position: Int) {
holder.setText(R.id.title, item.title)
// private val adapter = object : CommonPagedAdapter<ChatItem>(R.layout.item_chat) {
// override fun convert(holder: ViewHolder, item: ChatItem, position: Int) {
// holder.setText(R.id.title, item.title)
//
// val tv = holder.getView<TextView>(R.id.content)
// markwon.setMarkdown(tv, item.content);
// }
// }
val tv = holder.getView<TextView>(R.id.content)
markwon.setMarkdown(tv, item.content);
}
}
override fun adapter(): BasePagedAdapter<ChatItem> = adapter
}

查看文件

@ -27,7 +27,6 @@ class WelcomeActivity : BaseActivity<ActivityWelcomeBinding>() {
}
binding.btnGet.setOnClickListener { vm.demoGet() }
binding.btnPost.setOnClickListener { vm.demoPost() }
binding.btnSse.setOnClickListener { vm.demoPostSse() }
binding.md.setOnClickListener { startActivity(Intent(this@WelcomeActivity, ChatActivity::class.java)) }
}

查看文件

@ -1,46 +1,55 @@
package com.nova.brain.glass.viewmodel
import com.nova.brain.glass.model.ChatItem
import com.xuqm.base.viewmodel.BaseListViewModel
import com.xuqm.base.viewmodel.callback.Response
import androidx.lifecycle.MutableLiveData
import com.nova.brain.glass.model.ChatModel
import com.nova.brain.glass.model.ChatModel1
import com.nova.brain.glass.model.ChatModel2
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.di.manager.HttpManager
import com.xuqm.sdhbwfu.core.viewModel.BaseViewModel
import io.reactivex.schedulers.Schedulers
class ChatVM : BaseListViewModel<ChatItem>() {
override fun loadData(
page: Int,
onResponse: Response<ChatItem>
) {
onResponse.onResponse(arrayListOf<ChatItem>().apply {
add(
ChatItem(
"本周周报", """
## 统计数据截止到2026年3月19日 1:36
### 纪检Agent3期产品设计明日到达计划完成时间
#### *重点关注*张三测试运维部负责的C大脑-脑实例- V2.24-测试方案设计
* 当前状态已延期13天严重程度为严重延期
* 参谋建议建议高优处理
""".trimIndent()
)
)
add(
ChatItem(
"我最紧急的任务是哪个", """
# 标题
这是 **加粗***斜体*~~删除线~~
- 列表1
- 列表2
[点击跳转](https://openai.com)
""".trimIndent()
)
)
})
class ChatVM : BaseViewModel() {
val result = MutableLiveData<String>()
private var t = "string"
fun demoPostSse() {
t = ""
HttpManager.getApi(Service::class.java).chat(ChatData("我的代办任务有哪些?"))
.subscribeOn(Schedulers.io())
.subscribe({ body ->
val sb = StringBuilder()
body.charStream().buffered().use { reader ->
try {
var line: String?
while (reader.readLine().also { line = it } != null) {
val l = line!!
if (l.isNotEmpty()) {
val model = GsonImplHelp.get().toObject(l, ChatModel::class.java)
if (t != model.type) {
sb.clear()
}
t = model.type
if (model.type == "string") {
val model1 =
GsonImplHelp.get().toObject(l, ChatModel1::class.java)
sb.appendLine(model1.data)
} else {
val model2 =
GsonImplHelp.get().toObject(l, ChatModel2::class.java)
sb.appendLine(model2.data.content)
}
result.postValue(sb.toString())
}
}
} catch (e: Exception) {
result.postValue("AI反馈异常: ${e.message}")
}
}
}, { e ->
result.postValue("AI反馈异常: ${e.message}")
}).adds()
}
}

查看文件

@ -36,28 +36,4 @@ class WelcomeVM : BaseViewModel() {
}).adds()
}
fun demoPostSse() {
result.postValue("SSE 连接中...")
HttpManager.getApi(Service::class.java).demoStream(5)
.subscribeOn(Schedulers.io())
.subscribe({ body ->
val sb = StringBuilder("SSE 流式响应:\n")
body.charStream().buffered().use { reader ->
try {
var line: String?
while (reader.readLine().also { line = it } != null) {
val l = line!!
if (l.isNotEmpty()) {
sb.appendLine(l)
result.postValue(sb.toString())
}
}
} catch (e: Exception) {
result.postValue("SSE 读取异常: ${e.message}")
}
}
}, { e ->
result.postValue("SSE 失败: ${e.message}")
}).adds()
}
}

查看文件

@ -1,18 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/app_color_black">
android:paddingHorizontal="29dp"
android:background="@color/app_color_black"
android:paddingVertical="10dp"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/baseRecyclerView"
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never" />
android:layout_height="wrap_content"
android:textColor="#2EB242"
android:textSize="10sp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="#ff40FF5E"
android:textSize="14sp" />
</LinearLayout>
</layout>