src/views/ai/LanguageModel.vue
@@ -1,11 +1,15 @@
<template>
  <div class="page-container">
    <div class="outer-container">
      <!--左侧历史会话区域-->
      <div class="left-container">
        <!--logo区域-->
        <div class="logo-container"><img src="@/assets/page/languageModel/logo.png"></div>
        <!--功能按键区域-->
        <div class="manage-history-container">
          <div @click="createNewConversation"
               :class="[isAtNewConversation?'create-history-container-active':'',isDeletingBatch?'fold-create-history-container':'expand-create-history-container']"
               :class="[isAtNewConversation?'create-history-container-active':'',isDeletingBatch?'fold-create-history-container':'expand-create-history-container',isModelResponding?'disable-expand':'']"
               class="create-new-conversation">
            <a-icon type="plus"></a-icon>
            <span v-if="!isDeletingBatch">新增会话</span>
@@ -38,7 +42,7 @@
                    </div>
                  </template>
                  <div @click="deleteBatchConversation" id="delete-batch-button"
                       :class="[checkedConversationIdList.length?'able-delete-button':'disable-delete-button']">
                       :class="[checkedConversationIdList.length&&!isModelResponding?'able-delete-button':'disable-delete-button']">
                    <a-icon type="delete"/>
                    <div>删除</div>
                  </div>
@@ -52,9 +56,18 @@
            </template>
          </div>
        </div>
        <!--历史会话列表区域-->
        <div class="chat-history-container">
          <template v-if="!chatHistoryList.length">
            <a-empty>
              <span slot="description">无历史会话</span>
            </a-empty
            >
          </template>
          <div v-for="(item,index) in chatHistoryList" :key="item.id"
               :class="[item.id===activeHistoryIndex?'single-history-active':'',item.inputVisible?'input-visible-class':'']"
               :class="[item.id===activeHistoryIndex?'single-history-active':'',item.inputVisible?'input-visible-class':'',isModelResponding?'disable-switch':'']"
               @click="switchToCurrentConversation(item,$event)"
               @mouseenter="item.iconVisible=true"
               @mouseleave="item.iconVisible=false">
@@ -65,7 +78,6 @@
              <span class="check-mark"></span>
            </label>
            <a-popover placement="top" :visible="item.deletePopVisible" trigger="click"
                       :getPopupContainer="node=>node.parentNode">
              <template slot="content">
@@ -73,13 +85,14 @@
                  <div>删除后无法恢复,是否继续删除?</div>
                  <div>
                    <button class="cancel-delete-button" @click="cancelDeleteConversation(item,$event)">取消</button>
                    <button @click="confirmDeleteConversation(item,index)" id="delete-conversation-button">删除</button>
                    <button @click.stop="confirmDeleteConversation(item,index)" id="delete-conversation-button">删除
                    </button>
                  </div>
                </div>
              </template>
              <template v-if="!item.inputVisible">
                <div class="conversation-title">{{item.title}}</div>
                <div class="icon-container hover-icon-container" v-show="!isDeletingBatch">
                <div class="conversation-title">{{item.problem.slice(0,15)}}</div>
                <div class="icon-container hover-icon-container" v-show="!isDeletingBatch&&!isModelResponding">
                  <a-icon type="edit" @click.stop="editConversationTitle(item)"/>
                  <a-icon type="delete" @click.stop="deleteConversation(item)"
                          @blur="cancelDeleteConversation(item,$event)"/>
@@ -102,28 +115,33 @@
        </div>
      </div>
      <!--右侧会话内容区域-->
      <div class="right-container">
        <!--会话内容列表区域-->
        <div class="conversation-container">
          <div v-for="item in currentConversation.messages" class="single-conversation" :id="'id'+item.index">
            <div v-if="item.role==='user'" class="user-question">
              <div class="avatar">
                <a-avatar :src="getAvatar()"/>
              </div>
              <div class="content">{{item.content}}</div>
              <div class="conversation-content">{{item.content}}</div>
            </div>
            <div v-else class="assistant-answer">
              <div class="avatar">
                <img src="@/assets/page/languageModel/ai-avatar.png"/>
              </div>
              <div class="content">{{item.content}}</div>
              <div class="conversation-content" v-html="item.content.replace(/\n/g,'<br>')"></div>
            </div>
          </div>
        </div>
        <!--提问输入区域-->
        <div class="input-container" :class="[textareaFocused?'input-container-active':'']">
          <textarea v-model="inputQuestion" :placeholder="textareaPlaceholder"
                    @keydown.enter="sendQuestion($event)" @focus="textareaFocused=true"
                    @blur="textareaFocused=false"></textarea>
          <img src="@/assets/page/languageModel/send-message.png" @click="sendQuestion($event)" v-if="!isResponding">
          <img src="@/assets/page/languageModel/send-message.png" @click="sendQuestion($event)"
               v-if="!isModelResponding">
          <a-icon type="loading" class="loading-icon" v-else/>
        </div>
      </div>
