2025-06-25 16:04:02 +08:00

287 lines
6.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="chat" :class="[inversion === 'user' ? 'self' : 'chatgpt']" v-if="getText || (props.presetQuestion && props.presetQuestion.length>0)">
<div class="avatar">
<img v-if="inversion === 'user'" :src="avatar()" />
<img v-else :src="getAiImg()" />
</div>
<div class="content">
<p class="date">
<span v-if="inversion === 'ai'" style="margin-right: 10px">{{appData.name || 'AI助手'}}</span>
<span>{{ dateTime }}</span>
</p>
<div v-if="inversion === 'user' && images && images.length>0" class="images">
<div v-for="(item,index) in images" :key="index" class="image" @click="handlePreview(item)">
<img :src="getImageUrl(item)"/>
</div>
</div>
<div v-if="inversion === 'ai' && retrievalText && loading" class="retrieval">
{{retrievalText}}
</div>
<div v-if="inversion === 'ai' && isCard" class="card">
<a-row>
<a-col :xl="6" :lg="8" :md="10" :sm="24" style="flex:1" v-for="item in getCardList()">
<a-card class="ai-card" @click="aiCardHandleClick(item.linkUrl)">
<div class="ai-card-title">{{item.productName}}</div>
<div class="ai-card-img">
<img :src="item.productImage">
</div>
<span class="ai-card-desc">{{item.descr}}</span>
</a-card>
</a-col>
</a-row>
</div>
<div class="msgArea" v-if="!isCard">
<chatText :text="text" :inversion="inversion" :error="error" :loading="loading" :referenceKnowledge="referenceKnowledge"></chatText>
</div>
<div v-if="presetQuestion" v-for="item in presetQuestion" class="question" @click="presetQuestionClick(item.descr)">
<span>{{item.descr}}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import chatText from './chatText.vue';
import defaultAvatar from "@/assets/images/ai/avatar.jpg";
import { useUserStore } from '/@/store/modules/user';
import defaultImg from '../img/ailogo.png';
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading','appData','presetQuestion','images','retrievalText', 'referenceKnowledge']);
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { createImgPreview } from "@/components/Preview";
import { computed } from "vue";
const getText = computed(()=>{
let text = props.text || props.retrievalText;
if(text){
text = text.trim();
}
return text;
})
const isCard = computed(() => {
let text = props.text;
if (text && text.indexOf('::card::') != -1) {
return true;
}
return false;
});
const { userInfo } = useUserStore();
const avatar = () => {
return getFileAccessHttpUrl(userInfo?.avatar) || defaultAvatar;
};
const emit = defineEmits(['send']);
const getAiImg = () => {
return getFileAccessHttpUrl(props.appData?.icon) || defaultImg;
};
/**
* 预设问题点击事件
*
*/
function presetQuestionClick(descr) {
emit("send",descr)
}
/**
* 获取图片
*
* @param item
*/
function getImageUrl(item) {
let url = item;
if(item.hasOwnProperty('url')){
url = item.url;
}
if(item.hasOwnProperty('base64Data') && item.base64Data){
return item.base64Data;
}
return getFileAccessHttpUrl(url);
}
/**
* 图片预览
* @param url
*/
function handlePreview(url){
const onImgLoad = ({ index, url, dom }) => {
console.log(`${index + 1}张图片已加载URL为${url}`, dom);
};
let imageList = [getImageUrl(url)];
createImgPreview({ imageList: imageList, defaultWidth: 700, rememberState: true, onImgLoad });
}
/**
* 获取卡片列表
*/
function getCardList() {
let text = props.text;
let card = text.replace('::card::', '').replace(/\s+/g, '');
try {
return JSON.parse(card);
} catch (e) {
console.log(e)
return '';
}
}
/**
* ai卡片点击事件
* @param url
*/
function aiCardHandleClick(url){
window.open(url,'_blank');
}
</script>
<style lang="less" scoped>
.chat {
display: flex;
margin-bottom: 1.5rem;
&.self {
flex-direction: row-reverse;
.avatar {
margin-right: 0;
margin-left: 10px;
}
.msgArea {
flex-direction: row-reverse;
}
.date {
text-align: right;
}
}
}
.avatar {
flex: none;
margin-right: 10px;
img {
width: 34px;
height: 34px;
border-radius: 50%;
overflow: hidden;
}
svg {
font-size: 28px;
}
}
.content {
width: 90%;
.date {
color: #b4bbc4;
font-size: 0.75rem;
margin-bottom: 10px;
}
.msgArea {
display: flex;
}
}
.question{
margin-top: 10px;
border-radius: 0.375rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
background-color: #ffffff;
font-size: 0.875rem;
line-height: 1.25rem;
cursor: pointer;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px #e6e6e6;
}
.images{
margin-bottom: 10px;
flex-wrap: wrap;
display: flex;
gap: 10px;
justify-content: end;
.image{
width: 120px;
height: 80px;
cursor: pointer;
img{
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 4px;
}
}
}
.retrieval,
.card {
background-color: #f4f6f8;
font-size: 0.875rem;
line-height: 1.25rem;
border-radius: 0.375rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.retrieval:after{
animation: blink 1s steps(5, start) infinite;
color: #000;
content: '_';
font-weight: 700;
margin-left: 3px;
vertical-align: baseline;
}
.card{
width: 100%;
background-color: unset;
}
.ai-card{
width: 98%;
height: 100%;
cursor: pointer;
.ai-card-title{
width: 100%;
line-height: 20px;
letter-spacing: 0;
white-space: pre-line;
overflow: hidden;
display: -webkit-box;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
font-weight: 600;
font-size: 18px;
text-align: left;
color: #191919;
-webkit-line-clamp: 1;
}
.ai-card-img{
margin-top: 10px;
background-color: transparent;
border-radius: 8px;
display: flex;
width: 100%;
height: max-content;
}
.ai-card-desc{
margin-top: 10px;
width: 100%;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: 0;
white-space: pre-line;
-webkit-box-orient: vertical;
overflow: hidden;
display: -webkit-box;
text-overflow: ellipsis;
text-align: left;
color: #666f;
-webkit-line-clamp: 3;
}
}
@media (max-width: 600px) {
.content{
width: 100%;
}
}
</style>