506 lines
21 KiB
HTML
506 lines
21 KiB
HTML
|
{{template "header" }}
|
|||
|
<div id="app" style="width:100%">
|
|||
|
<template>
|
|||
|
<el-tabs type="border-card" v-model="activeTab">
|
|||
|
<el-tab-pane label="训练素材" name="fileList">
|
|||
|
<el-input placeholder="搜索知识库 (基于向量相似性语义搜索)" v-model="article.search" style="width:300px;margin:0px 0px 10px 10px;"></el-input>
|
|||
|
<el-button type="primary" @click="searchKnowledge()">搜索</el-button>
|
|||
|
<el-table
|
|||
|
:data="fileList"
|
|||
|
border
|
|||
|
style="width: 100%;margin-top: 10px">
|
|||
|
<el-table-column
|
|||
|
prop="id"
|
|||
|
label="ID">
|
|||
|
</el-table-column>
|
|||
|
<el-table-column
|
|||
|
prop="file_name"
|
|||
|
label="文件名">
|
|||
|
<template slot-scope="scope">
|
|||
|
<a @click="activeTab='knowledgeList';fileId=scope.row.id;getAllKnowledge()"><{scope.row.file_name}></a>
|
|||
|
</template>
|
|||
|
</el-table-column>
|
|||
|
<el-table-column
|
|||
|
prop="file_name"
|
|||
|
label="文件路径">
|
|||
|
<template slot-scope="scope">
|
|||
|
<{getFilePath(scope.row.collect_name,scope.row.file_name)}>
|
|||
|
</template>
|
|||
|
</el-table-column>
|
|||
|
<el-table-column
|
|||
|
prop="file_size"
|
|||
|
label="字符数">
|
|||
|
</el-table-column>
|
|||
|
<el-table-column
|
|||
|
prop="created_at"
|
|||
|
label="上传时间">
|
|||
|
<template slot-scope="scope">
|
|||
|
<{dateFormat(scope.row.created_at)}>
|
|||
|
</template>
|
|||
|
</el-table-column>
|
|||
|
<el-table-column
|
|||
|
prop="id"
|
|||
|
label="操作">
|
|||
|
<template slot-scope="scope">
|
|||
|
<el-button style="margin-right: 10px" @click="activeTab='knowledgeList';fileId=scope.row.id;getAllKnowledge()" type="text">知识列表</el-button>
|
|||
|
<el-button style="margin-right: 15px" @click="addSigleTxt.content='',addSigleTxt.title=scope.row.file_name;addSigleTxt.fileId=scope.row.id;addSigleTxt.dialog=true" type="text">添加</el-button>
|
|||
|
<el-button style="margin-right: 10px" @click="exportToExcel(scope.row.id,scope.row.file_name)" type="text">导出</el-button>
|
|||
|
|
|||
|
<el-button style="margin-right: 10px" @click="delFile(scope.row.id)" type="text">删除</el-button>
|
|||
|
|
|||
|
</template>
|
|||
|
</el-table-column>
|
|||
|
</el-table>
|
|||
|
</el-tab-pane>
|
|||
|
<el-tab-pane label="手动录入">
|
|||
|
<el-descriptions style="font-size: 14px;" direction="vertical" :column="1" border>
|
|||
|
<el-descriptions-item label="输入文本">
|
|||
|
<el-form style="font-size: 14px;width: 800px">
|
|||
|
<el-form-item label="文本标题">
|
|||
|
<el-input v-model="singleTxt.title"></el-input>
|
|||
|
</el-form-item>
|
|||
|
<el-form-item label="文本内容">
|
|||
|
<el-input type="textarea" show-word-limit rows="10" v-model="singleTxt.content"></el-input>
|
|||
|
</el-form-item>
|
|||
|
<el-form-item>
|
|||
|
<el-button @click="addPoints" type="primary" size="small">提交</el-button>
|
|||
|
</el-form-item>
|
|||
|
</el-form>
|
|||
|
</el-descriptions-item>
|
|||
|
</el-descriptions>
|
|||
|
</el-tab-pane>
|
|||
|
<el-tab-pane label="上传素材">
|
|||
|
<el-alert
|
|||
|
title="注意: 支持上传.txt .docx .pdf .xlsx文档! pdf文档不能是图片扫描生成的,读取不到数据! excel文档会按行进行分割入库"
|
|||
|
type="info"
|
|||
|
show-icon>
|
|||
|
</el-alert>
|
|||
|
|
|||
|
|
|||
|
<el-form style="margin:10px 0px;">
|
|||
|
<el-upload
|
|||
|
multiple
|
|||
|
drag
|
|||
|
style="margin:10px 0px;"
|
|||
|
:action="uploadUrl"
|
|||
|
:on-success="uploadDocSuccess"
|
|||
|
:on-error="uploadError"
|
|||
|
:before-upload="beforeUpload"
|
|||
|
>
|
|||
|
<i class="el-icon-upload"></i>
|
|||
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
|||
|
</el-upload>
|
|||
|
<el-form-item style="display: none">
|
|||
|
<el-input style="width: 800px;" v-model="learnUrl" placeholder="网页地址"></el-input>
|
|||
|
</el-form-item>
|
|||
|
<el-form-item style="display: none">
|
|||
|
<el-button @click="uploadUrlPage" icon="el-icon-upload" type="primary">上传网址</el-button>
|
|||
|
</el-form-item>
|
|||
|
</el-form>
|
|||
|
</el-tab-pane>
|
|||
|
<el-tab-pane label="知识列表" name="knowledgeList">
|
|||
|
<el-input placeholder="搜索知识库 (基于向量相似性语义搜索)" v-model="article.search" style="width:300px;margin:0px 0px 10px 10px;"></el-input>
|
|||
|
<el-button type="primary" @click="searchKnowledge()">搜索</el-button>
|
|||
|
<el-table
|
|||
|
:data="filePoints"
|
|||
|
border
|
|||
|
style="width: 100%;margin-top: 10px">
|
|||
|
<el-table-column
|
|||
|
prop="content"
|
|||
|
label="内容">
|
|||
|
<template slot-scope="scope">
|
|||
|
<{scope.row.payload.text}>
|
|||
|
</template>
|
|||
|
</el-table-column>
|
|||
|
<el-table-column
|
|||
|
width="380px"
|
|||
|
prop="content"
|
|||
|
label="文件ID">
|
|||
|
<template slot-scope="scope">
|
|||
|
<{scope.row.payload.fileid}>
|
|||
|
</template>
|
|||
|
</el-table-column>
|
|||
|
<el-table-column
|
|||
|
width="180px"
|
|||
|
prop="id"
|
|||
|
label="操作">
|
|||
|
<template slot-scope="scope">
|
|||
|
<el-button style="float: right;margin-right: 10px" @click="delPoints(scope.row.id)" type="text">删除</el-button>
|
|||
|
<el-button style="float: right;margin-right: 15px" @click="article.fileId=fileId;article.title=scope.row.payload.url;article.id=scope.row.id;article.content=scope.row.payload.text;article.dialog=true" type="text">编辑</el-button>
|
|||
|
</template>
|
|||
|
</el-table-column>
|
|||
|
</el-table>
|
|||
|
</el-tab-pane>
|
|||
|
<el-tab-pane label="提问日志" name="questionList">
|
|||
|
<el-button type="primary" @click="getLogs()">刷新</el-button>
|
|||
|
<el-table
|
|||
|
:data="logList.list"
|
|||
|
border
|
|||
|
style="width: 100%;margin-top: 10px">
|
|||
|
<el-table-column
|
|||
|
prop="question"
|
|||
|
width="800"
|
|||
|
label="提问日志">
|
|||
|
<template slot-scope="scope">
|
|||
|
<div v-html='replace(scope.row.question,"\n","<br>")'></div>
|
|||
|
</template>
|
|||
|
</el-table-column>
|
|||
|
<el-table-column
|
|||
|
prop="answer"
|
|||
|
label="回答日志">
|
|||
|
</el-table-column>
|
|||
|
<el-table-column
|
|||
|
prop="created_at"
|
|||
|
label="时间">
|
|||
|
</el-table-column>
|
|||
|
</el-table>
|
|||
|
<el-pagination
|
|||
|
background
|
|||
|
@current-change="getLogs"
|
|||
|
:current-page="logList.page"
|
|||
|
layout="prev,pager, next"
|
|||
|
:page-size="logList.pagesize"
|
|||
|
:total="logList.count">
|
|||
|
</el-pagination>
|
|||
|
</el-tab-pane>
|
|||
|
</el-tabs>
|
|||
|
|
|||
|
|
|||
|
<el-dialog
|
|||
|
title="添加知识"
|
|||
|
:visible.sync="addSigleTxt.dialog"
|
|||
|
width="50%"
|
|||
|
:close-on-click-modal="false"
|
|||
|
top="3%"
|
|||
|
>
|
|||
|
<el-form size="medium">
|
|||
|
<el-form-item label="内容">
|
|||
|
<el-input type="textarea" show-word-limit :rows="10" v-model="addSigleTxt.content"></el-input>
|
|||
|
</el-form-item>
|
|||
|
</el-form>
|
|||
|
<span slot="footer" class="dialog-footer">
|
|||
|
<el-button @click="addSigleTxt.dialog = false">取消</el-button>
|
|||
|
<el-button type="primary" @click="saveFilePoints()">确认</el-button>
|
|||
|
</span>
|
|||
|
</el-dialog>
|
|||
|
<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="10" v-model="article.content"></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="savePoints()">确认</el-button>
|
|||
|
</span>
|
|||
|
</el-dialog>
|
|||
|
</template>
|
|||
|
</div>
|
|||
|
<script src="/static/js/xlsx.full.min.js" type="text/javascript"></script>
|
|||
|
<script>
|
|||
|
new Vue({
|
|||
|
el: '#app',
|
|||
|
delimiters: ["<{", "}>"],
|
|||
|
data: {
|
|||
|
activeTab:"fileList",
|
|||
|
fileList:[],
|
|||
|
filePoints:[],
|
|||
|
fileId:"",
|
|||
|
addSigleTxt:{
|
|||
|
dialog:false,
|
|||
|
fileId:"",
|
|||
|
content:"",
|
|||
|
title:"",
|
|||
|
},
|
|||
|
singleTxt:{
|
|||
|
title:"",
|
|||
|
content:""
|
|||
|
},
|
|||
|
article:{
|
|||
|
title:"",
|
|||
|
dialog:false,
|
|||
|
id:"",
|
|||
|
content:"",
|
|||
|
search:"",
|
|||
|
},
|
|||
|
uploadUrl:"",
|
|||
|
learnUrl:"",
|
|||
|
logList:{
|
|||
|
page:1,
|
|||
|
count:0,
|
|||
|
pagesize:0,
|
|||
|
list:[],
|
|||
|
},
|
|||
|
},
|
|||
|
methods: {
|
|||
|
//文件列表
|
|||
|
getFileList(){
|
|||
|
let _this=this;
|
|||
|
sendAjax('/kefu/ai/fileList',"GET",{},function(data){
|
|||
|
if(data.result){
|
|||
|
_this.fileList=data.result;
|
|||
|
}else{
|
|||
|
_this.$message({
|
|||
|
message:"请联系管理员创建集合,然后补充完整(大模型接口,大模型key,知识库集合)",
|
|||
|
type: 'error'
|
|||
|
});
|
|||
|
}
|
|||
|
})
|
|||
|
},
|
|||
|
//时间格式化
|
|||
|
dateFormat(oldTime){
|
|||
|
return dateFormat("YYYY-mm-dd HH:MM:SS",new Date(oldTime))
|
|||
|
},
|
|||
|
//上传文本数据
|
|||
|
addPoints(){
|
|||
|
let _this=this;
|
|||
|
this.listLoading = this.$loading({
|
|||
|
lock: true,
|
|||
|
text: "上传中",
|
|||
|
});
|
|||
|
sendAjax('/kefu/ai/training',"POST",this.singleTxt,function(data){
|
|||
|
_this.activeTab="fileList";
|
|||
|
_this.listLoading.close();
|
|||
|
let ret=JSON.parse(data);
|
|||
|
if(ret.status!="ok"){
|
|||
|
_this.$message({
|
|||
|
message:data,
|
|||
|
type: 'error'
|
|||
|
});
|
|||
|
return;
|
|||
|
}
|
|||
|
_this.singleTxt={
|
|||
|
title:"",
|
|||
|
content:""
|
|||
|
};
|
|||
|
_this.getFileList();
|
|||
|
_this.$message({
|
|||
|
message: "success",
|
|||
|
type: 'success'
|
|||
|
});
|
|||
|
})
|
|||
|
},
|
|||
|
//编辑知识
|
|||
|
savePoints(){
|
|||
|
let _this=this;
|
|||
|
this.listLoading = this.$loading({
|
|||
|
lock: true,
|
|||
|
text: "修改中",
|
|||
|
});
|
|||
|
sendAjax('/kefu/ai/training',"POST",this.article,function(data){
|
|||
|
_this.listLoading.close();
|
|||
|
let ret=JSON.parse(data);
|
|||
|
if(ret.status!="ok"){
|
|||
|
_this.$message({
|
|||
|
message:data,
|
|||
|
type: 'error'
|
|||
|
});
|
|||
|
return;
|
|||
|
}
|
|||
|
_this.article.dialog = false;
|
|||
|
_this.getAllKnowledge();
|
|||
|
_this.searchKnowledge();
|
|||
|
_this.$message({
|
|||
|
message: "success",
|
|||
|
type: 'success'
|
|||
|
});
|
|||
|
})
|
|||
|
},
|
|||
|
//搜索知识
|
|||
|
searchKnowledge(){
|
|||
|
let _this=this;
|
|||
|
if(!this.article.search) return;
|
|||
|
this.listLoading = this.$loading({
|
|||
|
lock: true,
|
|||
|
text: "搜索中",
|
|||
|
});
|
|||
|
sendAjax('/kefu/ai/searchPoints',"POST",{search:this.article.search},function(data){
|
|||
|
_this.listLoading.close();
|
|||
|
let ret=JSON.parse(data)
|
|||
|
if(ret.result){
|
|||
|
_this.filePoints=ret.result;
|
|||
|
_this.activeTab='knowledgeList'
|
|||
|
}
|
|||
|
})
|
|||
|
},
|
|||
|
//新增知识
|
|||
|
saveFilePoints(){
|
|||
|
let _this=this;
|
|||
|
this.listLoading = this.$loading({
|
|||
|
lock: true,
|
|||
|
text: "添加中",
|
|||
|
});
|
|||
|
sendAjax('/kefu/ai/training',"POST",this.addSigleTxt,function(data){
|
|||
|
_this.listLoading.close();
|
|||
|
let ret=JSON.parse(data);
|
|||
|
if(ret.status!="ok"){
|
|||
|
_this.$message({
|
|||
|
message:data,
|
|||
|
type: 'error'
|
|||
|
});
|
|||
|
return;
|
|||
|
}
|
|||
|
_this.addSigleTxt.dialog=false;
|
|||
|
_this.getFileList();
|
|||
|
_this.$message({
|
|||
|
message: "success",
|
|||
|
type: 'success'
|
|||
|
});
|
|||
|
})
|
|||
|
},
|
|||
|
delFile(id){
|
|||
|
let _this=this;
|
|||
|
this.$confirm("是否删除", '提示', {
|
|||
|
confirmButtonText: '确定',
|
|||
|
cancelButtonText: '取消',
|
|||
|
type: 'warning'
|
|||
|
}).then(function(){
|
|||
|
sendAjax('/kefu/ai/delFile',"GET",{id:id},function(data){
|
|||
|
_this.getFileList();
|
|||
|
_this.$message({
|
|||
|
message: "success",
|
|||
|
type: 'success'
|
|||
|
});
|
|||
|
})
|
|||
|
}).catch(function(){
|
|||
|
});
|
|||
|
},
|
|||
|
//知识列表
|
|||
|
getAllKnowledge(){
|
|||
|
let _this=this;
|
|||
|
let id=this.fileId;
|
|||
|
if(id){
|
|||
|
sendAjax('/kefu/ai/filePoints',"GET",{id:id},function(data){
|
|||
|
let ret=JSON.parse(data)
|
|||
|
if(ret.result){
|
|||
|
_this.filePoints=ret.result;
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
},
|
|||
|
//删除知识
|
|||
|
delPoints(id){
|
|||
|
let _this=this;
|
|||
|
this.$confirm("是否删除", '提示', {
|
|||
|
confirmButtonText: '确定',
|
|||
|
cancelButtonText: '取消',
|
|||
|
type: 'warning'
|
|||
|
}).then(function(){
|
|||
|
sendAjax('/kefu/ai/delPoints',"GET",{id:id},function(data){
|
|||
|
_this.getAllKnowledge();
|
|||
|
})
|
|||
|
}).catch(function(){
|
|||
|
});
|
|||
|
},
|
|||
|
//上传文件失败
|
|||
|
uploadDocSuccess(response, file, fileList){
|
|||
|
this.listLoading.close();
|
|||
|
this.activeTab="fileList";
|
|||
|
if(response.code==200){
|
|||
|
this.$message({
|
|||
|
message: "上传成功",
|
|||
|
type: 'success'
|
|||
|
});
|
|||
|
this.getFileList();
|
|||
|
}else{
|
|||
|
this.$message({
|
|||
|
message: response.msg,
|
|||
|
type: 'error'
|
|||
|
});
|
|||
|
}
|
|||
|
},
|
|||
|
//上传文件失败
|
|||
|
uploadError(){
|
|||
|
this.listLoading.close();
|
|||
|
},
|
|||
|
//上传之前
|
|||
|
beforeUpload(file){
|
|||
|
this.listLoading = 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.listLoading.close();
|
|||
|
return false;
|
|||
|
}
|
|||
|
},
|
|||
|
//上传URL
|
|||
|
uploadUrlPage(){
|
|||
|
let _this=this;
|
|||
|
this.listLoading = this.$loading({
|
|||
|
lock: true,
|
|||
|
text: "上传中",
|
|||
|
});
|
|||
|
sendAjax('/kefu/ai/uploadUrl',"POST",{url:this.learnUrl},function(data){
|
|||
|
_this.listLoading.close();
|
|||
|
_this.learnUrl="";
|
|||
|
_this.getFileList();
|
|||
|
_this.activeTab="fileList";
|
|||
|
_this.$message({
|
|||
|
message: "success",
|
|||
|
type: 'success'
|
|||
|
});
|
|||
|
})
|
|||
|
},
|
|||
|
//获取文件路径
|
|||
|
getFilePath(collectName,fileName){
|
|||
|
if(fileName.includes(".")){
|
|||
|
return "/static/upload/ai/"+collectName+"/"+fileName;
|
|||
|
}else{
|
|||
|
return "文案知识";
|
|||
|
}
|
|||
|
},
|
|||
|
//导出excel
|
|||
|
exportToExcel(id,filename) {
|
|||
|
let _this=this;
|
|||
|
this.listLoading = this.$loading({
|
|||
|
lock: true,
|
|||
|
text: "正在导出excel",
|
|||
|
});
|
|||
|
sendAjax('/kefu/ai/filePoints',"GET",{id:id},function(data){
|
|||
|
let ret=JSON.parse(data)
|
|||
|
if(ret.result){
|
|||
|
let data=[[filename+'的主要内容']];
|
|||
|
for(let i in ret.result){
|
|||
|
data.push([ret.result[i].payload.text]);
|
|||
|
}
|
|||
|
const worksheet = XLSX.utils.aoa_to_sheet(data);
|
|||
|
const workbook = XLSX.utils.book_new();
|
|||
|
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
|
|||
|
XLSX.writeFile(workbook, filename+'.xlsx');
|
|||
|
_this.listLoading.close();
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
},
|
|||
|
//获取日志列表
|
|||
|
getLogs(page){
|
|||
|
var _this=this;
|
|||
|
sendAjax("/kefu/llmLogs","get",{page:page,pagesize:20},function(result){
|
|||
|
_this.logList=result.result;
|
|||
|
});
|
|||
|
},
|
|||
|
replace(str,source,dest){
|
|||
|
return str.replaceAll(source,dest);
|
|||
|
},
|
|||
|
},
|
|||
|
mounted:function(){
|
|||
|
|
|||
|
},
|
|||
|
created: function () {
|
|||
|
this.singleTxt.title="文本-"+this.dateFormat(new Date());
|
|||
|
this.uploadUrl=`/kefu/ai/uploadDoc?token=`+localStorage.getItem("token");
|
|||
|
this.getFileList();
|
|||
|
this.getLogs();
|
|||
|
}
|
|||
|
});
|
|||
|
</script>
|