@@ -136,6 +154,16 @@
  import { mapGetters } from 'vuex'
  import { message } from 'ant-design-vue'
  import { randomUUID } from '@/utils/util'
  import {
    addNewConversationApi,
    getChatHistoryListApi,
    getCurrentConversationApi,
    deleteSingleChatHistoryApi,
    askToLanguageModelApi
  } from '@/api/ai'
  import { EventSourcePolyfill } from 'event-source-polyfill'
  import Vue from 'vue'
  import { ACCESS_TOKEN, TENANT_ID } from '@/store/mutation-types'
  message.config({
    maxCount: 1,
@@ -151,62 +179,22 @@
        checkedConversationIdList: [],
        deleteBatchPopVisible: false,
        currentConversation: {
          id: '683a65fd-8feb-4446-ad32-714c4785f667',
          messages: [
            {
              'role': 'user',
              'content': '你是谁?你是谁你是谁你是谁你是谁你是谁你是谁你是谁你是谁你是谁你是谁你是谁你是谁你是谁你是谁你是谁你是谁你是谁'
            },
            {
              'role': 'assistant',
              'content': '我是26中生涯指导师小爱'
            },
            {
              'role': 'user',
              'content': '你都能做些什么?'
            },
            {
              'role': 'assistant',
              'content': '我能站在未来视角帮助同学们做好生涯规划指导。'
            },
            {
              'role': 'user',
              'content': '今天天气如何?'
            },
            {
              'role': 'assistant',
              'content': '我是26中生涯指导师小爱'
            },
            {
              'role': 'user',
              'content': '你都能做些什么?'
            },
            {
              'role': 'assistant',
              'content': '我能站在未来视角帮助同学们做好生涯规划指导。'
            }
          ],
          stream: false,
          max_tokens: 500,
          iconVisible: false,
          inputVisible: false,
          deletePopVisible: false
          id: '',
          messages: [],
          stream: true,
          max_tokens: 500
        },
        // currentConversation: {},
        isAtNewConversation: false,
        isDeletingBatch: false,
        activeHistoryIndex: null,
        editingHistoryIndex: null,
        deletingHistoryIndex: null,
        iconVisible: false,
        inputVisible: false,
        editedConversationTitle: '',
        deletePopVisible: false,
        conversationContainer: null,
        inputQuestion: '',
        textareaPlaceholder: 'Enter发送,Shift+Enter换行',
        textareaPlaceholder: '',
        textareaFocused: false,
        isResponding: false
        isModelResponding: false
      }
    },
