|
|
@ -3,16 +3,33 @@ import { renderMarkdown } from "@/util/markdown.ts"; |
|
|
|
import moment from "moment"; |
|
|
|
import { computed, ref } from "vue"; |
|
|
|
import { getSystemKey } from "@/system/config"; |
|
|
|
import { System } from "@/system"; |
|
|
|
import { ElMessageBox } from "element-plus"; |
|
|
|
interface Props { |
|
|
|
content: string; |
|
|
|
role?: string; |
|
|
|
link?: any; |
|
|
|
createdAt: number | string; |
|
|
|
doc: any[]; |
|
|
|
web_search: any[]; |
|
|
|
system: System; |
|
|
|
} |
|
|
|
|
|
|
|
const props = defineProps<Props>(); |
|
|
|
|
|
|
|
const getDateTime = (t:any) => { |
|
|
|
const sourceExpand = ref(false); |
|
|
|
const openFile = (filePath: string) => { |
|
|
|
props.system.openFile(filePath); |
|
|
|
}; |
|
|
|
const openlink = (item: any) => { |
|
|
|
if (!item.link) return ElMessageBox.alert( |
|
|
|
item.content, |
|
|
|
{ |
|
|
|
confirmButtonText: '确定', |
|
|
|
} |
|
|
|
) |
|
|
|
window.open(item.link) |
|
|
|
} |
|
|
|
const getDateTime = (t: any) => { |
|
|
|
return moment(t).format("MM-DD HH:mm"); |
|
|
|
}; |
|
|
|
const showLink = ref(false); |
|
|
@ -36,7 +53,7 @@ const imgList = computed<any[]>(() => { |
|
|
|
if (props.link && props.link.length > 0) { |
|
|
|
const list: any = []; |
|
|
|
const url = getSystemKey("apiUrl") + "/showimage?path="; |
|
|
|
props.link.forEach((item:any) => { |
|
|
|
props.link.forEach((item: any) => { |
|
|
|
if (item.metadata.type === "image") { |
|
|
|
list.push(url + item.metadata.file); |
|
|
|
} |
|
|
@ -47,7 +64,7 @@ const imgList = computed<any[]>(() => { |
|
|
|
}); |
|
|
|
async function showDetail(file: string, type: string) { |
|
|
|
const filePath = getSystemKey("apiUrl") + "/filedetail?path=" + file |
|
|
|
if(type != "image") { |
|
|
|
if (type != "image") { |
|
|
|
const reponse = await fetch(filePath); |
|
|
|
if (reponse.ok) { |
|
|
|
const data = await reponse.text(); |
|
|
@ -55,10 +72,10 @@ async function showDetail(file: string, type: string) { |
|
|
|
detailTxt.value = data; |
|
|
|
detailImg.value = ""; |
|
|
|
} |
|
|
|
}else{ |
|
|
|
refbox.value = true; |
|
|
|
detailTxt.value = ''; |
|
|
|
detailImg.value = filePath; |
|
|
|
} else { |
|
|
|
refbox.value = true; |
|
|
|
detailTxt.value = ''; |
|
|
|
detailImg.value = filePath; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
@ -76,7 +93,9 @@ async function showDetail(file: string, type: string) { |
|
|
|
<div v-if="props.role === 'assistant'" class="assistant align-start spacing"> |
|
|
|
<div class="assistant-avatar"> |
|
|
|
<div class="icon-container"> |
|
|
|
<el-icon><GoldMedal /></el-icon> |
|
|
|
<el-icon> |
|
|
|
<GoldMedal /> |
|
|
|
</el-icon> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="message assistant-message rounded-al"> |
|
|
@ -86,31 +105,13 @@ async function showDetail(file: string, type: string) { |
|
|
|
{{ getDateTime(props.createdAt) }} |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<el-button |
|
|
|
icon="InfoFilled" |
|
|
|
circle |
|
|
|
size="small" |
|
|
|
v-if="props.link" |
|
|
|
@click="handleFlag" |
|
|
|
/> |
|
|
|
<el-button |
|
|
|
icon="PictureFilled" |
|
|
|
circle |
|
|
|
size="small" |
|
|
|
v-if="imgList.length > 0" |
|
|
|
@click="handleLink" |
|
|
|
/> |
|
|
|
<el-button icon="InfoFilled" circle size="small" v-if="props.link" @click="handleFlag" /> |
|
|
|
<el-button icon="PictureFilled" circle size="small" v-if="imgList.length > 0" @click="handleLink" /> |
|
|
|
</div> |
|
|
|
</el-row> |
|
|
|
<el-card v-if="showLink" style="color: black"> |
|
|
|
<el-row |
|
|
|
type="flex" |
|
|
|
justify="space-between" |
|
|
|
align="middle" |
|
|
|
:gutter="24" |
|
|
|
style="margin-top: 10px" |
|
|
|
v-for="(item, key) in props.link" |
|
|
|
> |
|
|
|
<el-row type="flex" justify="space-between" align="middle" :gutter="24" style="margin-top: 10px" |
|
|
|
v-for="(item, key) in props.link"> |
|
|
|
<el-col :span="2"> |
|
|
|
<el-avatar size="small"> |
|
|
|
{{ key + 1 }} |
|
|
@ -124,35 +125,52 @@ async function showDetail(file: string, type: string) { |
|
|
|
<el-card v-if="showImg" style="color: black"> |
|
|
|
<div class="image-grid"> |
|
|
|
<div v-for="(item, index) in imgList" :key="index" class="image-item"> |
|
|
|
<el-image |
|
|
|
:src="item" |
|
|
|
alt="Image" |
|
|
|
:zoom-rate="1.2" |
|
|
|
:max-scale="7" |
|
|
|
:min-scale="0.2" |
|
|
|
:preview-src-list="imgList" |
|
|
|
:initial-index="index" |
|
|
|
fit="cover" |
|
|
|
class="image" |
|
|
|
/> |
|
|
|
<el-image :src="item" alt="Image" :zoom-rate="1.2" :max-scale="7" :min-scale="0.2" |
|
|
|
:preview-src-list="imgList" :initial-index="index" fit="cover" class="image" /> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</el-card> |
|
|
|
|
|
|
|
<!-- 参考源 --> |
|
|
|
<div class="reference-source" v-if="props.doc.length || props.web_search.length"> |
|
|
|
<div class="source-title" @click="sourceExpand = !sourceExpand" style="cursor: pointer;"> |
|
|
|
<el-button type="default" style="margin-right: 5px; width: 20px;height: 20px;" icon="Document" circle /> |
|
|
|
<span style="margin-right: 5px">{{ props.doc.length || props.web_search.length }}篇资料作为参考</span> |
|
|
|
<ArrowDown v-if="sourceExpand" class="icon"></ArrowDown> |
|
|
|
<ArrowRight v-else class="icon"></ArrowRight> |
|
|
|
</div> |
|
|
|
<!-- doucument --> |
|
|
|
<div v-if="props.doc.length"> |
|
|
|
<ul class="source-list" v-if="sourceExpand" v-for="(item, i) in props.doc" :key="i"> |
|
|
|
<li class="source-item" @click="openFile(item.file_path)"> |
|
|
|
{{ item.file_name }}</li> |
|
|
|
</ul> |
|
|
|
</div> |
|
|
|
<!-- web_search --> |
|
|
|
<div v-else> |
|
|
|
<ul class="source-list" v-if="sourceExpand" v-for="item in props.web_search" :key="item.title"> |
|
|
|
<li class="source-item" @click="openlink(item)"> |
|
|
|
<img :src="item.icon" v-if="item.icon" style="width: 15px;height: 15px; margin-right: 5px;">{{ |
|
|
|
item.title |
|
|
|
}} |
|
|
|
</li> |
|
|
|
</ul> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<!-- 用户信息 --> |
|
|
|
<div v-else-if="props.role === 'user'" class="user align-center spacing"> |
|
|
|
<div class="message-container"> |
|
|
|
<div |
|
|
|
class="message user-message rounded-xl" |
|
|
|
style="max-height: 80px; overflow-y: auto" |
|
|
|
> |
|
|
|
<div class="message user-message rounded-xl" style="max-height: 80px; overflow-y: auto"> |
|
|
|
<div>{{ props.content }}</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="avatar"> |
|
|
|
<div class="icon-container"> |
|
|
|
<el-icon><UserFilled /></el-icon> |
|
|
|
<el-icon> |
|
|
|
<UserFilled /> |
|
|
|
</el-icon> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -165,12 +183,14 @@ $win10-blue: #0078d7; |
|
|
|
$win10-light-blue: #c7e8ff; |
|
|
|
$win10-grey: #afafaf; |
|
|
|
$win10-light-grey: #f2f2f2; |
|
|
|
|
|
|
|
.message-container { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
align-items: flex-end; |
|
|
|
width: 99%; |
|
|
|
} |
|
|
|
|
|
|
|
.message { |
|
|
|
margin-top: 10px; |
|
|
|
padding: 8px; |
|
|
@ -181,9 +201,11 @@ $win10-light-grey: #f2f2f2; |
|
|
|
text-align: left; |
|
|
|
margin: 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.text-grey { |
|
|
|
color: $win10-grey; |
|
|
|
} |
|
|
|
|
|
|
|
.user-message { |
|
|
|
background-color: $win10-blue; |
|
|
|
border-radius: 12px 0 0 12px; |
|
|
@ -191,19 +213,25 @@ $win10-light-grey: #f2f2f2; |
|
|
|
font-size: 14px; |
|
|
|
color: $win10-light-blue; |
|
|
|
} |
|
|
|
|
|
|
|
.assistant-message { |
|
|
|
background-color: $win10-blue; |
|
|
|
flex: 1; |
|
|
|
background-color: #fff; |
|
|
|
border-radius: 0 12px 12px 0; |
|
|
|
font-weight: 600; |
|
|
|
font-size: 14px; |
|
|
|
color: $win10-light-blue; |
|
|
|
color: #333; |
|
|
|
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; |
|
|
|
padding: 10px 20px; |
|
|
|
} |
|
|
|
|
|
|
|
.avatar { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
width: 50px; |
|
|
|
height: 50px; |
|
|
|
} |
|
|
|
|
|
|
|
.assistant-avatar { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
@ -211,6 +239,7 @@ $win10-light-grey: #f2f2f2; |
|
|
|
height: 35px; |
|
|
|
padding-left: 10px; |
|
|
|
} |
|
|
|
|
|
|
|
.icon-container { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
@ -225,9 +254,11 @@ $win10-light-grey: #f2f2f2; |
|
|
|
.rounded-xl { |
|
|
|
border-radius: 12px 0px 12px 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.rounded-al { |
|
|
|
border-radius: 0px 12px 12px 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.align-start, |
|
|
|
.align-center { |
|
|
|
display: flex; |
|
|
@ -237,6 +268,7 @@ $win10-light-grey: #f2f2f2; |
|
|
|
.align-center { |
|
|
|
justify-content: flex-end; |
|
|
|
} |
|
|
|
|
|
|
|
.image-grid { |
|
|
|
display: grid; |
|
|
|
grid-template-columns: repeat(3, 1fr); |
|
|
@ -245,7 +277,8 @@ $win10-light-grey: #f2f2f2; |
|
|
|
|
|
|
|
.image-item { |
|
|
|
position: relative; |
|
|
|
border: 1px solid white; /* 白边效果 */ |
|
|
|
border: 1px solid white; |
|
|
|
/* 白边效果 */ |
|
|
|
overflow: hidden; |
|
|
|
} |
|
|
|
|
|
|
@ -253,4 +286,30 @@ $win10-light-grey: #f2f2f2; |
|
|
|
width: 100%; |
|
|
|
height: auto; |
|
|
|
} |
|
|
|
|
|
|
|
.reference-source { |
|
|
|
font-size: 13px; |
|
|
|
color: #9b9b9b; |
|
|
|
margin-top: 10px; |
|
|
|
|
|
|
|
.source-title { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
} |
|
|
|
|
|
|
|
.source-list { |
|
|
|
list-style: disc inside none; |
|
|
|
margin-top: 5px; |
|
|
|
|
|
|
|
.source-item { |
|
|
|
background-color: #ecebeb; |
|
|
|
height: 30px; |
|
|
|
padding: 0 10px; |
|
|
|
line-height: 30px; |
|
|
|
border-radius: 15px; |
|
|
|
cursor: pointer; |
|
|
|
overflow: hidden; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
</style> |
|
|
|