Android AI大模型WebSocket内容渲染【GPT、豆包、kimi】,支持markdown。支持代码块,具体代码块内容有需求的可以给我发邮箱或者私信wuyueyueai@vip.qq.com

ai问答效果视频

//1、初始化自定义AI操作manager  
AiWSManager.ins().init(this@AiActivity)
  AiWSManager.ins().initSessionId()

//2、创建监听器
AiWSManager.ins().aiMessageCallBackListener = object : AiWSManager.AiMessageCallBackListener {
                override fun onMessage(text: String?) {
                    this@AiActivity.runOnUiThread {
                        if (!TextUtils.isEmpty(text)) {

                            val aIChatBean = Gson().fromJson(text, AiWSManager.AIChatBean::class.java)
                            if (aIChatBean.sessionId == AiWSManager.ins().sessionId) {
                                //检查当前列表是否存在该数据
                                val isInChatIndex = aIChatBeanList.indexOfFirst { it.chatId == aIChatBean.chatId }
                                if (isInChatIndex == -1) { // 不存在,创建新的
                                    checkSave()
                                    createOneQuestion(aIChatBean)
                                } else { // 存在,更新现有的
                                    if (aIChatBeanList[isInChatIndex].interrupt == "F") {
                                        // 获取现有对象
                                        val existingBean = aIChatBeanList[isInChatIndex]
                                        // 更新 useAnswer 字段
                                        if (TextUtils.isEmpty(existingBean.useAnswer)) {
                                            existingBean.useAnswer = aIChatBean.answer
                                        } else {
                                            existingBean.useAnswer += aIChatBean.answer
                                        }

                                        if (aIChatBean.matchKnowledges.size > 0) {
                                            existingBean.matchKnowledges.clear()
                                            existingBean.matchKnowledges.addAll(aIChatBean.matchKnowledges)
                                        }
                                        if (aIChatBean.knowledgeInfo.size > 0) {
                                            existingBean.knowledgeInfo.clear()
                                            existingBean.knowledgeInfo.addAll(aIChatBean.knowledgeInfo)
                                        }
                                        existingBean.queryData = aIChatBean.queryData
                                        // 更新 answerFlag 字段
                                        existingBean.answerFlag = aIChatBean.answerFlag
                                        // 如果 answerFlag 是 "end",更新 keywords
                                        if ("end" == existingBean.answerFlag) {
                                            existingBean.keywords.clear()
                                            existingBean.keywords.addAll(aIChatBean.keywords)
                                        }
                                        // 确保更新后的对象保留在列表中
                                        aIChatBeanList[isInChatIndex] = existingBean
                                        // 开启线程监听单条数据更新状态
                                        AiWSManager.ins().aiAdapter.updateLastView(aIChatBeanList[isInChatIndex])
                                    }
                                }
                            } else {
                                Log.e("MZQ_AI", "非同一个会话内容$text")
                            }
                        }
                    }
                }

                override fun onReconnect() {
                    Log.e("MZQ_AI", "onReconnect--Ai网络错误处理:" + isIngChat)
                }


                override fun onFailure() {
                    Log.e("MZQ_AI", "onFailure--Ai网络错误处理:" + isIngChat)
                    if (isIngChat) {
                        userStop("S", object : ChatStopListener {
                            override fun stop() {

                            }
                        })
                    }
                }

                override fun onStatusChange() {
                    if (!isIngChat) {
                        sendCheckView()
                    }
                }

                override fun onOpen() {
                    if (AiWSManager.ins().isConnect) {
                        if (isFirstFlag) {
                            isFirstFlag = false
                            val question = intent.getStringExtra("question")
                            if (!TextUtils.isEmpty(question)) {
                                if (!isIngChat) {
                                    if (question != null) {
                                        sendMessage(question)
                                    }
                                }
                            }
                            if (!TextUtils.isEmpty(intent.getStringExtra("chatId"))) {
                                getChatById(intent.getStringExtra("chatId"))
                            }
                        }
                    }
                }
            }