@@ -219,40 +207,265 @@
            document.removeEventListener('click', this.handleDocumentClick)
          }
        }
      },
      isAtNewConversation: {
        handler(val) {
          if (val) {
            this.textareaPlaceholder = '请尝试问我:你是谁?'
          } else {
            this.textareaPlaceholder = 'Enter发送,Shift+Enter换行'
          }
        },
        immediate: true
      }
    },
    created() {
      this.getChatHistoryListByApi()
    },
    mounted() {
      this.getConversationByApi()
      this.conversationContainer = document.querySelector('.conversation-container')
    },
    methods: {
      ...mapGetters(['avatar']),
      /* 调用接口获取当前会话记录 */
      getConversationByApi() {
        this.currentConversation.title = this.currentConversation.messages[0].content.slice(0, 15)
        this.chatHistoryList.push(this.currentConversation)
        this.activeHistoryIndex = this.chatHistoryList[0].id
        if (!this.chatHistoryList.length) this.textareaPlaceholder = '请尝试问我:你是谁?'
      getChatHistoryListByApi() {
        //  TODO 调用接口获取历史会话列表,如果历史会话不为空则跳转至第一条历史会话内容中
        getChatHistoryListApi()
          .then(res => {
            console.log('res', res)
            if (res.success && res.result && res.result.length > 0) {
              this.chatHistoryList = res.result.map(item => {
                return {
                  ...item,
                  iconVisible: false,
                  inputVisible: false,
                  deletePopVisible: false
                }
              })
              if (!this.activeHistoryIndex) this.switchToCurrentConversation(this.chatHistoryList[0])
              if (this.isDeletingBatch) this.singleHistoryCheckedChange()
            } else {
              this.chatHistoryList = []
              this.createNewConversation()
            }
          })
          .catch(err => {
            this.chatHistoryList = []
            this.createNewConversation()
            console.log('err', err)
          })
      },
      /* 创建一个新会话 */
      /* 点击新增会话后触发 */
      createNewConversation() {
        if (this.isAtNewConversation) this.$message.info('当前已是最新对话')
        if (this.isModelResponding) return // 模型回答期间禁止新增会话
        this.isAtNewConversation = true
        this.currentConversation = {
          id: randomUUID(),
          title: '',
          id: '',
          messages: [],
          stream: true,
          max_tokens: 500,
          iconVisible: false,
          inputVisible: false,
          deletePopVisible: false
          max_tokens: 500
        }
        // 新建会话时取消原先被选中的历史对话,更清晰告诉用户现在界面处于新建对话中
        if (this.activeHistoryIndex !== null) this.activeHistoryIndex = null
        this.textareaPlaceholder = '请尝试问我:你是谁?'
      },
      /* 调用接口获取当前会话记录 */
      getConversationByApi(id) {
        // TODO 根据点击的历史会话ID获取对应历史会话的对话记录列表
        getCurrentConversationApi({ id })
          .then(res => {
            console.log('currentRes', res)
            if (res.success && res.result) {
              this.currentConversation.messages = res.result.map(item => {
                return {
                  id: item.id,
                  role: item.aiType === 2 ? 'user' : 'assistant',
                  content: item.aiType === 2 ? item.problem : item.answer
                }
              })
              this.scrollToConversationContainerBottom()
              this.currentConversation.id = res.result[0].parentId
              if (this.isModelResponding) this.isModelResponding = false
              if (res.result[res.result.length - 1].aiType === 2) {
                console.log('触发向模型提问', res.result)
                this.askToLanguageModel()
              }
            }
          })
      },
      addNewConversationByApi(params) {
        this.inputQuestion = ''
        // TODO 调用后端接口保存当前问题且创建一条历史会话记录,然后再重新调用获取历史记录列表接口刷新列表
        addNewConversationApi(params)
          .then(res => {
            if (res.success) {
              switch (+params.aiType) {
                case 1:
                  this.getChatHistoryListByApi()
                  this.$message.success('会话列表记录' + res.message)
                  break
                case 2:
                  this.getConversationByApi(this.activeHistoryIndex)
                  this.$message.success('新增会话内容问题记录' + res.message)
                  break
                case 3:
                  this.getConversationByApi(this.activeHistoryIndex)
                  // this.currentConversation.messages[this.currentConversation.messages.length - 1].content += '\n' + '对话结束'
                  this.$message.success('新增会话内容答案记录' + res.message)
                  break
              }
            } else {
              this.$message.error(res.message)
            }
          })
      },
      /* 向模型提问 */
      sendQuestion(event) {
        //监测是否按下shift键
        if (!event.shiftKey) {
          event.preventDefault()
          if (this.isModelResponding) {
            this.$message.error('请等待机器人回复后再发送哦~')
            return
          }
          if (!this.inputQuestion) {
            this.$message.error('你没有输入内容哦')
            return
          }
          const params = {
            problem: this.inputQuestion,
            aiType: ''
          }
          if (this.isAtNewConversation) {
            params.aiType = 1
            params.parentId = ''
          } else {
            params.aiType = 2
            params.parentId = this.activeHistoryIndex
          }
          this.addNewConversationByApi(params)
        }
      },
      askToLanguageModel() {
        const messages = JSON.parse(JSON.stringify(this.currentConversation))
        const answer = {
          id: '',
          role: 'assistant',
          content: ''
        }
        this.currentConversation.messages.push(answer)
        let lastElement
        this.$nextTick(() => {
          const elementArr = document.querySelectorAll('.single-conversation')
          lastElement = elementArr[elementArr.length - 1]
          console.log('elementArr', elementArr)
        })
        console.log('beforeAnswerConversation', messages)
        console.log('this.currentConversation', this.currentConversation)
        this.isModelResponding = true
        // 发送POST请求到模型,获取响应流
        askToLanguageModelApi(messages)
          .then(async (response) => {
            if (!response.body) return
            const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
            // const decoder = new TextDecoder()
            // console.log(reader)
            let discontinuousJsonArray = []
            let isContinuous = null
            while (true) {
              const { value, done } = await reader.read()
              if (done) {
                const params = {
                  parentId: this.activeHistoryIndex,
                  answer: this.currentConversation.messages.find(item => !item.id && item.role === 'assistant').content,
                  aiType: 3
                }
                this.addNewConversationByApi(params)
                break
              }
              this.scrollToConversationContainerBottom()
              const objectArray = parsePack(value)
              // console.log('objectArray', objectArray)
              if (Array.isArray(objectArray) && objectArray.length > 0) {
                if (discontinuousJsonArray.length === 2) discontinuousJsonArray = []
                objectArray.forEach(json => {
                  if (!json.choices || json.choices.length === 0) {
                    return
                  }
                  const text = json.choices[0].delta.content
                  this.currentConversation.messages.find(item => !item.id && item.role === 'assistant').content += text
                })
                if (isContinuous) {
                  discontinuousJsonArray = []
                  isContinuous = null
                }
              }
            }
            // 递归找到DOM下最后一个元素节点
            function parsePack(str) {
              const pattern = /data:\s*(?!\[DONE\])(\{.*?\})\s*\n/g
              const result = []
              let match
              while ((match = pattern.exec(str)) !== null) {
                const jsonStr = match[1]
                console.log('jsonStr', jsonStr)
                try {
                  const object = JSON.parse(jsonStr)
                  if (object) result.push(object)
                } catch (err) {
                  console.log('err', err)
                }
              }
              // 此判断处理返回的不完整的数组
              if (match = pattern.exec(str) === null) {
                isContinuous = false
                // console.log('str', str)
                // 此处为屏蔽返回带有ping字符串
                if (!str.includes('ping')) {
                  discontinuousJsonArray.push(str)
                  // 判断条件为2是由于不完整数组仅经过2次返回值就可以拼接完整,但还是不应该用数字作为判断条件,以防不止2次
                  if (discontinuousJsonArray.length === 2) {
                    // console.log('discontinuousJsonArray', discontinuousJsonArray[0], '---', discontinuousJsonArray[1])
                    const discontinuousMatch = pattern.exec(discontinuousJsonArray[0] + discontinuousJsonArray[1])
                    discontinuousJsonArray = [JSON.parse(discontinuousMatch[1])]
                    isContinuous = true
                    return discontinuousJsonArray
                  }
                }
              }
              return result
            }
          })
          .catch(error => {
            console.error('请求失败:', error)
          })
      },
      /* 切换至当前点击会话 */
      switchToCurrentConversation(record, event = {}) {
        if (record.id === this.activeHistoryIndex) return // 避免重复点击
        if (event.target && event.target.type === 'checkbox') return //点击多选按钮避免传递给此事件
        if (record.inputVisible) return // 当条会话正在被修改时再次点击本条会话无反馈
        // 关闭点击编辑按钮后的输入框
        if (this.editingHistoryIndex !== null && this.editingHistoryIndex !== record.id) this.cancelEditConversationTitle(this.chatHistoryList.find(item => item.id === this.editingHistoryIndex))
        // 关闭所有确认删除弹窗
        if (this.deletingHistoryIndex !== null) this.cancelDeleteConversation(this.chatHistoryList.find(item => item.id === this.deletingHistoryIndex))
        if (this.isAtNewConversation) this.isAtNewConversation = false // 如果在创建新对话界面则将新对话界面关闭
        if (this.isModelResponding) return // 模型回答期间禁止切换会话
        this.activeHistoryIndex = record.id
        this.getConversationByApi(record.id)
      },
      /* 点击全选按钮后改变勾选后触发 */
@@ -263,7 +476,7 @@
        } else {
          this.checkedConversationIdList = []
        }
        console.log('触发全选', event.target.checked)
        console.log('触发全选', this.checkedConversationIdList)
      },
      /* 点击历史记录中多选框改变勾选后触发 */
