583 lines
23 KiB
HTML
583 lines
23 KiB
HTML
<!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> |