//3、发起会话
  if (FastClickUtil.isFastClick()) {
                    return@setOnClickListener
                }
                if (AiWSManager.ins().isConnect) {
                    if (isIngChat) {
                        userStop("T", object : ChatStopListener {
                            override fun stop() {

                            }
                        })
                    } else {
                        if (TextUtils.isEmpty(editSend.text.toString().trim())) {
                            SolutionToast.show("请输入您的问题")
                            return@setOnClickListener
                        }
                        sendMessage(editSend.text.toString())
                    }
                } else {
                    Log.e("MZQ_AI", "AI大模型连接失败,当前AI提问功能不可用")
                }


//manager具体内容

import android.app.Activity
import android.content.Context
import android.os.Build
import android.text.TextUtils
import android.util.Log
import androidx.annotation.RequiresApi
import com.example.okhttp.OkHttpManager
import com.google.gson.reflect.TypeToken
import com.mohism.medtion.base.websocketUtils.AIWsManager
import com.mohism.medtion.base.websocketUtils.listener.WsStatusListener
import com.mohism.medtion.bean.BaseBean
import com.mohism.medtion.constant.ConstantAI
import com.mohism.medtion.constant.ConstantObj
import com.mohism.medtion.okhttp.NyhHttpUtil
import com.mohism.medtion.requestcallback.MyResultCallback
import com.mohism.medtion.ui.ai.adapter.AIAdapter
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okio.ByteString
import java.util.UUID

/**
 * Ai问答
 */
class AiWSManager {
    // 定义一个接口用于异步请求结果的回调
    interface AiClientCallback<T> {
        fun onSuccess(result: T?)
        fun onError(code: Int, msg: String?)
    }

    companion object {
        private var sInstance: AiWSManager? = null
        const val AI_TIP = "AI_TIP"

        @JvmStatic
        fun ins(): AiWSManager {
            if (sInstance == null) {
                sInstance = AiWSManager()
            }
            return sInstance as AiWSManager
        }
    }
    lateinit var aiAdapter: AIAdapter
    var wsManager: AIWsManager? = null
    var aiMessageCallBackListener: AiMessageCallBackListener? = null
    var isConnect = false
    var sessionId = ""
    fun init(context: Context) {
        close()
        if (wsManager == null) {
            wsManager = AIWsManager.Builder(context).client(
                OkHttpClient().newBuilder()
                    .retryOnConnectionFailure(true).build()
            ).needReconnect(true).wsUrl(ConstantObj.AI_URL).build()
            wsManager?.setWsStatusListener(wsStatusListener)
        }
        wsManager?.startConnect()
    }
    fun reInit(context: Context) {
        if (wsManager == null) {
            wsManager = AIWsManager.Builder(context).client(
                OkHttpClient().newBuilder()
                    .retryOnConnectionFailure(true).build()
            ).needReconnect(true).wsUrl(ConstantObj.AI_URL).build()
            wsManager?.setWsStatusListener(wsStatusListener)
        }
        wsManager?.startConnect()
    }

    fun initSessionId(session: String? = null) {
        sessionId = if (!TextUtils.isEmpty(session)) {
            session ?: UUID.randomUUID().toString()
        } else {
            UUID.randomUUID().toString()
        }
    }

    fun close() {
        sessionId = ""
        wsManager?.stopConnect()
    }


    var isOnFailure = false
    /**
     * 发送ai消息
     */
    fun sendAiMessage(json: String) {
        if (isConnect) {
            Log.e("MZQ_AI", "请求参数:$json")
            wsManager?.sendMessage(json)
        } else {
            Log.e("MZQ_AI", "网络连接失败,请稍后重试!")
        }
    }


    private val wsStatusListener: WsStatusListener = object : WsStatusListener() {
        override fun onOpen(response: Response?) {
            Log.e("MZQ_AI", "onOpen")
            isConnect = true
            isOnFailure = false
            aiMessageCallBackListener?.onOpen()
        }

        @RequiresApi(Build.VERSION_CODES.O)
        override fun onMessage(text: String?) {
            Log.e("MZQ_AI", "onMessage:$text")
            aiMessageCallBackListener?.onMessage(text)
        }

        override fun onMessage(bytes: ByteString?) {
            Log.e("MZQ_AI", "onMessage")
        }

        override fun onReconnect() {
            isConnect = false
            Log.e("MZQ_AI", "onReconnect")
            aiMessageCallBackListener?.onStatusChange()
        }

        override fun onClosing(code: Int, reason: String?) {
            isConnect = false
            Log.e("MZQ_AI", "onClosing$code------$reason")
            aiMessageCallBackListener?.onStatusChange()
        }

        override fun onClosed(code: Int, reason: String?) {
            isConnect = false
            Log.e("MZQ_AI", "onClosed$code------$reason")
            aiMessageCallBackListener?.onStatusChange()
        }

        override fun onFailure(t: Throwable?, response: Response?) {
            isConnect = false
            isOnFailure = true
            Log.e("MZQ_AI", "onFailure")
            aiMessageCallBackListener?.onFailure()
            aiMessageCallBackListener?.onStatusChange()
        }
    }

