| | |
| | | <template> |
| | | <div class="flex px-4 py-6 rounded-lg relative flex-row-reverse" :key="`${msg.historyId}_${msg.role}`"> |
| | | <img class="rounded-full size-12 flex-0 ml-4" :src="roleImageMap[msg.role]" alt="" srcset="" /> |
| | | <div class="flex-auto flex justify-end"> |
| | | <div class="inline-flex flex-col"> |
| | | <div class="rounded-[6px] p-4 leading-relaxed group" :style="{ backgroundColor: 'rgb(197 224 255)' }"> |
| | | <!-- #region ====================== 用户操作按钮 ======================--> |
| | | <div v-if="msg.content?.values && !isSharePage" class="absolute flex items-center bottom-0 group invisible"> |
| | | <div class="bg-[#fff] flex items-center relative mr-4 space-x-2 flex-nowrap rounded-[6px] py-2 px-2 group-hover:visible"> |
| | | <el-tooltip effect="dark" content="复制" placement="top"> |
| | | <div class="flex items-center justify-center size-[20px]"> |
| | | <i |
| | | class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] font-medium !text-[15px] hover:!text-[18px]" |
| | | @click="copyUserClick(msg)" |
| | | <div class="flex flex-col py-6 gap-2"> |
| | | <!-- #region ====================== 附件展示 ======================--> |
| | | <div class="flex px-4 rounded-lg relative flex-row-reverse items-center" :key="`${msg.historyId}_${msg.role}`"> |
| | | <img class="rounded-full size-12 flex-0 ml-4 invisible" :src="roleImageMap[msg.role]" alt="" srcset="" /> |
| | | <div class="flex-auto flex justify-end"> |
| | | <div class="inline-flex flex-col"> |
| | | <div class="rounded-[6px] leading-relaxed group"> |
| | | <div v-if="msg.attachList?.length > 0" class="flex gap-3.5 w-full overflow-x-auto"> |
| | | <div |
| | | v-for="(item, index) in msg.attachList" |
| | | :key="index" |
| | | class="flex items-center gap-2 bg-[#e9e9e9] px-2 py-3 rounded-lg w-[220px] relative group cursor-pointer" |
| | | @click="openAttachPreview(item)" |
| | | > |
| | | <template v-if="item.type === 'file'"> |
| | | <el-image |
| | | :zoom-rate="1.2" |
| | | fit="cover" |
| | | class="w-[24px] rounded cursor-pointer" |
| | | v-if="item.model.groupType === 'image'" |
| | | :src="item.model.previewUrl" |
| | | :preview-src-list="[item.model.previewUrl]" |
| | | /> |
| | | </div> |
| | | </el-tooltip> |
| | | <el-tooltip effect="dark" content="设为常用语" placement="top"> |
| | | <div class="flex items-center justify-center size-[20px]"> |
| | | <i |
| | | class="p-2 ywifont ywicon-cubelifangti cursor-pointer hover:text-[#0284ff] text-[#000] font-[590] !text-[15px] hover:!text-[18px]" |
| | | @click="setCommonQuestionClick(msg)" |
| | | /> |
| | | </div> |
| | | </el-tooltip> |
| | | <el-tooltip effect="dark" content="分享" placement="top"> |
| | | <div class="flex items-center justify-center size-[15px]"> |
| | | <i |
| | | :class="{ 'text-[#0284ff]': msg.state === AnswerState.Unlike }" |
| | | class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]" |
| | | @click="shareClick(msg)" |
| | | /> |
| | | </div> |
| | | </el-tooltip> |
| | | <div v-else class="ywifont !text-[24px] flex-0" :class="[`ywicon-${item.icon}`, item.iconClass]"></div> |
| | | <div class="flex flex-col gap-0.5 w-full flex-auto justify-between"> |
| | | <div class="font-bold over-ellipsis w-full">{{ item.title }}</div> |
| | | <div v-if="item.model.type" class="text-info text-sm over-ellipsis w-full"> |
| | | {{ `${item.model.type ?? ''},${item.model.size ?? ''}` }} |
| | | </div> |
| | | <el-tooltip effect="dark" content="下载" placement="top"> |
| | | <span |
| | | class="group-hover:visible invisible ywifont ywicon-download2 absolute right-2 cursor-pointer" |
| | | @click.stop="downloadAttach(item)" |
| | | ></span> |
| | | </el-tooltip> |
| | | </div> |
| | | </template> |
| | | <template v-if="item.type === 'metric'"> |
| | | <div class="ywifont !text-[24px] flex-0" :class="[`ywicon-${item.icon}`, item.iconClass]"></div> |
| | | <div class="flex flex-col gap-0.5 w-full flex-auto"> |
| | | <div class="font-bold over-ellipsis w-full">{{ item.title }}</div> |
| | | <div class="text-info text-sm over-ellipsis w-full">{{ `指标,${item.model.values?.length} 条记录` }}</div> |
| | | </div> |
| | | <el-tooltip effect="dark" content="引用" placement="top"> |
| | | <span |
| | | class="group-hover:visible invisible ywifont ywicon-quote absolute right-2 cursor-pointer" |
| | | @click.stop="quoteAttach(item)" |
| | | ></span> |
| | | </el-tooltip> |
| | | </template> |
| | | <template v-if="item.type === 'table'"> |
| | | <div class="ywifont !text-[24px] flex-0" :class="[`ywicon-${item.icon}`, item.iconClass]"></div> |
| | | <div class="flex flex-col gap-0.5 w-full flex-auto"> |
| | | <div class="font-bold over-ellipsis w-full">{{ item.title }}</div> |
| | | <div class="text-info text-sm over-ellipsis w-full">{{ `业务表格,${item.model.values?.length} 条记录` }}</div> |
| | | </div> |
| | | <el-tooltip effect="dark" content="引用" placement="top"> |
| | | <span |
| | | class="group-hover:visible invisible ywifont ywicon-quote absolute right-2 cursor-pointer" |
| | | @click.stop="quoteAttach(item)" |
| | | ></span> |
| | | </el-tooltip> |
| | | </template> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <BusinessTablePreview :data="attachPreviewData" v-model="attachPreviewIsShow" /> |
| | | |
| | | <!-- #endregion --> |
| | | <!-- #region ====================== 消息内容 ======================--> |
| | | <template v-if="msg.content?.values"> |
| | | <!-- #region ====================== 回答组件 ======================--> |
| | | <component |
| | | :conclusion="msg.conclusion" |
| | | :is="answerTypeMapCom[msg.content.type]" |
| | | :data="msg.content.values" |
| | | :originData="msg" |
| | | /> |
| | | <MetricValuesPreview v-model="metricPreviewIsShow" :data="metricPreviewData" /> |
| | | |
| | | <!-- #endregion --> |
| | | |
| | | <div class="flex px-4 rounded-lg relative flex-row-reverse items-center" :key="`${msg.historyId}_${msg.role}`"> |
| | | <img class="rounded-full size-12 flex-0 ml-4" :src="roleImageMap[msg.role]" alt="" srcset="" /> |
| | | <div class="flex-auto flex justify-end"> |
| | | <div class="inline-flex flex-col"> |
| | | <div class="rounded-[6px] p-4 leading-relaxed group" :style="{ backgroundColor: 'rgb(197 224 255)' }"> |
| | | <!-- #region ====================== 用户操作按钮 ======================--> |
| | | <div v-if="msg.content?.values && !isSharePage" class="absolute flex items-center bottom-0 group invisible"> |
| | | <div class="bg-[#fff] flex items-center relative mr-4 space-x-2 flex-nowrap rounded-[6px] py-2 px-2 group-hover:visible"> |
| | | <el-tooltip effect="dark" content="复制" placement="top"> |
| | | <div class="flex items-center justify-center size-[20px]"> |
| | | <i |
| | | class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] font-medium !text-[15px] hover:!text-[18px]" |
| | | @click="copyUserClick(msg)" |
| | | /> |
| | | </div> |
| | | </el-tooltip> |
| | | <el-tooltip effect="dark" content="设为常用语" placement="top"> |
| | | <div class="flex items-center justify-center size-[20px]"> |
| | | <i |
| | | class="p-2 ywifont ywicon-cubelifangti cursor-pointer hover:text-[#0284ff] text-[#000] font-[590] !text-[15px] hover:!text-[18px]" |
| | | @click="setCommonQuestionClick(msg)" |
| | | /> |
| | | </div> |
| | | </el-tooltip> |
| | | <el-tooltip effect="dark" content="分享" placement="top"> |
| | | <div class="flex items-center justify-center size-[15px]"> |
| | | <i |
| | | :class="{ 'text-[#0284ff]': msg.state === AnswerState.Unlike }" |
| | | class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]" |
| | | @click="shareClick(msg)" |
| | | /> |
| | | </div> |
| | | </el-tooltip> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- #endregion --> |
| | | </template> |
| | | <!-- #region ====================== 消息内容 ======================--> |
| | | <template v-if="msg.content?.values"> |
| | | <!-- #region ====================== 回答组件 ======================--> |
| | | <component |
| | | :conclusion="msg.conclusion" |
| | | :is="answerTypeMapCom[msg.content.type]" |
| | | :data="msg.content.values" |
| | | :originData="msg" |
| | | /> |
| | | |
| | | <!-- #endregion --> |
| | | <!-- #endregion --> |
| | | </template> |
| | | |
| | | <!-- #endregion --> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup lang="ts" name="UserMsg"> |
| | | import { useClipboard } from '@vueuse/core'; |
| | | import { ElMessage } from 'element-plus'; |
| | | import { AnswerState, answerTypeMapCom, roleImageMap, type ChatMessage } from '../model/types'; |
| | | import { isSharePage } from '/@/stores/chatRoom'; |
| | | import { onClickOutside, useClipboard } from '@vueuse/core'; |
| | | import { onActivated, ref } from 'vue'; |
| | | import BusinessTablePreview from '../components/playBar/businessTablePreview/index.vue'; |
| | | import { Attach } from '../components/playBar/hook/useAttach'; |
| | | import MetricValuesPreview from '../components/playBar/metricValues/MetricValuesPreview.vue'; |
| | | |
| | | import emitter from '/@/utils/mitt'; |
| | | import { downloadFileByPost } from '/@/api/file'; |
| | | |
| | | const emit = defineEmits<{ |
| | | (event: 'copyMsg', msgObj: ChatMessage): void; |
| | |
| | | const shareClick = (msg) => { |
| | | emit('shareClick', msg); |
| | | }; |
| | | |
| | | //#region ====================== 指标附件预览 ====================== |
| | | const metricPreviewIsShow = ref(false); |
| | | const metricPreviewData = ref<Attach>(); |
| | | |
| | | //#endregion |
| | | //#region ====================== 附件预览 ====================== |
| | | const attachPreviewIsShow = ref(false); |
| | | const attachPreviewData = ref<Attach>(); |
| | | const openAttachPreview = (item: Attach) => { |
| | | if (item.type === 'file') { |
| | | openFileContent(item); |
| | | } else if (item.type === 'table') { |
| | | attachPreviewIsShow.value = true; |
| | | attachPreviewData.value = item; |
| | | } else if (item.type === 'metric') { |
| | | metricPreviewIsShow.value = true; |
| | | metricPreviewData.value = item; |
| | | } |
| | | }; |
| | | //#endregion |
| | | |
| | | //#region ====================== 附件引用 ====================== |
| | | const quoteAttach = (item: Attach) => { |
| | | emitter.emit('quoteAttach', item); |
| | | }; |
| | | //#endregion |
| | | |
| | | //#region ====================== 查看文件文本内容 ====================== |
| | | const openFileContent = (item: Attach) => { |
| | | emitter.emit('setFileContent', { |
| | | title: item.title, |
| | | content: item.model?.file_content ?? '', |
| | | }); |
| | | }; |
| | | //#endregion |
| | | |
| | | //#region ====================== 附件下载 ====================== |
| | | const downloadAttach = async (item: Attach) => { |
| | | const fileId = item.model?.file_id; |
| | | if (!fileId) return; |
| | | |
| | | const res = await downloadFileByPost({ |
| | | file_id: fileId, |
| | | }); |
| | | |
| | | const url = window.URL.createObjectURL(res as any); |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.download = item.title || 'download'; // Use item title or fallback name |
| | | document.body.appendChild(link); |
| | | link.click(); |
| | | document.body.removeChild(link); |
| | | window.URL.revokeObjectURL(url); |
| | | }; |
| | | //#endregion |
| | | </script> |
| | | <style scoped lang="scss"></style> |