| | |
| | | <div class="chat-list" v-for="(item, index) in list" :key="index"> |
| | | <!-- 靠左 message:system、assistant 类型 --> |
| | | <div class="left-message message-item" v-if="item.type !== 'user'"> |
| | | <div class="avatar"> |
| | | <el-avatar :src="roleAvatar" /> |
| | | </div> |
| | | <div class="message"> |
| | | <div class="left-text-container" ref="markdownViewRef"> |
| | | <MarkdownView class="left-text" :content="item.content" /> |
| | | <div> |
| | | <el-text class="time">{{ formatDate(item.createTime) }}</el-text> |
| | | </div> |
| | | <div v-if="item.thinkingFlag" class="left-text-container-thinking" ref="markdownViewRef"> |
| | | <MarkdownView v-if="item.thinking" class="left-text thinking" :content="item.thinking" /> |
| | | <MarkdownView v-else class="left-text thinking" :content="item.content" /> |
| | | </div> |
| | | <div v-else-if="item.thinking" class="left-text-container-thinking" ref="markdownViewRef"> |
| | | <MarkdownView class="left-text thinking" :content="item.thinking" /> |
| | | </div> |
| | | <div class="left-text-container-conclusion" ref="markdownViewRef"> |
| | | <MarkdownView class="left-text" :content="item.conclusion" /> |
| | | </div> |
| | | <div class="left-btns"> |
| | | <el-button class="btn-cus" link @click="copyContent(item.content)"> |
| | | <img class="btn-image" src="@/assets/ai/zhuanlu/copy.png" /> |
| | | </el-button> |
| | | <el-button v-if="item.id > 0" class="btn-cus" link @click="onDelete(item.id)"> |
| | | <img class="btn-image h-17px" src="@/assets/ai/zhuanlu/delete.png" /> |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 靠右 message:user 类型 --> |
| | | <div class="right-message message-item" v-if="item.type === 'user'"> |
| | | <div class="avatar"> |
| | | <el-avatar :src="userAvatar" /> |
| | | </div> |
| | | <div class="message"> |
| | | <div> |
| | | <el-text class="time">{{ formatDate(item.createTime) }}</el-text> |
| | | </div> |
| | | <div class="right-text-container"> |
| | | <div class="right-text">{{ item.content }}</div> |
| | | </div> |
| | | <div class="right-btns"> |
| | | <el-button class="btn-cus" link @click="copyContent(item.content)"> |
| | | <img class="btn-image" src="@/assets/ai/zhuanlu/copy.png" /> |
| | | </el-button> |
| | | <el-button class="btn-cus" link @click="onDelete(item.id)"> |
| | | <img class="btn-image h-17px mr-12px" src="@/assets/ai/zhuanlu/delete.png" /> |
| | | </el-button> |
| | | <el-button class="btn-cus" link @click="onRefresh(item)"> |
| | | <img class="btn-image h-17px mr-12px" src="@/assets/ai/zhuanlu/refresh.png" /> |
| | | </el-button> |
| | | <el-button class="btn-cus" link @click="onEdit(item)"> |
| | | <img class="btn-image h-17px mr-12px" src="@/assets/ai/zhuanlu/edit.png" /> |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </template> |
| | | <script setup lang="ts"> |
| | | import { PropType } from 'vue' |
| | | import { formatDate } from '@/utils/formatTime' |
| | | import MarkdownView from '@/components/MarkdownView/index.vue' |
| | | import { useClipboard } from '@vueuse/core' |
| | | import { ArrowDownBold} from '@element-plus/icons-vue' |
| | | import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message' |
| | | import { ChatConversationVO } from '@/api/ai/chat/conversation' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import userAvatarDefaultImg from '@/assets/ai/zhuanlu/user.png' |
| | | import roleAvatarDefaultImg from '@/assets/ai/zhuanlu/assistant.png' |
| | | |
| | | const message = useMessage() // 消息弹窗 |
| | | const { copy } = useClipboard() // 初始化 copy 到粘贴板 |
| | |
| | | // 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方) |
| | | const messageContainer: any = ref(null) |
| | | const isScrolling = ref(false) //用于判断用户是否在滚动 |
| | | |
| | | const userAvatar = computed(() => userAvatarDefaultImg) |
| | | // const userAvatar = computed(() => userStore.user.avatar || userAvatarDefaultImg) |
| | | const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg) |
| | | |
| | | // 定义 props |
| | | const props = defineProps({ |
| | |
| | | /** 回到底部 */ |
| | | const handleGoBottom = async () => { |
| | | const scrollContainer = messageContainer.value |
| | | console.log(scrollContainer.scrollHeight) |
| | | scrollContainer.scrollTop = scrollContainer.scrollHeight |
| | | } |
| | | |
| | | /** 回到顶部 */ |
| | | const handlerGoTop = async () => { |
| | | const scrollContainer = messageContainer.value |
| | | console.log(scrollContainer.scrollHeight) |
| | | scrollContainer.scrollTop = 0 |
| | | } |
| | | |
| | | defineExpose({ scrollToBottom, handlerGoTop }) // 提供方法给 parent 调用 |
| | | defineExpose({ scrollToBottom, handlerGoTop, handleGoBottom }) // 提供方法给 parent 调用 |
| | | |
| | | // ============ 处理消息操作 ============== |
| | | |
| | |
| | | /** 删除 */ |
| | | const onDelete = async (id) => { |
| | | // 删除 message |
| | | await ChatMessageApi.deleteEnergyChatMessage(id) |
| | | await ChatMessageApi.deleteChatMessage(id) |
| | | message.success('删除成功!') |
| | | // 回调 |
| | | emits('onDeleteSuccess') |
| | |
| | | flex-direction: row; |
| | | } |
| | | |
| | | .right-message { |
| | | display: flex; |
| | | flex-direction: row-reverse; |
| | | justify-content: flex-start; |
| | | } |
| | | |
| | | .message { |
| | | display: flex; |
| | | flex-direction: column; |
| | | text-align: left; |
| | | height: 462px; |
| | | margin: 0 15px; |
| | | |
| | | .left-text-container { |
| | | width: 855px; |
| | | height: 462px; |
| | | .time { |
| | | text-align: left; |
| | | line-height: 30px; |
| | | } |
| | | |
| | | .left-text-container-thinking { |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | |
| | | border-radius: 4px 4px 4px 4px; |
| | | border-left: 1px solid #73C4FF; |
| | | padding: 10px 10px 5px 10px; |
| | | |
| | | .left-text { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: rgba(219,238,255,0.6); |
| | | color: rgba(219,238,255,0.5); |
| | | font-size: 0.85rem; |
| | | } |
| | | } |
| | | |
| | | .left-text-container-conclusion { |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow-wrap: break-word; |
| | | background: rgba(115,196,255,0); |
| | | border-radius: 4px 4px 4px 4px; |
| | | padding: 20px 10px 5px 0; |
| | | .left-text { |
| | | color: rgba(219,238,255,0.8); |
| | | font-size: 1rem; |
| | | } |
| | | } |
| | | |
| | | .right-text-container { |
| | | display: flex; |
| | | flex-direction: row-reverse; |
| | | |
| | | .right-text { |
| | | font-size: 0.95rem; |
| | | color: #DBEEFF; |
| | | display: inline; |
| | | background: rgba(40,139,255,0.1); |
| | | box-shadow: 0 0 0 1px rgba(40,139,255,0.3); |
| | | border-radius: 10px; |
| | | padding: 10px; |
| | | width: auto; |
| | | overflow-wrap: break-word; |
| | | white-space: pre-wrap; |
| | | } |
| | | } |
| | | |
| | | .left-btns { |
| | | display: flex; |
| | | flex-direction: row; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .right-btns { |
| | | display: flex; |
| | | flex-direction: row-reverse; |
| | | margin-top: 8px; |
| | | } |
| | | } |
| | | |
| | | // 复制、删除按钮 |