init
This commit is contained in:
324
src/views/knowledge/aiDocument/form.vue
Normal file
324
src/views/knowledge/aiDocument/form.vue
Normal file
@@ -0,0 +1,324 @@
|
||||
<template>
|
||||
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :width="800" :close-on-click-modal="false" draggable :destroy-on-close="true" class="dark:bg-gray-800">
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
formDialogRef
|
||||
@submit.prevent
|
||||
label-width="90px"
|
||||
v-loading="loading"
|
||||
class="dark:text-gray-300"
|
||||
>
|
||||
<el-collapse v-model="activeNames" class="dark:border-gray-700">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mt-8 mb20">
|
||||
<el-form-item label="知识库" prop="datasetId" class="dark:text-gray-300">
|
||||
<el-select v-model="form.datasetId" placeholder="请选择知识库" class="dark:bg-gray-700">
|
||||
<el-option v-for="item in datasetList" :key="item.id" :label="item.name" :value="item.id" class="dark:hover:bg-gray-600" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mt-8 mb20">
|
||||
<el-form-item label="来源" prop="sourceType" class="dark:text-gray-300">
|
||||
<el-radio-group v-model="form.sourceType" class="dark:text-gray-300">
|
||||
<el-radio-button
|
||||
v-for="item in source_type"
|
||||
:key="item.value"
|
||||
:label="item.value"
|
||||
border
|
||||
class="dark:border-gray-600 dark:text-gray-300"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<TextDocumentForm v-if="form.sourceType === '2'" v-model="form" />
|
||||
<FileDocumentForm v-else-if="form.sourceType === '1'" v-model="form.files" />
|
||||
<QADocumentForm v-else-if="form.sourceType === '3'" v-model="form.files" />
|
||||
<CrawlerDocumentForm v-else-if="form.sourceType === '4'" v-model="form" />
|
||||
|
||||
<el-collapse-item name="3" class="dark:border-gray-700" :disabled="false">
|
||||
<template #title>
|
||||
<div class="flex items-center mb-3 font-medium text-gray-700 dark:text-gray-300">
|
||||
<el-icon class="mr-1"><Setting /></el-icon>
|
||||
分片设置
|
||||
</div>
|
||||
</template>
|
||||
<el-row :gutter="24" class="mb-4">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="分片算法" prop="sliceType" class="dark:text-gray-300">
|
||||
<div class="flex flex-wrap gap-2 items-center">
|
||||
<div
|
||||
v-for="item in slice_algorithm_types"
|
||||
:key="item.value"
|
||||
:class="[
|
||||
'cursor-pointer mr-6 border rounded-lg py-2 px-3 flex items-center transition-all hover:shadow-sm',
|
||||
form.sliceType === item.value ? 'border-blue-500 shadow-sm dark:bg-gray-700' : 'border-gray-200 dark:border-gray-600',
|
||||
]"
|
||||
@click="form.sliceType = item.value"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'rounded-full w-6 h-6 flex items-center justify-center mr-2',
|
||||
form.sliceType === item.value ? 'bg-blue-500' : 'bg-gray-100 dark:bg-gray-600',
|
||||
]"
|
||||
>
|
||||
<el-icon :class="['text-lg', form.sliceType === item.value ? 'text-white' : 'text-gray-500 dark:text-gray-300']">
|
||||
<component :is="getIconForType(item.value)" />
|
||||
</el-icon>
|
||||
</div>
|
||||
<span :class="['text-sm', form.sliceType === item.value ? 'font-medium text-blue-500 dark:text-blue-400' : 'dark:text-gray-300']">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24" class="mt-6">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="分片值" prop="maxSegmentSizeInTokens" class="dark:text-gray-300">
|
||||
<template #label>分片值 <tip content="每个分片里面字符总数量" /> </template>
|
||||
|
||||
<el-input-number
|
||||
v-model="form.maxSegmentSizeInTokens"
|
||||
:min="500"
|
||||
:max="4000"
|
||||
:step="100"
|
||||
class="w-full dark:bg-gray-700"
|
||||
placeholder="请输入分片大小"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="maxOverlapSizeInTokens" class="dark:text-gray-300">
|
||||
<template #label>重叠值 <tip content="是指分片之间的重叠大小,避免分割丢失上下文" /> </template>
|
||||
<el-input-number
|
||||
v-model="form.maxOverlapSizeInTokens"
|
||||
:min="0"
|
||||
:max="200"
|
||||
:step="50"
|
||||
class="w-full dark:bg-gray-700"
|
||||
placeholder="请输入重叠大小"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false" class="dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700"> 取消 </el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading" class="dark:border-gray-600"> 确认 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="AiDocumentDialog">
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { getObj, addObj, putObj } from '/@/api/knowledge/aiDocument';
|
||||
import { fetchDataList } from '/@/api/knowledge/aiDataset';
|
||||
import { rule } from '/@/utils/validate';
|
||||
import { Setting, Document, ChatLineRound, Reading, Collection, Paperclip, Operation, Link } from '@element-plus/icons-vue';
|
||||
|
||||
const TextDocumentForm = defineAsyncComponent(() => import('./sources/TextDocumentForm.vue'));
|
||||
const FileDocumentForm = defineAsyncComponent(() => import('./sources/FileDocumentForm.vue'));
|
||||
const QADocumentForm = defineAsyncComponent(() => import('./sources/QADocumentForm.vue'));
|
||||
const CrawlerDocumentForm = defineAsyncComponent(() => import('./sources/CrawlerDocumentForm.vue'));
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// 定义变量内容
|
||||
const { source_type } = useDict('source_type');
|
||||
const dataFormRef = ref();
|
||||
const route = useRoute();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const fileType = ref(['jpeg', 'png', 'jpg', 'gif', 'md', 'doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'pptx', 'html']);
|
||||
|
||||
/**
|
||||
* 文档分片算法枚举
|
||||
*/
|
||||
enum SliceAlgorithm {
|
||||
PARAGRAPH = 'paragraph', // 段落分割器
|
||||
LINE = 'line', // 行分割器
|
||||
SENTENCE = 'sentence', // 句子分割器
|
||||
WORD = 'word', // 单词分割器
|
||||
CHARACTER = 'character', // 字符分割器
|
||||
REGEX = 'regex', // 正则表达式分割器
|
||||
RECURSIVE = 'recursive', // 递归智能分割器
|
||||
}
|
||||
|
||||
// 分片算法选项
|
||||
const slice_algorithm_types = [
|
||||
{ value: SliceAlgorithm.RECURSIVE, label: '智能分片' },
|
||||
{ value: SliceAlgorithm.PARAGRAPH, label: '段落分片' },
|
||||
{ value: SliceAlgorithm.SENTENCE, label: '句子分片' },
|
||||
{ value: SliceAlgorithm.CHARACTER, label: '字符分片' },
|
||||
];
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
datasetId: '',
|
||||
fileType: '',
|
||||
content: '',
|
||||
files: [],
|
||||
sourceType: '1',
|
||||
sliceCount: '',
|
||||
hitCount: '',
|
||||
fileSize: '',
|
||||
fileStatus: '1',
|
||||
sliceType: SliceAlgorithm.RECURSIVE,
|
||||
repoType: '',
|
||||
repoOwner: '',
|
||||
repoName: '',
|
||||
accessToken: '',
|
||||
maxSegmentSizeInTokens: 1000,
|
||||
maxOverlapSizeInTokens: 50,
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
datasetId: [{ required: true, message: '所属知识库不能为空', trigger: 'blur' }],
|
||||
name: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '文件名不能为空', trigger: 'blur' },
|
||||
],
|
||||
content: [{ required: true, message: '内容不能为空', trigger: 'blur' }],
|
||||
url: [
|
||||
{ required: true, message: '网址不能为空', trigger: 'blur' },
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ type: 'url', message: '请输入正确的网址', trigger: 'blur' },
|
||||
],
|
||||
settings: [{ required: true, message: '请输入配置', trigger: 'change' }],
|
||||
sliceType: [{ required: true, message: '请选择分片算法', trigger: 'change' }],
|
||||
maxSegmentSizeInTokens: [{ required: true, message: '请输入分片大小', trigger: 'blur' }],
|
||||
maxOverlapSizeInTokens: [{ required: true, message: '请输入重叠大小', trigger: 'blur' }],
|
||||
files: [
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (form.sourceType === '1' && (!form.files || form.files.length === 0)) {
|
||||
callback(new Error('文件不能为空'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
});
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
form.files = [];
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
getDatasetList();
|
||||
|
||||
// 获取aiDocument信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getAiDocumentData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 监听 form.sourceType 变化,如果 sourceType === 1 则打开 excelUploadRef.show()
|
||||
watch(
|
||||
() => form.sourceType,
|
||||
(value) => {
|
||||
if (value === '3') {
|
||||
fileType.value = ['xlsx'];
|
||||
} else {
|
||||
fileType.value = ['jpeg', 'png', 'jpg', 'gif', 'md', 'doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'pptx', 'html'];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
form.id ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(form.id ? '修改成功' : '添加成功');
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const datasetList = ref<{ id: string; name: string }[]>([]);
|
||||
const getDatasetList = async () => {
|
||||
const { data } = await fetchDataList();
|
||||
datasetList.value = data;
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const getAiDocumentData = (id: string) => {
|
||||
// 获取数据
|
||||
loading.value = true;
|
||||
getObj(id)
|
||||
.then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const datasetId = route.query.datasetId;
|
||||
if (typeof datasetId === 'string') {
|
||||
form.datasetId = datasetId;
|
||||
}
|
||||
});
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
|
||||
// 新增的响应式变量
|
||||
const activeNames = ref(['1', '2', '3']);
|
||||
|
||||
// 获取对应切片类型的图标
|
||||
const getIconForType = (type: string) => {
|
||||
switch (type) {
|
||||
case SliceAlgorithm.PARAGRAPH:
|
||||
return Document;
|
||||
case SliceAlgorithm.LINE:
|
||||
return Reading;
|
||||
case SliceAlgorithm.SENTENCE:
|
||||
return ChatLineRound;
|
||||
case SliceAlgorithm.WORD:
|
||||
return Collection;
|
||||
case SliceAlgorithm.CHARACTER:
|
||||
return Paperclip;
|
||||
case SliceAlgorithm.REGEX:
|
||||
return Operation;
|
||||
case SliceAlgorithm.RECURSIVE:
|
||||
return Link;
|
||||
default:
|
||||
return Document;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
281
src/views/knowledge/aiDocument/index.vue
Normal file
281
src/views/knowledge/aiDocument/index.vue
Normal file
@@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item label="知识库名" prop="title">
|
||||
<el-select placeholder="请选择知识库" v-model="state.queryForm.datasetId">
|
||||
<el-option :key="index" :label="item.name" :value="item.id" v-for="(item, index) in datasetList">
|
||||
{{ item.name }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="切片状态" prop="sliceStatus">
|
||||
<el-select placeholder="请选择状态" v-model="state.queryForm.sliceStatus">
|
||||
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in slice_status">
|
||||
{{ item.label }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="总结状态" prop="summaryStatus">
|
||||
<el-select placeholder="请选择状态" v-model="state.queryForm.summaryStatus">
|
||||
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in summary_status">
|
||||
{{ item.label }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件名" prop="name">
|
||||
<el-input placeholder="请输入文件名" v-model="state.queryForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文件来源" prop="sourceType">
|
||||
<el-select placeholder="请选择状态" v-model="state.queryForm.sourceType">
|
||||
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in source_type">
|
||||
{{ item.label }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="getDataList"> 查询</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()" v-auth="'knowledge_aiDocument_add'">
|
||||
新 增
|
||||
</el-button>
|
||||
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'knowledge_aiDocument_del'" @click="handleDelete(selectObjs)">
|
||||
删除
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'knowledge_aiDocument_export'"
|
||||
@exportExcel="exportExcel"
|
||||
class="ml10 mr20"
|
||||
style="float: right"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
@row-dblclick="go2slice"
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
@selection-change="selectionChangHandle"
|
||||
@sort-change="sortChangeHandle"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" label="#" width="40" />
|
||||
<el-table-column prop="name" label="名称" width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="fileType" label="文件类型" show-overflow-tooltip />
|
||||
<el-table-column prop="sourceType" label="文件来源" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="source_type" :value="scope.row.sourceType"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sliceCount" label="切片数量" show-overflow-tooltip />
|
||||
<el-table-column prop="hitCount" label="命中次数" show-overflow-tooltip />
|
||||
<el-table-column prop="fileStatus" width="100" show-overflow-tooltip>
|
||||
<template #header>
|
||||
切片结果
|
||||
<tip content="点击【失败】标签可查看失败原因" />
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.sliceStatus === '9'">
|
||||
<el-tooltip placement="top">
|
||||
<template #content>{{ scope.row.sliceFailReason }}</template>
|
||||
<dict-tag :options="slice_status" :value="scope.row.sliceStatus" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<dict-tag :options="slice_status" :value="scope.row.sliceStatus" />
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="summaryStatus" width="100" show-overflow-tooltip>
|
||||
<template #header>
|
||||
总结结果
|
||||
<tip content="点击【失败】标签可查看失败原因" />
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.summaryStatus === '9'">
|
||||
<el-tooltip placement="top">
|
||||
<template #content>{{ scope.row.summaryFailReason }}</template>
|
||||
<dict-tag :options="summary_status" :value="scope.row.summaryStatus" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<dict-tag :options="summary_status" :value="scope.row.summaryStatus" />
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="300">
|
||||
<template #default="scope">
|
||||
<el-button icon="View" text type="primary" @click="viewDocument(scope.row)" :disabled="scope.row.sliceStatus !== '1'">文档</el-button>
|
||||
<el-button icon="Refresh" text type="primary" @click="retry2slice(scope.row)">重试</el-button>
|
||||
<el-button icon="edit-pen" text type="primary" v-auth="'knowledge_aiDocument_del'" @click="go2slice(scope.row)">切片</el-button>
|
||||
<el-button icon="delete" text type="primary" v-auth="'knowledge_aiDocument_del'" @click="handleDelete([scope.row.id])">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
|
||||
|
||||
<!-- 文档查看抽屉 -->
|
||||
<document-drawer
|
||||
v-model="documentDrawerVisible"
|
||||
:document-id="selectedDocumentId"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemAiDocument">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchList, delObjs, retrySlice, retryIssue } from '/@/api/knowledge/aiDocument';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { fetchDataList } from '/@/api/knowledge/aiDataset';
|
||||
import DocumentDrawer from '../aiSlice/components/DocumentDrawer.vue';
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
// 定义查询字典
|
||||
const { source_type, slice_status, summary_status } = useDict('yes_no_type', 'source_type', 'slice_status', 'summary_status');
|
||||
const router = useRouter();
|
||||
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
createdIsNeed: false,
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields();
|
||||
state.queryForm = {};
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/knowledge/aiDocument/export', Object.assign(state.queryForm, { ids: selectObjs }), 'aiDocument.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const selectionChangHandle = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm('此操作将永久删除');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObjs(ids);
|
||||
getDataList();
|
||||
useMessage().success('删除成功');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
const datasetList = ref([]);
|
||||
/**
|
||||
* 获取知识库列表数据
|
||||
* 通过API调用获取所有可用的知识库
|
||||
*/
|
||||
const getDatasetList = async () => {
|
||||
const { data } = await fetchDataList();
|
||||
datasetList.value = data;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await getDatasetList();
|
||||
if (route.query.datasetId) {
|
||||
state.queryForm.datasetId = route.query.datasetId;
|
||||
}
|
||||
|
||||
// 查询表格数据
|
||||
await getDataList();
|
||||
});
|
||||
|
||||
/**
|
||||
* 重新执行文档切片
|
||||
* @param document 需要重新切片的文档对象
|
||||
*/
|
||||
const retry2slice = async (document: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm('此操作将重新切片,删除原有切片数据');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await retrySlice(document);
|
||||
useMessage().success('操作成功,稍后请刷新列表查看');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
// 文档查看抽屉
|
||||
const documentDrawerVisible = ref(false);
|
||||
const selectedDocumentId = ref('');
|
||||
|
||||
/**
|
||||
* 跳转到文档切片页面
|
||||
* @param document 要查看/编辑切片的文档对象
|
||||
*/
|
||||
const go2slice = (document: any) => {
|
||||
router.push({
|
||||
path: '/knowledge/aiSlice/index',
|
||||
query: {
|
||||
documentId: document.id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看完整文档
|
||||
* @param document 要查看的文档对象
|
||||
*/
|
||||
const viewDocument = (document: any) => {
|
||||
// 只有切片成功的文档才能查看
|
||||
if (document.sliceStatus !== '1') {
|
||||
useMessage().warning('只有切片成功的文档才能查看');
|
||||
return;
|
||||
}
|
||||
|
||||
selectedDocumentId.value = document.id;
|
||||
documentDrawerVisible.value = true;
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<el-col class="mb20">
|
||||
<el-form-item label="文件名" prop="name">
|
||||
<el-input placeholder="请输入文件名" v-model="modelValue.name" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col class="mb20">
|
||||
<el-form-item label="网址" prop="url">
|
||||
<el-input placeholder="请输入网址" v-model="modelValue.url">
|
||||
<template #append>
|
||||
<el-button :icon="Search" :loading="loading" @click="handleParse"></el-button>
|
||||
<el-divider direction="vertical" class="mx-4" />
|
||||
<el-button :icon="Setting" @click="handleToggleSettings"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col class="mb20" v-if="showSettings">
|
||||
<el-form-item prop="settings">
|
||||
<template #label>目标<tip content="请输入目标元素class,例如 .class1"></tip> </template>
|
||||
<el-input show-word-limit v-model="modelValue.settings" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col class="mb20">
|
||||
<el-form-item label="内容" prop="content">
|
||||
<el-input type="textarea" rows="10" maxlength="3000" show-word-limit v-model="modelValue.content" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, PropType } from 'vue';
|
||||
import { Search, Setting } from '@element-plus/icons-vue';
|
||||
// @ts-ignore
|
||||
import JsonEditor from '@axolo/json-editor-vue';
|
||||
import { crawleObj } from '/@/api/knowledge/aiDocument';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object as PropType<any>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const showSettings = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const handleToggleSettings = () => {
|
||||
showSettings.value = !showSettings.value;
|
||||
};
|
||||
|
||||
const handleParse = async () => {
|
||||
if (loading.value) return;
|
||||
if (!props.modelValue.url) {
|
||||
ElMessage.warning('请输入网址');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const payload = {
|
||||
url: props.modelValue.url,
|
||||
settings: props.modelValue.settings,
|
||||
};
|
||||
|
||||
const { data } = await crawleObj(payload);
|
||||
props.modelValue.content = data;
|
||||
emit('update:modelValue', { ...props.modelValue, content: data });
|
||||
ElMessage.success('解析成功');
|
||||
} catch (error) {
|
||||
ElMessage.error('解析失败,请检查后台服务或接口定义');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
29
src/views/knowledge/aiDocument/sources/FileDocumentForm.vue
Normal file
29
src/views/knowledge/aiDocument/sources/FileDocumentForm.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<el-col class="mb20">
|
||||
<el-form-item label="资料" prop="files">
|
||||
<upload-file :limit="fileLimit" :fileSize="fileSize" :fileType="fileType" @change="handleFileChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
},
|
||||
});
|
||||
|
||||
// 单此上传文件数量限制
|
||||
const fileLimit = ref(5);
|
||||
// 单个文件大小限制
|
||||
const fileSize = ref(10);
|
||||
// 文件类型限制
|
||||
const fileType = ref(['jpeg', 'png', 'jpg', 'gif', 'md', 'doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'pptx']);
|
||||
|
||||
const handleFileChange = (fileNames: string, fileList: any[]) => {
|
||||
emit('update:modelValue', fileList);
|
||||
};
|
||||
</script>
|
||||
34
src/views/knowledge/aiDocument/sources/QADocumentForm.vue
Normal file
34
src/views/knowledge/aiDocument/sources/QADocumentForm.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<el-col class="mb20">
|
||||
<el-form-item label="资料" prop="files">
|
||||
<upload-file
|
||||
:limit="1"
|
||||
@change="handleFileChange"
|
||||
:fileType="['xlsx']"
|
||||
/>
|
||||
<a class="link link-primary" @click="downloadTemplate">Q&A Excel 模板下载</a>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
import { downBlobFile } from "/@/utils/other";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object as PropType<any>,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const handleFileChange = (fileNames: string, fileList: any[]) => {
|
||||
emit('update:modelValue', fileList);
|
||||
}
|
||||
|
||||
const downloadTemplate = () => {
|
||||
downBlobFile('/admin/sys-file/local/file/qa.xlsx', {}, 'Q&A.xlsx');
|
||||
};
|
||||
</script>
|
||||
30
src/views/knowledge/aiDocument/sources/TextDocumentForm.vue
Normal file
30
src/views/knowledge/aiDocument/sources/TextDocumentForm.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<el-col class="mb20">
|
||||
<el-form-item label="文件名" prop="name">
|
||||
<el-input placeholder="请输入文件名" v-model="modelValue.name" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col class="mb20">
|
||||
<el-form-item label="内容" prop="content">
|
||||
<ai-editor
|
||||
v-model="modelValue.content"
|
||||
output="text"
|
||||
placeholder="选择输入文本,即可调用 AI 辅助功能"
|
||||
:minHeight="400"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Object as PropType<any>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
defineEmits(['update:modelValue']);
|
||||
</script>
|
||||
Reference in New Issue
Block a user