Browse Source

add: 添加参考资料

master
tiantian 5 months ago
parent
commit
4b8cb1b188
  1. 16
      frontend/src/components/localchat/AiChatMain.vue
  2. 159
      frontend/src/components/localchat/AiChatMessage.vue

16
frontend/src/components/localchat/AiChatMain.vue

@ -6,7 +6,7 @@ import { notifyError } from "@/util/msg.ts";
import { ElScrollbar } from "element-plus";
import { getSystemConfig } from "@/system/config";
import { Vue3Lottie } from "vue3-lottie";
import { BrowserWindow } from "@/system";
import { BrowserWindow, useSystem } from "@/system";
import { isMobileDevice } from "@/util/device";
const chatStore = useAiChatStore();
const modelStore = useModelStore();
@ -22,6 +22,7 @@ const messageInnerRef = ref<HTMLDivElement>();
// User Input Message
const userMessage = ref("");
const knowledgeId = ref(0)
const system = useSystem()
const promptMessage = computed(() => {
return [
{
@ -132,7 +133,7 @@ const createCompletion = async () => {
};
const chatConfig = modelStore.chatConfig.chat;
const knowledgeId = chatStore.chatInfo.knowledgeId*1;
const knowledgeId = chatStore.chatInfo.knowledgeId * 1;
let postMsg: any = {
messages: requestMessages.value,
model: chatStore.chatInfo.model,
@ -182,11 +183,17 @@ const createCompletion = async () => {
return;
}
const res = await completion.json();
//console.log(res)
console.log(res)
if (res && res.choices && res.choices.length > 0) {
if (res.choices[0].message.content) {
const msg = res.choices[0].message.content;
saveMessage.content = msg;
if (res.documents && res.documents.length > 0) {
saveMessage.doc = res.documents;
}
if (res.web_search && res.web_search.length > 0) {
saveMessage.web_search = res.web_search;
}
chatStore.messageList.push(saveMessage);
await chatStore.addMessages(chatStore.activeId, saveMessage);
}
@ -275,7 +282,8 @@ const uploadImage = async (event: any) => {
<el-scrollbar v-if="chatStore.messageList.length > 0" class="message-container" ref="messageContainerRef">
<div ref="messageInnerRef">
<ai-chat-message v-for="message in chatStore.messageList" :key="message.messageId" :content="message.content"
:link="message.link" :role="message.role" :createdAt="message.createdAt" />
:link="message.link" :role="message.role" :createdAt="message.createdAt" :doc="message.doc || []"
:web_search="message.web_search || []" :system="system" />
</div>
</el-scrollbar>
<div class="no-message-container" v-else>

159
frontend/src/components/localchat/AiChatMessage.vue

@ -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>

Loading…
Cancel
Save