    interface AiMessageCallBackListener {
        fun onStatusChange()
        fun onMessage(text: String?)
        fun onReconnect()
        fun onFailure()
        fun onOpen()
    }


    enum class AiModel(var modelName: String) {
        CHAT_GPT("chatgpt"),
        KIMI("kimi"),
        DOU_BAO("doubao"),
    }

    /**
     * 获取问答请求参数
     */
    data class AiQuestionRequestBean(
        var question: String? = null,
        var model: String? = null,
        var history: MutableList<AiQuestionHistory> = ArrayList(),
        var sessionId: String? = null
    )

    data class AiStopRequestBean(
        var stopFlag: Boolean = true,
        var chatId: String? = null
    )

    data class AiQuestionHistory(
        var query: String? = null,
        var answer: String = ""
    )

    /**
     * 相关问题请求对象
     */
    data class AiQuestionOtherBean(
        var question: String? = null,
        var chatId: String? = null
    )

    data class AIHistoryBean(
        val sessionId: String? = null, // 会话的唯一标识符
        var name: String? = null,
        var dateType: Int = 1,//1:今天 2:本周 3:本月 4:更早
        var dateValue: String? = null,
    )

    /**
     * 表示来自 AI 聊天系统的响应的数据类。
     *
     * @property queryData 查询数据的信息;NULL 表示没有找到相关数据。
     * @property answerFlag 答案的状态;可以是 "start"、"ing"、"end" 或 NULL 表示答案尚未生成。
     * @property answer AI 模型提供的答案。
     * @property knowledgeInfo 与查询相关的知识信息列表。
     * @property matchKnowledges 匹配到的相关知识列表(仅供展示,不提供跳转)。
     * @property keyWords 与查询相关的关键词列表。
     * @property chatId 聊天会话的唯一标识符。
     * @property sessionId 会话的唯一标识符。
     */
    data class AIChatBean(
        var isDefaultChat: Boolean = false,//是否为默认消息

        var queryData: String? = null, // NULL值说明没找到相关资料
        var answerFlag: String? = null, // start:答案开始输出。 ing:答案输出中。 end:答案输出完成。 null值表示正在获取资料答案还没开始生成
        var answer: String? = null, // 模型回答
        var useAnswer: String? = null,//使用回答
        val knowledgeInfo: MutableList<AIKnowledgeInfo> = ArrayList(), // 知识信息列表
        val matchKnowledges: MutableList<AIMatchKnowledge> = ArrayList(), // 已查找到的相关参考资料列表
        var chatId: String? = null, // 会话的唯一标识符
        var sessionId: String? = null, // 会话的唯一标识符
        val keywords: MutableList<String> = ArrayList(), // 关键词列表
        val question: String? = null,//问题标题
        var collect: String = "F",
        var feedback: String = "F",
        val relatedQuestions: MutableList<String> = ArrayList(),
        var createTime: String? = null,
        var updateTime: String? = null,
        var interrupt: String = "F",//用户是否手动暂停?/或者网络中断
        var isOpenMore: Boolean = false,//是否展开更多了
        var isRequestOther: Boolean = false,//是否已经请求过该接口
        var saveStatus: Boolean = false,
        var isCollect: Boolean = false,//是否是收藏过来的数据
        var isTipShowing:Boolean = false,//knowLedge是否展示完毕
    )

    /**
     * 表示知识信息项的数据类。
     *
     * @property title 知识项的标题。
     * @property type 知识项的类型(例如,信息、文献、社区问答)。
     * @property url 知识项的 URL。
     * @property author 知识项的作者,可能为 NULL。
     */
    data class AIKnowledgeInfo(
        val title: String? = null, // 标题
        val type: String? = null,// 类型
        val url: String? = null, // URL
        var content: String? = null,
        val magazineId: Int? = null,//
        val parentId: Int? = null,//
        val answerId: Int? = null,//
        val userId: Int? = null,
        val productId:Int?=null,
        val extras:Int?=null,
    )

