kefu/knowledge/templates/qdrant.html

583 lines
23 KiB
HTML
Raw Permalink 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.

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