New file |
| | |
| | | <template> |
| | | <div :style="{ height: heightrg }"> |
| | | <div style="padding: 10px"> |
| | | <a-avatar :style="{ backgroundColor: '#3370ff' }"> |
| | | <img |
| | | :style="{ width: '100%' }" |
| | | alt="dessert" |
| | | src="https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a20012a2d4d5b9db43dfc6a01fe508c0.png~tplv-uwbnlip3yd-webp.webp" |
| | | /> |
| | | </a-avatar> |
| | | 调试预览 |
| | | </div> |
| | | <a-divider style="margin: 0; margin-left: 10px" /> |
| | | <a-scrollbar |
| | | ref="scrollbar" |
| | | id="home" |
| | | class="chat-list" |
| | | style="width: 90%; overflow: auto; height: 70vh; margin: 0px auto" |
| | | > |
| | | <div class="chat-item" v-for="sessionDetail in sessionDetailList"> |
| | | <a-comment |
| | | v-if="sessionDetail.role === 'user'" |
| | | avatar="https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp" |
| | | > |
| | | <template #content> |
| | | <div :class="{ light: theme === 'light' }">{{ |
| | | sessionDetail.content |
| | | }}</div> |
| | | </template> |
| | | </a-comment> |
| | | <a-comment |
| | | v-else-if="sessionDetail.role === 'assistant'" |
| | | avatar="https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/9eeb1800d9b78349b24682c3518ac4a3.png~tplv-uwbnlip3yd-webp.webp" |
| | | > |
| | | <template #content> |
| | | <a-card |
| | | class="chat-item-answer" |
| | | style="background-color: rgba(63, 64, 79, 1)" |
| | | > |
| | | <div :class="{ light: theme === 'light' }">{{ |
| | | sessionDetail.content |
| | | }}</div> |
| | | </a-card> |
| | | </template> |
| | | </a-comment> |
| | | <a-comment |
| | | v-else-if="sessionDetail.role === 'last'" |
| | | avatar="https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/9eeb1800d9b78349b24682c3518ac4a3.png~tplv-uwbnlip3yd-webp.webp" |
| | | > |
| | | <template #content> |
| | | <a-textarea |
| | | readonly |
| | | auto-size |
| | | v-model="displayedText" |
| | | class="chat-item-answer" |
| | | style="background-color: rgba(63, 64, 79, 1)" |
| | | > |
| | | </a-textarea> |
| | | </template> |
| | | </a-comment> |
| | | </div> |
| | | </a-scrollbar> |
| | | <div class="bottom"> |
| | | <div class="input"> |
| | | <a-input |
| | | v-model="inputMsg" |
| | | @keydown.enter="sendMessage" |
| | | placeholder="输入您想了解的内容,按Enter发送" |
| | | > |
| | | <template #suffix> |
| | | <icon-send style="cursor: pointer" @click="sendMessage" /> |
| | | </template> </a-input |
| | | ></div> |
| | | <div class="text">内容由AI生成,仅供参考</div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { |
| | | IconMoreVertical, |
| | | IconQuestionCircleFill, |
| | | IconPoweroff, |
| | | IconCommon, |
| | | IconSend, |
| | | } from '@arco-design/web-vue/es/icon'; |
| | | import img1 from '@/assets/images/u64.png'; |
| | | import img2 from '@/assets/images/u69.png'; |
| | | import img3 from '@/assets/images/u74.png'; |
| | | import { ref, onMounted, computed, reactive, nextTick, watch } from 'vue'; |
| | | import { useUserStore, useAppStore } from '@/store'; |
| | | import { |
| | | sessionListApi, |
| | | deleteSessionApi, |
| | | getSessionDetailsApi, |
| | | chatApi, |
| | | } from '@/api/session'; |
| | | import { Message } from '@arco-design/web-vue'; |
| | | const userStore = useUserStore(); |
| | | const appStore = useAppStore(); |
| | | const theme = computed(() => { |
| | | return appStore.theme; |
| | | }); |
| | | const heightrg = ref('calc(100vh - 100px)'); |
| | | const sessionList = ref([]); //会话列表 |
| | | const sessionDetailList = ref([]); //根据会话id出来的会话详情 |
| | | const activeSessionId = ref(''); |
| | | const inputMsg = ref(''); |
| | | const scrollbar = ref(null); |
| | | |
| | | const currIndex = ref(0); |
| | | const displayedText = ref(''); // 正在显示的文字 |
| | | let timer: number | null = null; |
| | | const streamStr = ref(''); |
| | | const modalObj = reactive({ add: false }); |
| | | //查询会话列表 |
| | | const querySessionList = async () => { |
| | | const { code, data } = await sessionListApi(); |
| | | if (code === 200) { |
| | | sessionList.value = data; |
| | | if (Array.isArray(data) && data.length > 0) { |
| | | activeSessionId.value = data[1].id; |
| | | const res = await getSessionDetailsApi(data[0].id); |
| | | if (res.code === 200) { |
| | | sessionDetailList.value = res.data.message; |
| | | refreshScroll(); |
| | | } |
| | | } |
| | | } else { |
| | | Message.warning('查询失败'); |
| | | } |
| | | }; |
| | | //根据会话id删除会话 |
| | | const deleteSession = async (session) => { |
| | | const { code } = await deleteSessionApi([session.id]); |
| | | if (code === 200) { |
| | | Message.success('删除成功'); |
| | | querySessionList(); |
| | | } |
| | | }; |
| | | // eslint-disable-next-line prettier/prettier |
| | | // 新增会话之后刷新会话列表 |
| | | const addSession = () => { |
| | | querySessionList(); |
| | | }; |
| | | // 初始化数据 |
| | | const initData = () => { |
| | | querySessionList(); |
| | | }; |
| | | // 获取登录信息 |
| | | const userName = computed(() => { |
| | | return userStore.name; |
| | | }); |
| | | const avatar = computed(() => { |
| | | return userStore.avatar; |
| | | }); |
| | | const refreshScroll = () => { |
| | | nextTick(() => { |
| | | const container = document.getElementById('home'); |
| | | scrollbar.value.scrollTop(container.scrollHeight); |
| | | }); |
| | | }; |
| | | // 根据会话id 查询会话详情 |
| | | const querySessionDetail = async (session) => { |
| | | activeSessionId.value = session.id; |
| | | const { code, data } = await getSessionDetailsApi(session.id); |
| | | if (code === 200) { |
| | | sessionDetailList.value = data.message; |
| | | refreshScroll(); //刷新滚动条位置 |
| | | } |
| | | }; |
| | | const sendMessage = async () => { |
| | | if (inputMsg.value) { |
| | | const { code, data } = await chatApi({ |
| | | conversation_id: activeSessionId.value, |
| | | messages: inputMsg.value, |
| | | }); |
| | | const res = await getSessionDetailsApi(activeSessionId.value); |
| | | if (res.code === 200) { |
| | | sessionDetailList.value = res.data.message.map((item, index) => { |
| | | if (index === res.data.message.length - 1) { |
| | | item.role = 'last'; |
| | | displayedText.value = ''; |
| | | currIndex.value = 0; |
| | | streamStr.value = item.content; |
| | | startDisplayStr(); |
| | | } |
| | | return item; |
| | | }); |
| | | refreshScroll(); |
| | | } |
| | | inputMsg.value = ''; |
| | | } else { |
| | | Message.warning('消息不能为空'); |
| | | } |
| | | }; |
| | | onMounted(() => { |
| | | initData(); |
| | | }); |
| | | //文字动态输出 |
| | | const startDisplayStr = () => { |
| | | if (timer) { |
| | | clearTimeout(timer!); |
| | | } |
| | | const res = streamStr.value; |
| | | // 将数组中的字符串拼接起来 |
| | | if (currIndex.value < res.length) { |
| | | displayedText.value += res[currIndex.value]; |
| | | currIndex.value++; |
| | | setTimeout(startDisplayStr, 100); |
| | | } else { |
| | | clearTimeout(timer!); |
| | | timer = null; |
| | | } |
| | | }; |
| | | watch( |
| | | () => scrollbar.value, |
| | | (newScroll, oldScroll) => { |
| | | if (newScroll) { |
| | | // 获取a-scroll的高度 |
| | | const height = newScroll.$el.offsetHeight; |
| | | console.log('a-scroll height changed to:', height); |
| | | } |
| | | }, |
| | | { deep: true } |
| | | ); |
| | | </script> |
| | | |
| | | <style scoped lang="less"> |
| | | .container { |
| | | width: 100%; |
| | | display: flex; |
| | | } |
| | | |
| | | .bottom { |
| | | width: 100%; |
| | | position: absolute; |
| | | bottom: 40px; |
| | | left: 0; |
| | | |
| | | .input { |
| | | margin-left: 20%; |
| | | width: 60%; |
| | | } |
| | | |
| | | .text { |
| | | margin-left: 40%; |
| | | font-size: 12px; |
| | | color: lightgrey; |
| | | line-height: 40px; |
| | | } |
| | | } |
| | | </style> |