@@ -298,6 +511,7 @@
      /* 点击红色图标批量删除按钮后触发 */
      deleteBatchConversation() {
        if (!this.checkedConversationIdList.length) return
        if (this.isModelResponding) return // 模型回答期间禁止删除会话
        this.deleteBatchPopVisible = !this.deleteBatchPopVisible
      },
@@ -326,23 +540,6 @@
        this.checkedConversationIdList = []
      },
      /* 切换至当前点击会话 */
      switchToCurrentConversation(record, event = {}) {
        if (event.target && event.target.type === 'checkbox') return //点击多选按钮避免传递给此事件
        if (record.inputVisible) return // 当条会话正在被修改时再次点击本条会话无反馈
        // 关闭点击编辑按钮后的输入框
        if (this.editingHistoryIndex !== null && this.editingHistoryIndex !== record.id) this.cancelEditConversationTitle(this.chatHistoryList.find(item => item.id === this.editingHistoryIndex))
        // 关闭所有确认删除弹窗
        if (this.deletingHistoryIndex !== null) this.cancelDeleteConversation(this.chatHistoryList.find(item => item.id === this.deletingHistoryIndex))
        this.activeHistoryIndex = record.id
        this.currentConversation = this.chatHistoryList.find(item => item.id === record.id)
        this.isAtNewConversation = false
        // 切换对话时更改提问输入框提示文字
        if (record.messages.length > 0) this.textareaPlaceholder = 'Enter发送,Shift+Enter换行'
      },
      /* 点击编辑会话标题按钮时触发 */
      editConversationTitle(record) {
        // 仅开启最后一次点击编辑按钮后的输入框
@@ -352,7 +549,7 @@
        this.editingHistoryIndex = record.id
        record.inputVisible = true
        this.editedConversationTitle = record.title
        this.editedConversationTitle = record.problem
        this.$nextTick(() => document.getElementById('edit-input').focus())
      },
