feat(chat): 实现聊天功能并集成AI助手接口
- 添加 ChatData 和 ChatModel 数据类用于处理聊天请求和响应 - 修改 HeaderInterceptor 配置请求头,添加认证和环境相关参数 - 更新 Service 接口,将 SSE 流改为 POST 请求到 AI 助手端点 - 重构 ChatActivity,移除分页列表结构,实现单次问答界面 - 在 ChatVM 中实现 SSE 流式响应处理逻辑,支持不同消息类型解析 - 移除 WelcomeActivity 中的 SSE 按钮,调整基地址配置 - 修改布局文件,从 RecyclerView 改为 TextView 显示问答内容
这个提交包含在:
父节点
13e3afa8cb
当前提交
6801b998e2
@ -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 {
|
||||||
@ -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() {
|
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>
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户