kefu/knowledge/templates/qdrant.html

583 lines
23 KiB
HTML
Raw Permalink Normal View History

2024-12-10 02:50:12 +00:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>控制面板</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.15.13/theme-chalk/index.min.css">
<script src="https://cdn.staticfile.org/vue/2.6.11/vue.min.js"></script>
<script src="https://cdn.staticfile.org/element-ui/2.15.13/index.js"></script>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
<style>
body{
font-size: 14px;
}
a{
color: #409EFF;
text-decoration: none;
cursor: pointer;
}
.cardBox{
display: flex;
flex-wrap: wrap;
}
.box-card {
width: 520px;
margin: 10px;
}
.cardBox .item {
margin-bottom: 18px;
max-height: 120px;
line-height: 23px;
overflow-y: auto;
}
.chatpdfBox{
height: 900px;
display: flex;
flex-direction: column;
flex: 1;
background: linear-gradient(to bottom right,#dbe6fb, #f3f4f8);
background-size: cover;
background-attachment: fixed;
}
.chatpdfHeader{
font-size: 18px;
padding: 10px;
text-align: center;
width: 100%;
}
.chatpdfLine{
flex: 1;
width: 100%;
overflow-y: auto;
}
.chatpdfLine h1{
color: #111111;
text-align: center;
margin-top: 80px;
margin-bottom: 20px;
font-size: 36px;
}
.chatpdfLine h2{
color: #1e1e1e;
text-align: center;
font-size: 20px;
font-weight: 400;
}
.chatpdfLineScroll{
max-width: 1000px;
margin: 0 auto;
}
.chatpdfRow{
margin: 20px 10px;
display: flex;
}
.chatpdfAsk{
justify-content: flex-end;
}
.chatpdfContent{
line-height: 23px;
display: inline-block;
border-radius: 8px;
padding: 12px 15px;
max-width: 700px;
background: rgba(255, 255, 255, 0.6);
font-size: 14px;
box-shadow: 0px 0.3px 0.9px rgba(0, 0, 0, 0.12), 0px 1.6px 3.6px rgba(0, 0, 0, 0.16);
}
.chatpdfAsk .chatpdfContent{
background: linear-gradient(90deg, #2870EA 10.79%, #1B4AEF 87.08%);;
color: #fff;
}
.chatpdfContent pre{
padding: 10px;
}
.chatpdfArea{
display: flex;
margin-bottom: 10px;
max-width: 1000px;
margin: 0 auto;
width: 98%;
margin-bottom: 15px;
transition: all 0.3s,height 0s;
}
.chatpdfArea textarea{
flex: 1;
border: none;
resize: none;
outline: none;
padding: 0px 5px;
height: 40px;
line-height: 35px;
color: #404040;
border-radius: 10px;
box-shadow: 0px 0.3px 0.9px rgba(0, 0, 0, 0.12), 0px 1.6px 3.6px rgba(0, 0, 0, 0.08);
}
.chatpdfArea:hover{
border-color: #4096ff;
}
.chatpdfArea button{
height: 40px;
color: #fff;
background: linear-gradient(90deg, #1B4AEF 10.79%, #2870EA 87.08%);
box-shadow: 0 2px 0 rgba(5, 145, 255, 0.1);
border: none;
padding: 0 20px;
border-radius: 15px;
cursor: pointer;
box-shadow: 0px 0.3px 0.9px rgba(0, 0, 0, 0.12), 0px 1.6px 3.6px rgba(0, 0, 0, 0.08);
margin-right: 10px;
}
.chatpdfArea button:hover{
background-color: #388aff;
}
</style>
</head>
<body style="padding: 10px">
<div id="app" style="display: flex;width: 100%">
<template>
<el-tabs v-model="activeTab" tab-position="left" @tab-click="" style="width: 100%">
<el-tab-pane label="批量上传">
<el-descriptions style="font-size: 14px;width: 1000px" direction="vertical" :column="1" border>
<el-descriptions-item label="上传TXT文档">
<el-upload
action="/{{.collectName}}/uploadDoc"
:on-success="uploadDocSuccess"
:on-error="uploadError"
:before-upload="beforeUpload"
>
<el-button type="primary" icon="el-icon-upload">上传 txt</el-button>
</el-upload>
</el-descriptions-item>
<el-descriptions-item label="上传Word文档注意只支持.docx">
<el-upload
action="/{{.collectName}}/uploadDoc"
:on-success="uploadDocSuccess"
:on-error="uploadError"
:before-upload="beforeUpload"
>
<el-button type="primary" icon="el-icon-upload">上传docx</el-button>
</el-upload>
</el-descriptions-item>
<el-descriptions-item label="上传Excel每一行为一条向量记录">
<el-upload
action="/{{.collectName}}/uploadDoc"
:on-success="uploadDocSuccess"
:on-error="uploadError"
:before-upload="beforeUpload"
>
<el-button type="primary" icon="el-icon-upload">上传xlsx</el-button>
</el-upload>
</el-descriptions-item>
<el-descriptions-item label="上传PDF不能传扫描图片后生成的PDF读不出数据">
<el-upload
action="/{{.collectName}}/uploadDoc"
:on-success="uploadDocSuccess"
:on-error="uploadError"
:before-upload="beforeUpload"
>
<el-button type="primary" icon="el-icon-upload">上传pdf</el-button>
</el-upload>
</el-descriptions-item>
</el-descriptions>
<el-table
:data="fileList"
border
style="width: 100%;margin-top: 10px">
<el-table-column
prop="file_name"
label="文件名">
<template slot-scope="scope">
<a @click="activeTab='tabList';fileId=scope.row.id;getAllKnowledge()"><{scope.row.file_name}></a>
</template>
</el-table-column>
<el-table-column
prop="created_at"
label="上传时间">
</el-table-column>
<el-table-column
prop="id"
label="操作">
<template slot-scope="scope">
<el-button style="float: right;margin-right: 10px" @click="delFile(scope.row.id)" type="danger" size="small" icon="el-icon-delete" circle></el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="单个上传">
<el-form style="font-size: 14px;width: 1000px">
<el-form-item label="ID(留空会生成默认UUID,可以输入数值型或UUID,如果ID存在就是覆盖修改)">
<el-input v-model="id"></el-input>
</el-form-item>
<el-form-item label="TEXT不能超200字">
<el-input type="textarea" maxlength="300" show-word-limit rows="15" v-model="text"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="addPoints" type="primary" size="small">上传</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="上传网址">
<el-form style="font-size: 14px;width: 1000px">
<el-form-item label="网址">
<el-input v-model="text"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="uploadUrl" type="primary" size="small">上传</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="知识列表" name="tabList">
<el-button @click="window.location.href=window.location.pathname" type="primary" size="mini">全部</el-button>
<el-button @click="backupData" type="primary" size="mini">导出备份</el-button>
<div class="cardBox">
<el-card class="box-card" v-for="item in points">
<div slot="header" class="clearfix">
<span><{item.id}></span>
<el-button style="float: right;" @click="id=item.id;text=item.payload.text;article.dialog=true" type="primary" size="small" icon="el-icon-edit" circle></el-button>
<el-button style="float: right;margin-right: 10px" @click="delPoints(item.id)" type="danger" size="small" icon="el-icon-delete" circle></el-button>
</div>
<div class="item">
<div v-html="item.payload.text"></div>
</div>
<div v-if="item.payload.url"><{item.payload.url}></div>
</el-card>
</div>
</el-tab-pane>
{{/* <el-tab-pane label="演示对话">*/}}
{{/* <div class="chatpdfBox">*/}}
{{/* <div class="chatpdfLine">*/}}
{{/* <div class="chatpdfLineScroll">*/}}
{{/* <h1>欢迎使用知识库AI</h1>*/}}
{{/* <h2>由 AI 支持的知识库AI机器人</h2>*/}}
{{/* <div class="chatpdfRow " v-bind:class="{'chatpdfAsk': item.type=='ask'}" v-for="(item,index) in msgList">*/}}
{{/* <div class="chatpdfContent" v-html="item.content"></div>*/}}
{{/* </div>*/}}
{{/* </div>*/}}
{{/* </div>*/}}
{{/* <div class="chatpdfArea">*/}}
{{/* <textarea @keydown.prevent.enter="sendAsk" v-model="askContent"></textarea>*/}}
{{/* </div>*/}}
{{/* </div>*/}}
{{/* </el-tab-pane>*/}}
</el-tabs>
<el-dialog
title="优化修改"
:visible.sync="article.dialog"
width="50%"
:close-on-click-modal="false"
top="3%"
>
<el-form size="medium">
<el-form-item label="您可以对这一段知识进行修改,使其意思更加明确完整" prop="remark">
<el-input type="textarea"
:rows="8" v-model="text"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="article.dialog = false">取消</el-button>
<el-button type="primary" @click="addPoints()">确认</el-button>
</span>
</el-dialog>
</template>
</div>
</body>
<script>
const collectName="";
new Vue({
el: '#app',
delimiters:["<{","}>"],
data: {
points:[],
id:"",
text:"",
loading:null,
article:{
dialog:false,
id:"",
content:"",
},
fileList:[],
activeTab:"",
fileId:"",
askContent:"",
msgList:[
{type:"ask",content:"自建私有数据知识库 · 与知识库AI聊天"},
{type:"answer",content:"我是知识库机器人,一个专门响应人类指令的大模型"},
],
},
methods: {
//查询集合信息
gettCollectInfo(){
let _this=this;
fetch(collectName+'info', {
method: 'GET',
}).then(res => res.json()).then(data => {
if(!data.result){
_this.$message({
message: data.status.error,
type: 'error'
});
}
});
},
getAllKnowledge(){
let _this=this;
let id=this.fileId;
if(id){
fetch(collectName+'filePoints?id='+id, {
method: 'GET',
}).then(res => res.json()).then(data => {
if(data.result){
_this.points=data.result;
}
});
}else{
fetch(collectName+'points', {
method: 'GET',
}).then(res => res.json()).then(data => {
if(data.result&&data.result.points){
_this.points=data.result.points;
}
});
}
},
//文件列表
getFileList(){
let _this=this;
fetch(collectName+'fileList', {
method: 'GET',
}).then(res => res.json()).then(data => {
if(data.result){
_this.fileList=data.result;
}
});
},
addPoints(){
let _this=this;
let postData={
id:this.id,
content:this.text
}
const encodedData = new URLSearchParams(postData).toString()
fetch(collectName+'training', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: encodedData
}).then(res => res.json()).then(data => {
_this.$message({
message: "success",
type: 'success'
});
_this.getAllKnowledge();
_this.article.dialog=false;
});
},
delPoints(id){
let _this=this;
this.$confirm("是否删除", '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(function(){
fetch(collectName+'delPoints?id='+id, {
method: 'GET',
}).then(res => res.json()).then(data => {
_this.getAllKnowledge();
});
}).catch(function(){
});
},
//删除文件
delFile(id){
let _this=this;
this.$confirm("是否删除", '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(function(){
fetch(collectName+'delFile?id='+id, {
method: 'GET',
}).then(res => res.json()).then(data => {
_this.getFileList();
_this.getAllKnowledge();
});
}).catch(function(){
});
},
//上传文件失败
uploadDocSuccess(response, file, fileList){
this.loading.close();
if(response.code==200){
this.$message({
message: "上传成功",
type: 'success'
});
this.getAllKnowledge();
this.getFileList();
}else{
this.$message({
message: response.msg,
type: 'error'
});
}
},
//上传文件失败
uploadError(){
this.loading.close();
},
//上传之前
beforeUpload(file){
this.loading = this.$loading({
lock: true,
text: "上传中",
});
let ext=file.name.substring(file.name.lastIndexOf(".")+1);
if (ext != 'txt' && ext != 'docx' && ext != 'xlsx' && ext != 'pdf') {
this.$message.error('上传文件只能是 .txt .docx .xlsx .pdf 格式!');
this.loading.close();
return false;
}
},
uploadUrl(){
let _this=this;
let postData={
url:this.text
}
const encodedData = new URLSearchParams(postData).toString()
this.loading = this.$loading({
lock: true,
text: "上传中",
});
fetch(collectName+'uploadUrl', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: encodedData
}).then(res => res.json()).then(data => {
this.loading.close();
if(data.code!=200){
_this.$message({
message: data.msg,
type: 'error'
});
return;
}
_this.$message({
message: "success",
type: 'success'
});
_this.getAllKnowledge();
});
},
backupData(){
let data=[];
for(let i in this.points){
data.push({"txt":this.points[i].payload.text})
}
this.exportCSV(data);
},
//数组导出CSV文件
exportCSV(jsonData,fileName){
if(!jsonData || jsonData.length==0){
return;
}
if(!fileName){
fileName="exportCSV.csv";
}
let one=jsonData[0];
let csvText="";
for(let key in one){
csvText+=key+","
}
csvText=this.trim(csvText,",")+"\n";
//增加\t为了不让表格显示科学计数法或者其他格式
for(let i = 0 ; i < jsonData.length ; i++ ){
let row="";
for(let item in jsonData[i]){
row+=`${jsonData[i][item] + '\t'},`;
}
csvText+=this.trim(row,",")+'\n';
}
//encodeURIComponent解决中文乱码
let uri = 'data:text/csv;charset=utf-8,\ufeff' + encodeURIComponent(csvText);
//通过创建a标签实现
let link = document.createElement("a");
link.href = uri;
//对下载的文件命名
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
trim(str, char) {
if (char) {
str=str.replace(new RegExp('^\\'+char+'+|\\'+char+'+$', 'g'), '');
}
return str.replace(/^\s+|\s+$/g, '');
},
sendAsk(){
if(this.askContent=="") return;
let msg={
'type':'ask',
'content':this.askContent,
}
this.msgList.push(msg);
let data=JSON.stringify(this.msgList);
localStorage.setItem("data_"+this.collect,data);
this.scrollBottom();
this.getReplyFromApi();
},
getReplyFromApi(){
let _this=this;
let msg={
'type':'answer',
'content':"正在为你生成答案...",
}
_this.msgList.push(msg);
let i=0;
var xhr = new XMLHttpRequest();
let system="假设你是一个文档,你必须根据提供的知识信息回答问题,对于与知识信息无关的问题,你应拒绝并告知用户\"对不起,没有查询到相关内容\"";
xhr.open("GET", collectName+"searchStream?keywords="+_this.askContent+"&system="+system);
xhr.setRequestHeader("Content-Type", "text/html");
xhr.onprogress = function(event) {
console.log(i,event.currentTarget.responseText);
_this.msgList[_this.msgList.length-1].content=event.currentTarget.responseText;
_this.scrollBottom();
};
xhr.onreadystatechange = () => {
};
xhr.send();
this.askContent="";
},
//滚动到底部
scrollBottom:function(){
var _this=this;
this.$nextTick(function(){
var container = _this.$el.querySelector(".chatpdfLine");
container.scrollTop = 999999999;
});
},
},
mounted:function(){
},
created: function () {
this.getAllKnowledge();
this.gettCollectInfo();
this.getFileList();
}
})
</script>
</html>