@@ -365,42 +562,49 @@
        }
        // 仅可使用最后一次点击删除按钮的功能
        if (this.deletingHistoryIndex !== null && this.deletingHistoryIndex !== record.id) this.cancelDeleteConversation(this.chatHistoryList.find(item => item.id === this.deletingHistoryIndex))
        if (this.isModelResponding) return // 模型回答期间禁止删除会话
        record.deletePopVisible = !record.deletePopVisible
        this.deletingHistoryIndex = record.id
      },
      /* 确认编辑会话标题 */
      confirmEditConversationTitle(record, event) {
        // TODO 调用编辑会话接口并重新获取历史会话数据
        record.title = this.editedConversationTitle
        this.cancelEditConversationTitle(record, event)
      },
      /* 确认删除会话时触发 */
      confirmDeleteConversation(record, index) {
        this.chatHistoryList = this.chatHistoryList.filter(item => item.id !== this.deletingHistoryIndex)
        if (this.chatHistoryList.length > 0) {
          // 判断当前会话是不是要删除的会话
          if (this.activeHistoryIndex === record.id) {
            if (this.chatHistoryList[index]) {
              console.log('删除非最后一条')
              this.currentConversation = this.chatHistoryList[index]
              this.activeHistoryIndex = this.chatHistoryList[index].id
            } else {
              console.log('删除最后一条')
              this.currentConversation = this.chatHistoryList[this.chatHistoryList.length - 1]
              this.activeHistoryIndex = this.chatHistoryList[this.chatHistoryList.length - 1].id
        deleteSingleChatHistoryApi({ id: record.id })
          .then(res => {
            if (res.success) {
              if (this.chatHistoryList.length !== 1) {
                // 判断当前会话是不是要删除的会话
                console.log('record', record)
                console.log('activeHistoryIndex', this.activeHistoryIndex)
                if (this.activeHistoryIndex === record.id) {
                  if (index !== 0) {
                    console.log('删除非第一条记录')
                    this.switchToCurrentConversation(this.chatHistoryList[index - 1])
                  } else {
                    console.log('删除第一条记录')
                    this.switchToCurrentConversation(this.chatHistoryList[index + 1])
                  }
                }
              } else {
                console.log('删除最后一条记录')
                this.activeHistoryIndex = null
              }
              record.deletePopVisible = false
              this.deletingHistoryIndex = null
              this.getChatHistoryListByApi()
              this.$message.success(res.message)
            }
          }
        } else {
          console.log('删除前只有一条')
          this.activeHistoryIndex = null
          this.createNewConversation()
        }
        record.deletePopVisible = false
        this.deletingHistoryIndex = null
        this.$message.success('删除成功!')
          })
          .catch(err => {
            this.$message.error(err.message)
          })
      },
      /* 取消编辑会话标题时触发 */
@@ -414,7 +618,7 @@
      /* 取消删除会话时触发 */
      cancelDeleteConversation(record, event) {
        // 失去焦点事件时若点击的元素是确认删除按钮则不进行失去焦点事件,直接进入确认删除事件
        if (event.relatedTarget && event.relatedTarget.id === 'delete-conversation-button') return
        if (event && event.relatedTarget && event.relatedTarget.id === 'delete-conversation-button') return
        record.deletePopVisible = false
        this.deletingHistoryIndex = null
      },
@@ -424,55 +628,20 @@
        return getFileAccessHttpUrl(this.avatar())
      },
      /* 向模型提问 */
      sendQuestion(e) {
        //监测是否按下shift键
        if (!e.shiftKey) {
          e.preventDefault()
          if (this.isResponding) {
            this.$message.error('请等待机器人回复后再发送哦~')
            return
          }
          if (!this.inputQuestion) {
            this.$message.error('你没有输入内容哦')
            return
          }
          const newQuestion = {
            role: 'user',
            content: this.inputQuestion
          }
          this.currentConversation.messages.push(newQuestion)
          // 当新建对话时需先提问再讲对话加入到历史记录中
          if (this.currentConversation.messages.length === 1) {
            this.currentConversation.title = newQuestion.content.slice(0, 15)
            this.chatHistoryList.unshift(this.currentConversation)
            if (this.isDeletingBatch) this.singleHistoryCheckedChange()
            this.switchToCurrentConversation(this.currentConversation)
          }
          this.isResponding = true
          const response = {
            role: 'assistant',
            content: '这个问题我也不太清楚'
          }
          setTimeout(() => {
            this.currentConversation.messages.push(response)
            this.inputQuestion = ''
            this.isResponding = false
            this.$nextTick(() => {
              this.conversationContainer.scrollTo({ top: 9999999999999999999999999999, behavior: 'smooth' })
            })
          }, 1000)
        }
      scrollToConversationContainerBottom(scrollBehavior = 'auto') {
        this.$nextTick(() => {
          this.conversationContainer.scrollTo({
            top: this.conversationContainer.scrollHeight,
            behavior: scrollBehavior
          })
        })
      }
    }
  }