    data class AIFeedBackBean(var id: Int? = null, var name: String? = null, var isSelect: Boolean = false)

    /**
     * 表示匹配到的知识项的数据类(仅供展示)。
     *
     * @property title 匹配到的知识项的标题。
     */
    data class AIMatchKnowledge(
        val title: String // 标题
    )

    data class AiCollectBean(var collectStatus: String? = null)
}


//具体呈现的adapter

import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.text.Html
import android.text.TextUtils
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.NonNull
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.google.gson.Gson
import com.mohism.medtion.R
import com.mohism.medtion.bean.BaseBean
import com.mohism.medtion.constant.ConstantObj
import com.mohism.medtion.databinding.AiItemBinding
import com.mohism.medtion.databinding.AiItemTopBinding
import com.mohism.medtion.ext.gone
import com.mohism.medtion.ext.visible
import com.mohism.medtion.ui.ai.AiWSManager
import com.mohism.medtion.ui.ai.util.AIGrammarLocator
import com.mohism.medtion.ui.ai.util.VerticalSpaceItemDecoration
import com.mohism.medtion.ui.ai.view.AIBottomSheetFeedBackFragment
import com.mohism.medtion.ui.home.search.SearchAllActivity
import com.mohism.medtion.ui.homenew.widget.ForegroundActivityManager
import com.mohism.medtion.ui.rtc.view.BIMMessageRecyclerView
import com.mohism.medtion.util.AnalysysAgentUtil
import com.mohism.medtion.util.ShareUtil
import com.mohism.medtion.util.SharedPreferencesManager
import com.mohism.medtion.util.TagViewUtil
import com.mohism.medtion.util.ToolUtil
import io.noties.markwon.Markwon
import io.noties.markwon.image.AsyncDrawable
import io.noties.markwon.image.glide.GlideImagesPlugin
import io.noties.markwon.image.glide.GlideImagesPlugin.GlideStore
import io.noties.markwon.syntax.Prism4jThemeDefault
import io.noties.markwon.syntax.SyntaxHighlightPlugin
import io.noties.prism4j.Prism4j
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext

