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 class MyApplication extends App {
public static String baseUrl = "http://192.168.27.248:8080"; public static String baseUrl = "http://22fs132201.imwork.net";
@Override @Override
public void onCreate() { 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() 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("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() // context.getStringForPreferences(SHARE_UESR_TOKEN).loge()

查看文件

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

查看文件

@ -1,20 +1,20 @@
package com.nova.brain.glass.ui package com.nova.brain.glass.ui
import android.widget.TextView import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
import com.nova.brain.glass.R import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivityChatBinding import com.nova.brain.glass.databinding.ActivityChatBinding
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
import com.nova.brain.glass.viewmodel.ChatVM 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.CommonPagedAdapter
import com.xuqm.base.adapter.ViewHolder import com.xuqm.base.adapter.ViewHolder
import com.xuqm.base.ui.BaseListActivity import com.xuqm.base.ui.BaseActivity
import com.xuqm.base.ui.BaseListFormLayoutNormalActivity
import io.noties.markwon.Markwon 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 getLayoutId(): Int = R.layout.activity_chat
override fun fullscreen(): Boolean = true override fun fullscreen(): Boolean = true
private val listener = object : OfflineCmdListener { private val listener = object : OfflineCmdListener {
@ -29,10 +29,19 @@ class ChatActivity : BaseListFormLayoutNormalActivity<ChatItem, ChatVM, Activity
} }
} }
private lateinit var markwon: Markwon private lateinit var markwon: Markwon
private lateinit var vm: ChatVM
override fun initData() { override fun initData() {
super.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() { override fun onResume() {
@ -45,14 +54,13 @@ private lateinit var markwon: Markwon
OfflineCmdServiceHelper.removeOnLineListener(listener) OfflineCmdServiceHelper.removeOnLineListener(listener)
} }
private val adapter = object : CommonPagedAdapter<ChatItem>(R.layout.item_chat) { // private val 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.setText(R.id.title, item.title) // 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.btnGet.setOnClickListener { vm.demoGet() }
binding.btnPost.setOnClickListener { vm.demoPost() } binding.btnPost.setOnClickListener { vm.demoPost() }
binding.btnSse.setOnClickListener { vm.demoPostSse() }
binding.md.setOnClickListener { startActivity(Intent(this@WelcomeActivity, ChatActivity::class.java)) } binding.md.setOnClickListener { startActivity(Intent(this@WelcomeActivity, ChatActivity::class.java)) }
} }

查看文件

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

查看文件

@ -36,28 +36,4 @@ class WelcomeVM : BaseViewModel() {
}).adds() }).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"?> <?xml version="1.0" encoding="utf-8"?>
<layout> <layout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <TextView
android:id="@+id/baseRecyclerView" android:id="@+id/title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:overScrollMode="never" /> android:textColor="#2EB242"
android:textSize="10sp" />
<TextView
</androidx.constraintlayout.widget.ConstraintLayout> 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> </layout>