</script>
<style scoped lang="less">
  @main-container-background: rgba(255, 255, 255, .8);
  @main-container-background: rgba(255, 255, 255, .7);
  @container-border-radius: 12px;
  @container-padding: 10px;
  @single-history-edit-border: 3px solid #ABC0CC;
@@ -564,6 +733,13 @@
                margin-right: 10px;
              }
            }
            &.disable-expand {
              cursor: not-allowed;
              &:hover {
                box-shadow: none;
              }
            }
          }
          .delete-batch-container {
@@ -633,7 +809,14 @@
          flex: 1;
          overflow: auto;
          & > div {
          .ant-empty {
            height: 100%;
            display: flex;
            flex-direction: column;
            justify-content: center;
          }
          & > div:not(.ant-empty) {
            border: 3px solid transparent;
            border-radius: 10px;
            padding: 10px 20px;
@@ -714,6 +897,14 @@
                }
              }
            }
            &.disable-switch {
              cursor: not-allowed;
              &:not(.single-history-active):hover {
                background-color: transparent;
              }
            }
          }
        }
@@ -866,16 +1057,20 @@
              &.user-question {
                align-items: flex-end;
                .content {
                .conversation-content {
                  background-color: @user-question-background;
                  text-align: justify;
                  text-align-last: left;
                }
              }
              &.assistant-answer {
                align-items: flex-start;
                .content {
                .conversation-content {
                  background-color: @assistant-answer-background;
                  text-align: justify;
                  text-align-last: left;
                }
              }
@@ -889,7 +1084,7 @@
                }
              }
              .content {
              .conversation-content {
                max-width: 80%;
                box-shadow: @conversation-content-container-box-shadow;
                border-radius: @container-border-radius;