open class AIAdapter(var context: Context, private val listBeans: List<AiWSManager.AIChatBean>, val recyclerView: BIMMessageRecyclerView) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private val inflater: LayoutInflater = LayoutInflater.from(context)
    protected var spManager: SharedPreferencesManager = SharedPreferencesManager.getInstance(context)
    protected var ds: DisplayMetrics = context.resources.displayMetrics
    val prism4j = Prism4j(AIGrammarLocator())
    private val markdown = Markwon.builder(context).usePlugin(GlideImagesPlugin.create(GifGlideStore(Glide.with(context))))
        .usePlugin(SyntaxHighlightPlugin.create(prism4j, Prism4jThemeDefault.create(Color.parseColor("#f6f6f6")))).build()

    private class GifGlideStore(requestManager: RequestManager) : GlideStore {
        private val requestManager: RequestManager

        init {
            this.requestManager = requestManager
        }

        @NonNull
        override fun load(@NonNull drawable: AsyncDrawable): RequestBuilder<Drawable> {
            // NB! Strange behaviour: First time a resource is requested - it fails, the next time it loads fine
            val destination: String = drawable.destination
            return requestManager // it is enough to call this (in order to load GIF and non-GIF)
                .asDrawable().addListener(object : RequestListener<Drawable?> {

                    override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable?>?, isFirstResource: Boolean): Boolean {
                        return false
                    }

                    override fun onResourceReady(
                        resource: Drawable?, model: Any?, target: Target<Drawable?>?, dataSource: com.bumptech.glide.load.DataSource?, isFirstResource: Boolean
                    ): Boolean {
                        // we must start GIF animation manually
                        if (resource is Animatable) {
                            (resource as Animatable).start()
                        }
                        return false
                    }

                }).load(destination)
        }

        override fun cancel(target: Target<*>) {
            requestManager.clear(target)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if (viewType == TYPE_DEFAULT) {
            DefaultViewHolder(AiItemTopBinding.inflate(inflater, parent, false))
        } else {
            ChatViewHolder(AiItemBinding.inflate(inflater, parent, false))
        }
    }

    inner class DefaultViewHolder(val binding: AiItemTopBinding) : RecyclerView.ViewHolder(binding.root)
    inner class ChatViewHolder(val binding: AiItemBinding) : RecyclerView.ViewHolder(binding.root)

    override fun getItemViewType(position: Int): Int {
        return if (listBeans[position].isDefaultChat) {
            TYPE_DEFAULT
        } else {
            TYPE_CHAT
        }
    }

    companion object {
        const val TYPE_DEFAULT = 0
        const val TYPE_CHAT = 1
    }

    var onAiCallBackListener: OnAiCallBackListener? = null

    interface OnAiCallBackListener {
        fun onStart(chatId: String?)
        fun onIng(chatId: String?)
        fun onNewQuestion(question: String)
        fun saveChat(chatId: String?)
        fun onStop()
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val aIChatBean = listBeans[position]
        if (!aIChatBean.isDefaultChat) {
            holder as ChatViewHolder
            holder.binding.apply {
                updateView(holder.itemView, aIChatBean)
                getQuestListOther(holder.itemView, aIChatBean)
            }
        }
    }

    override fun getItemCount(): Int {
        return listBeans.size
    }


    private val markdownLoading = """
![gif-image](file:///android_asset/ai_loading.gif)
"""
    val regex = Regex("```")
    private fun getRecycleLastView(): View? {
        val lastItemPosition = recyclerView.adapter?.itemCount
        if (lastItemPosition!! > 0) {
            val viewHolder = recyclerView.findViewHolderForAdapterPosition(0)
            return viewHolder?.itemView
        }
        return null
    }

    //
    var currentDisplayedText = ""
    var intervalTime = 20 // 1秒
    suspend fun startDisplayingKnowledge() {
        var isGo = true
        while ((isFinish() || isGo) && listBeans[0].interrupt == "F") {
            getRecycleLastView()?.apply {
                try {
                    val binding = AiItemBinding.bind(this)
                    binding.apply {
                        val targetText = listBeans[0].useAnswer ?: ""
                        if (!TextUtils.isEmpty(targetText)) {
                            //执行完毕后调用内容线程
                            withContext(Dispatchers.Main) {
                                tvAiTitle.gone()
                            }
                            if (currentDisplayedText.length < targetText.length) {
                                isGo = true
                                currentDisplayedText += targetText[currentDisplayedText.length]
                                withContext(Dispatchers.Main) {
                                    // 使用 Regex 查找包含 "```" 的个数 当持续输出代码块内容时 去除尾部追加的loading状态
                                    val matchCount = regex.findAll(currentDisplayedText).count()
                                    if (matchCount % 2 == 1) {
                                        markdown.setMarkdown(tvAiContent, currentDisplayedText)
                                    } else {
                                        markdown.setMarkdown(tvAiContent, currentDisplayedText + markdownLoading)
                                    }
                                    tvAiContent.visible()
                                }
                                // 根据剩余内容量调整等待时间
                                val remainingChars = targetText.length - currentDisplayedText.length
                                val delayTime = if (remainingChars > 50) 5L else 20L
                                delay(delayTime) // 每个字符输出后等待相应的毫秒
                            }
                            if (!TextUtils.isEmpty(listBeans[0].answerFlag) && listBeans[0].answerFlag == "end" && currentDisplayedText.length == targetText.length) {
                                isGo = false
                                withContext(Dispatchers.Main) {
                                    // 移除尾部追加的markdownContent
                                    markdown.setMarkdown(tvAiContent, currentDisplayedText.replace(markdownLoading, ""))
                                    if (currentDisplayedText.contains("您好,我是脑医汇")) {
                                        llyBottom.gone()
                                        llySave.gone()
                                        llyShare.gone()
                                        llyMenu.gone()
                                        llyLine.gone()
                                        tvTip.gone()
                                        tvAiTitle.gone()
                                        onAiCallBackListener?.saveChat(listBeans[0].chatId)
                                    } else {
                                        llyBottom.visible()
                                        llySave.visible()
                                        llyShare.visible()
                                        llyMenu.visible()
                                        llyLine.visible()
                                        tvTip.visible()
                                        llyBody.setBackgroundResource(R.drawable.ai_text_bg_off)
                                        if (listBeans[0].relatedQuestions.size > 0) {
                                            llyOther.visible()
                                        } else {
                                            llyOther.gone()
                                        }
                                        onAiCallBackListener?.saveChat(listBeans[0].chatId)
                                        doEndView(listBeans[0], binding)
                                    }

                                }
                            }
                            delay(20)
                        } else {
                            withContext(Dispatchers.Main) {
                                tvAiTitle.visible()
                                tvAiContent.gone()
                                if (intervalTime == 0) {
                                    tvAiTitle.text = "答案生成中…"
                                } else {
                                    tvAiTitle.text = Html.fromHtml("正在获取资料,答案预计在<font color='#0581ce'>$intervalTime 秒</font>内生成…", Html.FROM_HTML_MODE_LEGACY)
                                    intervalTime--
                                }
                            }
                            delay(1000)//每秒执行一次
                        }
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
    }

    private fun isFinish() = !listBeans[0].saveStatus
    fun updateLastView(aIChatBean: AiWSManager.AIChatBean?) {
        getRecycleLastView()?.apply {
            val binding = AiItemBinding.bind(this)
            binding.apply {
                llyBottom.gone()
                llyKnowLedgeInfo.gone()
                tvAiContent.gone()
                tvMore.gone()
                viewMoreLine.gone()
                llyTag.gone()
                llyReTry.gone()
                llyMenu.gone()
                llySave.gone()
                llyShare.gone()
                llyOther.gone()
                llyLine.gone()
                tvTip.gone()
                llyBody.setBackgroundResource(0)
                ToolUtil.loadImg(context, spManager.getString(ConstantObj.AVATARADRESS), imgUserPhoto, R.drawable.ai_user_default, (24 * ds.density).toInt(), (24 * ds.density).toInt())
                aIChatBean?.apply {
                    tvQuestion.text = aIChatBean.question
                    if (!TextUtils.isEmpty(aIChatBean.answerFlag)) {
                        when (aIChatBean.answerFlag) {
                            "start" -> {
                                onAiCallBackListener?.onStart(aIChatBean.chatId)
                                tvAiContent.text = ""
                            }

                            "ing" -> {
                                tvAiTitle.gone()
                                tvAiContent.visible()
                                onAiCallBackListener?.onIng(aIChatBean.chatId)
                            }

                            "end" -> {
                                tvAiContent.visible()
                            }
                        }
                    } else {
                        tvAiTitle.visible()
                    }
                    stopViewInit(binding, aIChatBean)
                    setListener(binding, aIChatBean)
                }
            }
        }
    }

    private fun updateView(view: View?, aIChatBean: AiWSManager.AIChatBean?) {
        view?.let {
            val binding = AiItemBinding.bind(view)
            binding.apply {
                llyBottom.gone()
                llyKnowLedgeInfo.gone()
                tvAiContent.gone()
                tvMore.gone()
                viewMoreLine.gone()
                llyTag.gone()
                llyReTry.gone()
                llyMenu.gone()
                llySave.gone()
                llyShare.gone()
                llyOther.gone()
                llyLine.visible()
                tvTip.gone()
                llyBody.setBackgroundResource(R.drawable.ai_text_bg_off)
                ToolUtil.loadImg(context, spManager.getString(ConstantObj.AVATARADRESS), imgUserPhoto, R.drawable.ai_user_default, (24 * ds.density).toInt(), (24 * ds.density).toInt())
                aIChatBean?.apply {
                    tvQuestion.text = aIChatBean.question
                    if (!TextUtils.isEmpty(aIChatBean.answerFlag)) {
                        when (aIChatBean.answerFlag) {
                            "start" -> {
                                if (!TextUtils.isEmpty(aIChatBean.useAnswer)) {
                                    aIChatBean.useAnswer?.let { markdown.setMarkdown(tvAiContent, it) }
                                    tvAiContent.visible()
                                }
                            }

                            "ing" -> {
                                if (!TextUtils.isEmpty(aIChatBean.useAnswer)) {
                                    aIChatBean.useAnswer?.let { markdown.setMarkdown(tvAiContent, it) }
                                    tvAiContent.visible()
                                }
                            }

                            "end" -> {
                                if (aIChatBean.answer?.contains("您好,我是脑医汇") == true || aIChatBean.useAnswer?.contains("您好,我是脑医汇") == true) {
                                    llyBottom.gone()
                                    llySave.gone()
                                    llyShare.gone()
                                    llyMenu.gone()
                                    llyLine.gone()
                                    tvTip.gone()
                                    tvAiTitle.gone()
                                    if (!TextUtils.isEmpty(aIChatBean.useAnswer)) {
                                        aIChatBean.useAnswer?.let { markdown.setMarkdown(tvAiContent, it) }
                                        tvAiContent.visible()
                                    }
                                } else {
                                    if (aIChatBean.saveStatus || aIChatBean.isCollect) {
                                        llyBottom.visible()
                                        llySave.visible()
                                        llyShare.visible()
                                        llyMenu.visible()
                                        tvTip.visible()
                                        doEndView(
                                            aIChatBean, binding
                                        )
                                        if (!TextUtils.isEmpty(aIChatBean.useAnswer)) {
                                            aIChatBean.useAnswer?.let { markdown.setMarkdown(tvAiContent, it) }
                                            tvAiContent.visible()
                                        }
                                    } else {
                                        tvTip.gone()
                                        llyBottom.gone()
                                        llySave.gone()
                                        llyShare.gone()
                                        llyMenu.gone()
                                    }
                                }
                            }
                        }
                    } else {
                        tvAiTitle.gone()
                    }
                    stopViewInit(binding, aIChatBean)
                    setListener(binding, aIChatBean)
                }
            }
        }

    }

    private fun stopViewInit(binding: AiItemBinding, aIChatBean: AiWSManager.AIChatBean) {
        binding.apply {
            if (aIChatBean.interrupt == "T" || aIChatBean.interrupt == "S") {
                //去除markdown
                aIChatBean.useAnswer?.replace(markdownLoading, "")?.let { markdown.setMarkdown(tvAiContent, it) }
                tvPause.text = if (aIChatBean.interrupt == "T") "(用户暂停)" else "(连接已断开)"
                llyBottom.gone()
                llyMenu.visible()
                llyReTry.visible()
                llyZan.visible()
                tvPause.visible()
                llySave.gone()
                llyShare.gone()
                llyKnowLedgeInfo.gone()
                tvAiTitle.gone()
                tvTip.gone()
                llyBody.setBackgroundResource(R.drawable.ai_text_bg_off)
            } else {
                llyReTry.gone()
                tvPause.gone()
            }
        }
    }

    private fun setListener(binding: AiItemBinding, aIChatBean: AiWSManager.AIChatBean?) {
        binding.apply {
            llyReTry.setOnClickListener {
                onAiCallBackListener?.onNewQuestion(aIChatBean?.question.toString())
            }
            llyShare.setOnClickListener {
                ShareUtil.shareDefult(
                    context as Activity,
                    ConstantObj.SHARE_URL + AiWSManager.URL_SHARE_URL + aIChatBean?.chatId,
                    AiWSManager.URL_SHARE_TITLE + aIChatBean?.question,
                    AiWSManager.URL_SHARE_DES,
                    R.mipmap.ai_icon_share
                )
            }
            llySave.setOnClickListener {
                if (aIChatBean != null) {
                    collectAiQa(aIChatBean, imgSave)
                }
            }
            llyZan.setOnClickListener {
                if (aIChatBean?.feedback == "F") {
                    AIBottomSheetFeedBackFragment.showRtcBottomSheet(context as Activity, aIChatBean, imgZan)
                } else {
                    if (aIChatBean != null) {
                        cancelSaveFeedback(aIChatBean, imgZan)
                    }
                }
            }
            tvSendCommunity.setOnClickListener {
                ForegroundActivityManager.getInstance().getCurrentActivity()?.let { it1 ->
                    TagViewUtil.createCommunity(
                        it1,
                        null,
                        "",
                        null,
                        null,
                        null,
                        AnalysysAgentUtil.AnalysysAgentType.AI_PAGE_NAME.AnalyName + "-" + aIChatBean?.question,
                        defaultQuestion = aIChatBean?.question.toString(), btnName = "发布问题"
                    )
                }
            }
        }
    }

    private fun doEndView(
        aIChatBean: AiWSManager.AIChatBean, binding: AiItemBinding
    ) {
        //输出结束
        binding.apply {
            if (aIChatBean.knowledgeInfo.isNotEmpty()) {
                llyKnowLedgeInfo.visible()
                recyclerView.layoutManager = LinearLayoutManager(context)
                val useKnowledgeInfo: MutableList<AiWSManager.AIKnowledgeInfo> = ArrayList() // 知识信息列表
                val infoAdapter = AIInfoAdapter(context, useKnowledgeInfo, aIChatBean.question)
                recyclerView.adapter = infoAdapter
                if (aIChatBean.isOpenMore) {
                    useKnowledgeInfo.clear()
                    useKnowledgeInfo.addAll(aIChatBean.knowledgeInfo)
                    infoAdapter.notifyDataSetChanged()
                    tvMore.text = "收起"
                    tvMore.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ai_more_up, 0)
                } else {
                    useKnowledgeInfo.clear()
                    useKnowledgeInfo.addAll(aIChatBean.knowledgeInfo.take(3).toMutableList())
                    infoAdapter.notifyDataSetChanged()
                    tvMore.text = "展开全部"
                    tvMore.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ai_more_down, 0)
                }
                if (aIChatBean.knowledgeInfo.size > 3) {
                    tvMore.visible()
                    viewMoreLine.visible()
                    tvMore.setOnClickListener {
                        if (aIChatBean.isOpenMore) {
                            aIChatBean.isOpenMore = false
                            useKnowledgeInfo.clear()
                            useKnowledgeInfo.addAll(aIChatBean.knowledgeInfo.take(3).toMutableList())
                            infoAdapter.notifyDataSetChanged()
                            tvMore.text = "展开全部"
                            tvMore.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ai_more_down, 0)
                            AnalysysAgentUtil.instance.btnClick(context, "AI问答", "参考资料-收起")
                        } else {
                            aIChatBean.isOpenMore = true
                            useKnowledgeInfo.clear()
                            useKnowledgeInfo.addAll(aIChatBean.knowledgeInfo)
                            infoAdapter.notifyDataSetChanged()
                            tvMore.text = "收起"
                            tvMore.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ai_more_up, 0)
                            AnalysysAgentUtil.instance.btnClick(context, "AI问答", "参考资料-展开全部")
                        }
                    }
                }
                if (recyclerView.itemDecorationCount == 0) {
                    recyclerView.addItemDecoration(VerticalSpaceItemDecoration((ds.density * 12).toInt()))
                }
            }
            if (aIChatBean.keywords.isNotEmpty()) {
                llyTag.visible()
                floatTag.removeAllViews()
                aIChatBean.keywords.take(3).forEach { keyWords ->
                    floatTag.addView(TextView(context).apply {
                        text = keyWords
                        textSize = 14f
                        maxLines = 1
                        ellipsize = TextUtils.TruncateAt.END
                        setTextColor(Color.parseColor("#111111"))
                        setBackgroundResource(R.drawable.ai_tag_key)
                        setPadding((ds.density * 8).toInt(), (ds.density * 4).toInt(), (ds.density * 8).toInt(), (ds.density * 4).toInt())
                        setOnClickListener {
                            onAiCallBackListener?.onStop()
                            SearchAllActivity.startSearchAllActivitySearch(context, keyWords)
                            AnalysysAgentUtil.instance.btnClick(context, "AI问答", "关键词-$keyWords")
                        }
                    })
                }
            }
            if (aIChatBean.collect == "F") {
                imgSave.setImageResource(R.drawable.ai_save)
            } else {
                imgSave.setImageResource(R.drawable.ai_save_on)
            }
            if (aIChatBean.feedback == "F") {
                imgZan.setImageResource(R.drawable.ai_zan)
            } else {
                imgZan.setImageResource(R.drawable.ai_zan_on)
            }
        }
    }

    private fun cancelSaveFeedback(aIChatBean: AiWSManager.AIChatBean, imgZan: ImageView) {
        AiWSManager.ins().cancelFeedback(context as Activity, HashMap<String, String?>().apply {
            put("userId", spManager.getString(ConstantObj.USERID))
            put("chatId", aIChatBean.chatId)
        }, object : AiWSManager.AiClientCallback<Boolean> {
            override fun onSuccess(result: Boolean?) {
                imgZan.setImageResource(R.drawable.ai_zan)
                aIChatBean.feedback = "F"
            }

            override fun onError(code: Int, msg: String?) {
            }

        })
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无敌程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值