作者 dong

修复bug

... ... @@ -33,12 +33,12 @@
<a-col :xxl="4" :xl="6" :lg="6" :md="6" :sm="12" :xs="24">
<a-card class="add-knowledge-card" @click="handleAddKnowled">
<div class="flex">
<Icon icon="ant-design:plus-outlined" class="add-knowledge-card-icon" size="20"></Icon>
<Icon icon="ant-design:plus-outlined" class="add-knowledge-card-icon" size="20" />
<span class="add-knowledge-card-title">创建知识库</span>
</div>
</a-card>
</a-col>
<a-col v-if="knowledgeList && knowledgeList.length>0" :xxl="4" :xl="6" :lg="6" :md="6" :sm="12" :xs="24" v-for="item in knowledgeList">
<a-col v-if="knowledgeList && knowledgeList.length > 0" :xxl="4" :xl="6" :lg="6" :md="6" :sm="12" :xs="24" v-for="item in knowledgeList">
<a-card class="knowledge-card pointer" @click="handleDocClick(item.id)">
<div class="knowledge-header">
<div class="flex">
... ... @@ -53,26 +53,26 @@
<span>{{ item.descr || '暂无描述' }}</span>
</div>
<div class="knowledge-footer">
<Icon class="knowledge-footer-icon" icon="ant-design:deployment-unit-outlined" size="14"></Icon>
<Icon class="knowledge-footer-icon" icon="ant-design:deployment-unit-outlined" size="14" />
<span>{{ item.embedId_dictText }}</span>
</div>
<div class="knowledge-btn">
<a-dropdown placement="bottomRight" :trigger="['click']" :getPopupContainer="(node) => node.parentNode">
<div class="ant-dropdown-link pointer model-icon" @click.prevent.stop>
<Icon icon="ant-design:ellipsis-outlined" size="16"></Icon>
<Icon icon="ant-design:ellipsis-outlined" size="16" />
</div>
<template #overlay>
<a-menu>
<a-menu-item key="vectorization" @click.prevent.stop="handleVectorization(item.id)">
<Icon icon="ant-design:retweet-outlined" size="16"></Icon>
<Icon icon="ant-design:retweet-outlined" size="16" />
向量化
</a-menu-item>
<a-menu-item key="text" @click.prevent.stop="handleEditClick(item)">
<Icon class="pointer" icon="ant-design:edit-outlined" size="16"></Icon>
<Icon class="pointer" icon="ant-design:edit-outlined" size="16" />
编辑
</a-menu-item>
<a-menu-item key="file" @click.prevent.stop="handleDelete(item)">
<Icon class="pointer" icon="ant-design:delete-outlined" size="16"></Icon>
<Icon class="pointer" icon="ant-design:delete-outlined" size="16" />
删除
</a-menu-item>
</a-menu>
... ... @@ -95,9 +95,9 @@
size="small"
/>
<!--添加知识库弹窗-->
<KnowledgeBaseModal @register="registerModal" @success="reload"></KnowledgeBaseModal>
<KnowledgeBaseModal @register="registerModal" @success="reload" />
<!-- 知识库文档弹窗 -->
<AiragKnowledgeDocListModal @register="docListRegister"></AiragKnowledgeDocListModal>
<AiragKnowledgeDocListModal @register="docListRegister" />
</div>
</template>
... ... @@ -112,7 +112,7 @@
import JDictSelectTag from '@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import AiragKnowledgeDocListModal from './components/AiragKnowledgeDocListModal.vue';
import Icon from '@/components/Icon';
import { useMessage } from "@/hooks/web/useMessage";
import { useMessage } from '@/hooks/web/useMessage';
export default {
name: 'KnowledgeBaseList',
... ... @@ -190,7 +190,7 @@
pageNo: pageNo.value,
pageSize: pageSize.value,
column: 'createTime',
order: 'desc'
order: 'desc',
};
Object.assign(params, queryParam);
... ... @@ -255,15 +255,17 @@
* @param id
*/
async function handleVectorization(id) {
rebuild({ knowIds: id }).then((res) =>{
if(res.success){
createMessage.success("向量化成功!");
rebuild({ knowIds: id })
.then((res) => {
if (res.success) {
createMessage.success('向量化成功!');
reload();
}else{
createMessage.warning("向量化失败!");
} else {
createMessage.warning('向量化失败!');
}
}).catch(err=>{
createMessage.warning("向量化失败!");
})
.catch((err) => {
createMessage.warning('向量化失败!');
});
}
... ... @@ -346,17 +348,17 @@
color: #676f83;
}
.knowledge-footer{
.knowledge-footer {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 16px;
.knowledge-footer-icon{
.knowledge-footer-icon {
position: relative;
top: 2px
top: 2px;
}
span{
span {
margin-left: 2px;
}
}
... ... @@ -389,7 +391,7 @@
margin-bottom: 20px;
background: #fcfcfd;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
border-radius: 10px;
display: inline-flex;
... ... @@ -407,14 +409,14 @@
}
.add-knowledge-card-title {
font-size: 16px;
color:#1f2329;
color: #1f2329;
font-weight: 400;
align-self: center;
}
}
.add-knowledge-card:hover {
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}
.knowledge-card {
... ... @@ -424,11 +426,11 @@
border-radius: 10px;
background: #fcfcfd;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.knowledge-card:hover {
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
.knowledge-btn {
display: block;
}
... ... @@ -452,17 +454,17 @@
}
}
.model-icon{
.model-icon {
background-color: unset;
border: none;
margin-right: 2px;
}
.model-icon:hover{
.model-icon:hover {
color: #000000;
background-color: rgba(0,0,0,0.05);
background-color: rgba(0, 0, 0, 0.05);
border: none;
}
.ant-dropdown-link{
.ant-dropdown-link {
font-size: 14px;
height: 24px;
padding: 0 7px;
... ... @@ -471,7 +473,7 @@
text-align: center;
}
.ellipsis{
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
... ...
... ... @@ -24,7 +24,7 @@ export const searchFormSchema: FormSchema[] = [
{
label: '问题',
field: 'question',
component: 'JInput',
component: 'Input',
},
// 新增知识库选择字段
{
... ...
... ... @@ -23,40 +23,40 @@ export const listKnowledge = async () => {
const res = await defHttp.get({ url: Api.listKnowledge });
return res;
} catch (error) {
console.error("Error fetching knowledge list:", error);
console.error('Error fetching knowledge list:', error);
return [];
}
}
};
export const list = async (params) => {
try {
const res = await defHttp.get({
url: Api.list,
params: { ...params, size: 1000 }
params: { ...params, size: 1000 },
});
if (res?.records && Array.isArray(res.records)) {
res.records = res.records.map(item => ({
res.records = res.records.map((item) => ({
...item,
...item.metadata,
question: item.question || '',
answer: item.answer || '',
knowledgeName: item.knowledgeName || '',
knowledgeId: item.knowledgeId || ''
knowledgeId: item.knowledgeId || '',
}));
}
return res;
} catch (error) {
console.error("Error fetching question embeddings:", error);
console.error('Error fetching question embeddings:', error);
return {
records: [],
total: 0,
size: 10,
current: 1,
pages: 0
pages: 0,
};
}
}
};
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
... ...
... ... @@ -13,19 +13,19 @@ export const columns: BasicColumn[] = [
title: '知识库',
align: 'center',
dataIndex: 'knowledgeName',
width: 150
width: 150,
},
{
title: '问题',
align: 'center',
dataIndex: 'question',
width: 250
width: 250,
},
{
title: '回答',
align: 'center',
dataIndex: 'answer',
width: 300
width: 300,
},
];
... ... @@ -39,20 +39,20 @@ export const searchFormSchema: FormSchema[] = [
labelField: 'name',
valueField: 'id',
},
colProps: { span: 8 }
colProps: { span: 8 },
},
{
field: 'question',
label: '问题',
component: 'Input',
colProps: { span: 8 }
colProps: { span: 8 },
},
{
field: 'answer',
label: '回答',
component: 'Input',
colProps: { span: 8 },
}
},
];
export const formSchema: FormSchema[] = [
... ... @@ -71,14 +71,15 @@ export const formSchema: FormSchema[] = [
api: listKnowledge,
labelField: 'name',
valueField: 'id',
}
showSearch: true,
},
},
{
field: 'question',
label: '问题',
component: 'Input',
required: true,
colProps: { span: 24 }
colProps: { span: 24 },
},
{
field: 'answer',
... ... @@ -90,16 +91,16 @@ export const formSchema: FormSchema[] = [
showCount: true,
autoSize: {
minRows: 5,
maxRows: 10
}
}
}
maxRows: 10,
},
},
},
];
export const superQuerySchema = {
knowledgeId: { title: '知识库', order: 0, view: 'text', type: 'string' },
question: { title: '问题', order: 1, view: 'text', type: 'string' },
answer: { title: '回答', order: 2, view: 'text', type: 'string' }
answer: { title: '回答', order: 2, view: 'text', type: 'string' },
};
export function getBpmFormSchema(_formData): FormSchema[] {
... ...
... ... @@ -4,17 +4,11 @@
<template #tableTitle>
<a-button type="primary" @click="handleAdd" preIcon="ant-design:plus-outlined">新增</a-button>
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button>
<a-button
type="primary"
preIcon="ant-design:import-outlined"
@click="handleOpenImportModal"
>导入ZIP</a-button>
<a-button type="primary" preIcon="ant-design:import-outlined" @click="handleOpenImportModal">导入ZIP</a-button>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="batchHandleDelete">
<Icon icon="ant-design:delete-outlined" />删除
</a-menu-item>
<a-menu-item key="1" @click="batchHandleDelete"> <Icon icon="ant-design:delete-outlined" />删除 </a-menu-item>
</a-menu>
</template>
<a-button>批量操作<Icon icon="mdi:chevron-down" /></a-button>
... ... @@ -36,30 +30,13 @@
:okButtonProps="{ disabled: !selectedKnowledgeId || !currentFile }"
>
<BasicForm @register="registerImportForm" :showActionButtonGroup="false" />
<div style="margin-top: 20px;margin-left: 5%">
<input
type="file"
ref="fileInput"
accept=".zip"
@change="handleFileChange"
style="display: none"
>
<a-button
type="primary"
@click="triggerFileInput"
>
<UploadOutlined /> 选择ZIP文件
</a-button>
<div v-if="currentFile" style="margin-top: 8px;">
已选择文件: {{ currentFile.name }} ({{ formatFileSize(currentFile.size) }})
</div>
<div v-if="uploadProgress > 0" style="margin-top: 8px;">
<div style="margin-top: 20px; margin-left: 5%">
<input type="file" ref="fileInput" accept=".zip" @change="handleFileChange" style="display: none" />
<a-button type="primary" @click="triggerFileInput"> <UploadOutlined /> 选择ZIP文件 </a-button>
<div v-if="currentFile" style="margin-top: 8px"> 已选择文件: {{ currentFile.name }} ({{ formatFileSize(currentFile.size) }}) </div>
<div v-if="uploadProgress > 0" style="margin-top: 8px">
上传进度: {{ uploadProgress }}%
<a-progress
:percent="uploadProgress"
:stroke-color="progressColor"
:status="uploadStatus"
/>
<a-progress :percent="uploadProgress" :stroke-color="progressColor" :status="uploadStatus" />
</div>
</div>
</BasicModal>
... ... @@ -67,32 +44,32 @@
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { UploadOutlined } from '@ant-design/icons-vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { BasicModal } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import { message } from 'ant-design-vue';
import QuestionEmbeddingModal from './components/QuestionEmbeddingModal.vue';
import { columns, searchFormSchema } from './QuestionEmbedding.data';
import { list, deleteOne, batchDelete, getImportZipUrl, getExportUrl, listKnowledge } from './QuestionEmbedding.api';
import { useUserStore } from '/@/store/modules/user';
import { getToken } from '/@/utils/auth';
import { ref, reactive } from 'vue';
import { UploadOutlined } from '@ant-design/icons-vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { BasicModal } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import { message } from 'ant-design-vue';
import QuestionEmbeddingModal from './components/QuestionEmbeddingModal.vue';
import { columns, searchFormSchema } from './QuestionEmbedding.data';
import { list, deleteOne, batchDelete, getImportZipUrl, getExportUrl, listKnowledge } from './QuestionEmbedding.api';
import { useUserStore } from '/@/store/modules/user';
import { getToken } from '/@/utils/auth';
const queryParam = reactive<any>({});
const [registerModal, { openModal }] = useModal();
const importModalVisible = ref(false);
const selectedKnowledgeId = ref('');
const fileInput = ref<HTMLInputElement | null>(null);
const currentFile = ref<File | null>(null);
const uploadProgress = ref(0);
const progressColor = ref('#1890ff');
const uploadStatus = ref<'normal' | 'exception' | 'active' | 'success'>('normal');
const queryParam = reactive<any>({});
const [registerModal, { openModal }] = useModal();
const importModalVisible = ref(false);
const selectedKnowledgeId = ref('');
const fileInput = ref<HTMLInputElement | null>(null);
const currentFile = ref<File | null>(null);
const uploadProgress = ref(0);
const progressColor = ref('#1890ff');
const uploadStatus = ref<'normal' | 'exception' | 'active' | 'success'>('normal');
// 知识库选择表单
const [registerImportForm] = useForm({
// 知识库选择表单
const [registerImportForm] = useForm({
labelWidth: 100,
showActionButtonGroup: false,
schemas: [
... ... @@ -108,13 +85,13 @@ const [registerImportForm] = useForm({
valueField: 'id',
onChange: (value: string) => {
selectedKnowledgeId.value = value;
}
},
},
},
],
});
});
const { tableContext, onExportXls } = useListPage({
const { tableContext, onExportXls } = useListPage({
tableProps: {
title: '问答向量库',
api: list,
... ... @@ -122,38 +99,38 @@ const { tableContext, onExportXls } = useListPage({
formConfig: {
schemas: searchFormSchema,
autoSubmitOnEnter: true,
showAdvancedButton: true
showAdvancedButton: true,
},
actionColumn: {
width: 120,
fixed: 'right'
fixed: 'right',
},
beforeFetch: (params) => Object.assign(params, queryParam)
beforeFetch: (params) => Object.assign(params, queryParam),
},
exportConfig: {
name: '问答向量库',
url: getExportUrl
}
});
url: getExportUrl,
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
function handleOpenImportModal() {
function handleOpenImportModal() {
importModalVisible.value = true;
selectedKnowledgeId.value = '';
resetFileInput();
}
}
function handleImportCancel() {
function handleImportCancel() {
importModalVisible.value = false;
resetFileInput();
}
}
function triggerFileInput() {
function triggerFileInput() {
fileInput.value?.click();
}
}
function handleFileChange(e: Event) {
function handleFileChange(e: Event) {
const input = e.target as HTMLInputElement;
if (input.files && input.files.length > 0) {
currentFile.value = input.files[0];
... ... @@ -162,35 +139,35 @@ function handleFileChange(e: Event) {
console.log('文件选择成功:', {
name: currentFile.value.name,
size: currentFile.value.size,
type: currentFile.value.type
type: currentFile.value.type,
});
} else {
currentFile.value = null;
}
}
}
function resetFileInput() {
function resetFileInput() {
if (fileInput.value) {
fileInput.value.value = '';
}
currentFile.value = null;
uploadProgress.value = 0;
uploadStatus.value = 'normal';
}
}
function formatFileSize(bytes: number): string {
function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
}
// 获取用户token
const userStore = useUserStore();
const token = getToken();
// 获取用户token
const userStore = useUserStore();
const token = getToken();
async function handleImportSubmit() {
async function handleImportSubmit() {
if (!selectedKnowledgeId.value || !currentFile.value) {
message.warning('请完整填写上传信息');
return;
... ... @@ -205,14 +182,14 @@ async function handleImportSubmit() {
uploadStatus.value = 'active';
// 使用fetch API并添加认证头
const response = await fetch("/jeecgboot" + getImportZipUrl, {
const response = await fetch('/jeecgboot' + getImportZipUrl, {
method: 'POST',
body: formData,
headers: {
'X-Access-Token': token, // JEECG标准认证头
'Authorization': `Bearer ${token}` // 备用认证头
Authorization: `Bearer ${token}`, // 备用认证头
},
credentials: 'include' // 携带cookie
credentials: 'include', // 携带cookie
});
if (response.status === 401) {
... ... @@ -238,68 +215,66 @@ async function handleImportSubmit() {
message.error(error.message || '上传失败');
console.error('上传错误:', error);
}
}
}
function handleSuperQuery(params: any) {
function handleSuperQuery(params: any) {
Object.assign(queryParam, params);
reload();
}
}
function handleAdd() {
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });
}
}
function handleEdit(record: any) {
function handleEdit(record: any) {
openModal(true, { record, isUpdate: true, showFooter: true });
}
}
function handleDetail(record: any) {
function handleDetail(record: any) {
openModal(true, { record, isUpdate: true, showFooter: false });
}
}
async function handleDelete(record: any) {
async function handleDelete(record: any) {
await deleteOne({ id: record.id }, handleSuccess);
}
}
async function batchHandleDelete() {
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
}
function handleSuccess() {
function handleSuccess() {
selectedRowKeys.value = [];
reload();
}
}
function getTableAction(record: any) {
return [
{ label: '编辑', onClick: handleEdit.bind(null, record) }
];
}
function getTableAction(record: any) {
return [{ label: '编辑', onClick: handleEdit.bind(null, record) }];
}
function getDropDownAction(record: any) {
function getDropDownAction(record: any) {
return [
{ label: '详情', onClick: handleDetail.bind(null, record) },
{
label: '删除',
popConfirm: {
title: '确认删除此问答?',
confirm: handleDelete.bind(null, record)
}
}
confirm: handleDelete.bind(null, record),
},
},
];
}
}
</script>
<style scoped>
.upload-area {
.upload-area {
margin-top: 20px;
padding: 20px;
border: 1px dashed #d9d9d9;
border-radius: 4px;
text-align: center;
cursor: pointer;
}
.upload-area:hover {
}
.upload-area:hover {
border-color: #1890ff;
}
}
</style>
... ...
... ... @@ -39,6 +39,7 @@
async function handleSubmit() {
try {
const values = await validate();
console.log(values);
setModalProps({ confirmLoading: true });
await saveOrUpdate(values, isUpdate.value);
closeModal();
... ...
... ... @@ -182,6 +182,7 @@
* 编辑事件
*/
function handleEdit(record: Recordable) {
console.log('当前行数据:', record);
openModal(true, {
record,
isUpdate: true,
... ... @@ -220,6 +221,7 @@
* 操作栏
*/
function getTableAction(record) {
console.log('record', record);
return [
{
label: '编辑',
... ...
... ... @@ -199,6 +199,12 @@ export const formSchema: FormSchema[] = [
},
},
},
{
label: '',
field: 'docId',
component: 'Input',
show: false,
},
// TODO 主键隐藏字段,目前写死为ID
{
label: '',
... ...
... ... @@ -36,6 +36,7 @@
try {
const metadata = typeof formData.metadata === 'string' ? JSON.parse(formData.metadata) : formData.metadata;
formData.docName = metadata.docName || '未命名';
formData.docId = metadata.docId || 'null';
// 关键修复:设置知识库ID
formData.knowledgeId = metadata.knowledgeId;
... ... @@ -53,19 +54,25 @@
//设置标题
const title = computed(() => (!unref(isUpdate) ? '新增' : !unref(isDetail) ? '详情' : '编辑'));
//表单提交事件
async function handleSubmit() {
async function handleSubmit(record) {
console.log('22222222222222', record);
try {
let values = await validate();
console.log('表单数据:', values);
const metadata = {
knowledgeId: values.knowledgeId,
docId: values.docId,
};
console.log('values', values);
// 仅保留需要的字段,其他由后端自动生成
const payload = {
id: values.id,
docName: values.docName, // 文件名称
text: values.text, // 文件内容
metadata: metadata,
knowledgeId: values.knowledgeId,
docId: values.docId,
};
setModalProps({ confirmLoading: true });
//提交表单
... ...