fetch: 页面移植

知识库接口对接
This commit is contained in:
杨谢雨 2025-02-28 17:38:36 +08:00
parent 6327866533
commit 05609d63c2
12 changed files with 595 additions and 234 deletions

View File

@ -26,36 +26,36 @@ export interface DocsVO {
export const DocsApi = {
// 获得文档分页
async getDocsPage(params: any) {
return await request.get({ url: '/aigc/docs/page', params })
return await request.get({ url: '/chat/aigc/docs/page', params })
},
// 获得文档列表
async getDocsList(params: any) {
return await request.get({ url: '/aigc/docs/list', params })
return await request.get({ url: '/chat/aigc/docs/list', params })
},
// 获得文档详情
async getDocsById(id: string) {
return await request.get({ url: `/aigc/docs/${id}` })
return await request.get({ url: `/chat/aigc/docs/${id}` })
},
// 创建文档
async addDocs(data: any) {
return await request.post({ url: '/aigc/docs', data })
return await request.post({ url: '/chat/aigc/docs', data })
},
// 更新文档
async updateDocs(data: any) {
return await request.put({ url: '/aigc/docs', data })
return await request.put({ url: '/chat/aigc/docs', data })
},
// 删除文档
async deleteDocs(id: string) {
return await request.delete({ url: `/aigc/docs/${id}` })
return await request.delete({ url: `/chat/aigc/docs/${id}` })
},
// 重新向量化
async reEmbedDocs(id: string) {
return await request.get({ url: `/aigc/embedding/re-embed/${id}` })
return await request.get({ url: `/chat/aigc/embedding/re-embed/${id}` })
}
}

View File

@ -5,12 +5,12 @@ import { AxiosProgressEvent } from 'axios'
export const EmbeddingApi = {
// 文本嵌入
embeddingText: async (params: any) => {
return await request.post({ url: '/aigc/embedding/text', params })
return await request.post({ url: '/chat/aigc/embedding/text', params })
},
// 嵌入搜索
embeddingSearch: async (data: any) => {
return await request.post({ url: '/aigc/embedding/search', data })
return await request.post({ url: '/chat/aigc/embedding/search', data })
},
// 文档嵌入
@ -20,7 +20,7 @@ export const EmbeddingApi = {
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void
) => {
return await request.post({
url: `/aigc/embedding/docs/${knowledgeId}`,
url: `/chat/aigc/embedding/docs/${knowledgeId}`,
data,
headers: {
'Content-Type': 'multipart/form-data'

View File

@ -10,31 +10,31 @@ export interface KnowledgeVO {
export const KnowledgeApi = {
// 获得知识库列表
getKnowledgeList: async (params: any) => {
return await request.get({ url: '/aigc/knowledge/list', params })
return await request.get({ url: '/chat/aigc/knowledge/list', params })
},
// 获得知识库分页
getKnowledgePage: async (params: any) => {
return await request.get({ url: '/aigc/knowledge/page', params })
return await request.get({ url: '/chat/aigc/knowledge/page', params })
},
// 获得知识库详情
getKnowledge: async (id: string) => {
return await request.get({ url: `/aigc/knowledge/${id}` })
return await request.get({ url: `/chat/aigc/knowledge/${id}` })
},
// 创建知识库
createKnowledge: async (data: any) => {
return await request.post({ url: '/aigc/knowledge', data })
return await request.post({ url: '/chat/aigc/knowledge', data })
},
// 更新知识库
updateKnowledge: async (data: any) => {
return await request.put({ url: '/aigc/knowledge', data })
return await request.put({ url: '/chat/aigc/knowledge', data })
},
// 删除知识库
deleteKnowledge: async (id: string) => {
return await request.delete({ url: `/aigc/knowledge/${id}` })
return await request.delete({ url: `/chat/aigc/knowledge/${id}` })
}
}

View File

@ -26,31 +26,31 @@ export interface SliceVO {
export const SliceApi = {
// 获得切片分页
getSlicePage: async (params: any) => {
return await request.get({ url: '/aigc/docs/slice/page', params })
return await request.get({ url: '/chat/aigc/docs/slice/page', params })
},
// 获得切片列表
getSliceList: async (params: any) => {
return await request.get({ url: '/aigc/docs/slice/list', params })
return await request.get({ url: '/chat/aigc/docs/slice/list', params })
},
// 获得切片详情
getSlice: async (id: string) => {
return await request.get({ url: `/aigc/docs/slice/${id}` })
return await request.get({ url: `/chat/aigc/docs/slice/${id}` })
},
// 创建切片
createSlice: async (data: any) => {
return await request.post({ url: '/aigc/docs/slice', data })
return await request.post({ url: '/chat/aigc/docs/slice', data })
},
// 更新切片
updateSlice: async (data: any) => {
return await request.put({ url: '/aigc/docs/slice', data })
return await request.put({ url: '/chat/aigc/docs/slice', data })
},
// 删除切片
deleteSlice: async (id: string) => {
return await request.delete({ url: `/aigc/docs/slice/${id}` })
return await request.delete({ url: `/chat/aigc/docs/slice/${id}` })
}
}

View File

@ -0,0 +1,103 @@
<template>
<el-dialog v-model="visible" title="创建知识库" width="800px" :close-on-click-modal="!loading" :close-on-press-escape="!loading" @close="handleClose">
<Form :schema="formSchemas" v-if="visible" :model="formData" ref="formRef" @register="register"/>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { FormSchema } from '@/types/form';
import {Form} from '@/components/Form'
import { useForm } from '@/hooks/web/useForm';
import { ref } from 'vue';
import { KnowledgeApi } from '@/api/new-ai/knowledge';
import { ElMessage } from 'element-plus';
const { register, methods, elFormRef } = useForm();
const visible = ref(false);
const loading = ref(false);
const formRef = ref();
const formData = ref({})
const emits = defineEmits(['reload'])
const handleSubmit = async () => {
try {
await elFormRef.value?.validate()
const data = await methods.getFormData();
loading.value = true;
await KnowledgeApi.createKnowledge(data).finally(() => loading.value = false);
ElMessage.success('创建成功');
handleClose();
emits('reload');
} catch (error) {
console.error('创建失败:', error);
ElMessage.error('创建失败');
} finally {
loading.value = false;
}
};
const handleClose = () => {
visible.value = false;
nextTick(() => {
formRef.value?.clearForm()
formRef.value?.setSchema([])
})
};
const formSchemas = computed(() => [
{
field: 'name',
component: 'Input',
label: '知识库名称',
componentProps: {
placeholder: '请输入知识库名称',
},
formItemProps: {
rules: [{ required: true, message: '请输入知识库名称', trigger: ['blur'] }],
}
},
{
field: 'embedStoreId',
label: '向量数据库',
component: 'Input',
formItemProps: {
rules: [{ required: true, message: '请选择关联向量数据库', trigger: ['blur'] }],
}
},
{
field: 'embedModelId',
label: '向量模型',
component: 'Input',
formItemProps: {
rules: [{ required: true, message: '请选择关联向量模型', trigger: ['blur'] }],
}
},
{
field: 'des',
component: 'Input',
label: '知识库描述',
componentProps: {
placeholder: '请输入知识库描述',
type: 'textarea',
autosize: {
minRows: 3,
maxRows: 3,
},
},
formItemProps: {
rules: [{ required: true, message: '请输入知识库描述', trigger: ['blur'] }],
}
},
])
const open = async () => {
visible.value = true;
}
//
defineExpose({
open
});
</script>
<script></script>

View File

@ -1,76 +1,66 @@
<script setup lang="ts">
import {useTable} from "@/hooks/web/useTable";
import {FormSchema} from "@/types/form";
import { useTable } from '@/hooks/web/useTable'
import { FormSchema } from '@/types/form'
import { ref } from 'vue'
import { SliceApi } from '@/api/new-ai/slice'
import {ElMessage} from 'element-plus'
const props = defineProps({
knowledgeData: {
type: Object,
required: true
}
})
import { ElMessage, ElTag } from 'element-plus'
import { DocsApi } from '@/api/new-ai/docs'
import { Delete } from '@element-plus/icons-vue'
const tableRef = ref()
const elTableRef = ref()
const docsList = ref([])
const columns = [
{
label: '文档名称',
field: 'text',
key: 'text',
width: 200,
field: 'name',
width: 200
},
{
label: '字符数',
field: 'text',
key: 'text',
width: 200,
field: 'wordNum',
width: 200
},
{
label: '切片内容',
field: 'text',
key: 'text',
width: 200,
field: 'content',
width: 200
},
{
label: '切片状态',
field: 'text',
key: 'text',
field: 'status',
width: 200,
formatter(row) {
return h(
ElTag,
{
size: 'small',
type: row.status == true ? 'success' : 'info'
},
{
default: () => (row.status == true ? '已训练' : '未训练')
}
)
}
},
{
label: '切片时间',
field: 'text',
field: 'createTime'
},
{
label: '操作',
field: 'text',
key: 'text',
width: 200,
field: 'action',
width: 200
}
]
const { tableObject, register, tableMethods } = useTable({
getListApi: async (params) => {
const res = await SliceApi.getSlicePage({
...params,
knowledgeId: props.knowledgeData.id
})
return {
list: res.data.list || [],
total: res.data.total || 0
}
getListApi: SliceApi.getSlicePage,
delListApi: SliceApi.deleteSlice,
defaultParams: {
knowledgeId: useRoute().params.id
},
delListApi: async (ids) => {
await Promise.all(ids.map(id => SliceApi.deleteSlice(id)))
ElMessage.success('删除成功')
},
response: {
list: 'list',
total: 'total'
}
})
const schema = ref<FormSchema[]>([
const schema = computed(() => {
return [
{
label: '所属文档',
field: 'docId',
@ -79,43 +69,49 @@ const schema = ref<FormSchema[]>([
style: {
width: '150px'
},
placeholder: '请选择所属文档'
placeholder: '请选择所属文档',
options: docsList.value.map((item: any) => {
return {
label: item.name,
value: item.id
}
})
}
}
])
]
})
const pagination = computed(() => {
return {
pageSize: tableObject.pageSize,
currentPage: tableObject.currentPage,
total: tableObject.total,
total: tableObject.total
}
})
onMounted(() => {
register(tableRef,elTableRef,)
onMounted(async () => {
tableMethods.getList()
})
</script>
<template>
<div>
<Search :schema="schema" @search="tableMethods.getList()" />
<Table
class="mt-20px"
ref="tableRef"
v-model:table="tableObject"
border
:data="tableObject.tableList"
:columns="columns"
:schema="schema"
:label-width="100"
:search-table-height="true">
:pagination="pagination"
@register="register"
:search-table-height="true"
>
<template #action="{ row }">
<el-button
link
type="danger"
@click="tableMethods.delList([row.id], false)">
删除
</el-button>
<el-button text :icon="Delete" type="danger" @click="tableMethods.delList(row.id, false)" />
</template>
</Table>
</div>
</template>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@ -1,84 +1,93 @@
<script setup lang="ts">
import {useTable} from "@/hooks/web/useTable";
import {FormSchema} from "@/types/form";
import { useTable } from '@/hooks/web/useTable'
import { FormSchema } from '@/types/form'
import { ref } from 'vue'
import { DocsApi } from '@/api/new-ai/docs'
import {ElMessage, ElMessageBox} from 'element-plus'
const props = defineProps({
knowledgeData: {
type: Object,
required: true
}
})
import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
import { useRouter, useRoute } from 'vue-router'
import { Refresh, Delete, Edit } from '@element-plus/icons-vue'
import EditDoc from './edit-doc.vue'
const editDocRef = ref()
const route = useRoute()
const tableRef = ref()
const elTableRef = ref()
const columns = [
{
label: '文档名称',
field: 'text',
key: 'text',
width: 200,
field: 'name',
width: 200
},
{
label: '文档链接',
field: 'text',
key: 'text',
width: 200,
field: 'url',
width: 200
},
{
label: '文档来源',
field: 'text',
key: 'text',
field: 'type',
width: 200,
formatter(row) {
return h(
ElTag,
{
size: 'small',
type: row.type == 'UPLOAD' ? 'success' : 'info',
},
{
default: () => (row.type == 'UPLOAD' ? '上传' : '录入'),
}
);
},
},
{
label: '切片数量',
field: 'text',
key: 'text',
width: 200,
field: 'sliceNum',
width: 200
},
{
label: '切片状态',
field: 'text',
key: 'text',
field: 'status',
width: 200,
formatter(row) {
return h(
ElTag,
{
size: 'small',
type: row.sliceStatus == true ? 'success' : 'info',
},
{
default: () => (row.sliceStatus == true ? '已训练' : '未训练'),
}
);
},
},
{
label: '文件大小',
field: 'text',
field: 'size',
formatter(rowData) {
return (Number(rowData.size) / 1000000).toFixed(2) + ' MB';
}
},
{
label: '操作',
field: 'text',
key: 'text',
width: 200,
field: 'action',
width: 200
}
]
const { tableObject, register, tableMethods } = useTable({
getListApi: async (params) => {
const res = await DocsApi.getDocsPage({
...params,
knowledgeId: props.knowledgeData.id
getListApi: DocsApi.getDocsPage,
delListApi: DocsApi.deleteDocs,
defaultParams: {
knowledgeId: route.params.id
}
})
const pagination = computed(() => {
return {
list: res.data.list || [],
total: res.data.total || 0
}
},
delListApi: async (ids) => {
await Promise.all(ids.map(id => DocsApi.deleteDocs(id)))
ElMessage.success('删除成功')
},
response: {
list: 'list',
total: 'total'
total: tableObject.total,
pageSize: tableObject.pageSize,
currentPage: tableObject.currentPage
}
})
const schema = ref<FormSchema[]>([
{
label: '文档名称',
@ -106,37 +115,37 @@ const handleReEmbed = async (row) => {
console.error('Failed to re-embed:', error)
}
}
//
register(tableRef as any ,elTableRef as any)
const openEdit = (row) => {
editDocRef.value.show(route.params.id, row.id) // docId
}
onMounted(() => {
tableMethods.getList()
})
</script>
<template>
<div>
<Search :schema="schema" @search="tableMethods.getList()" />
<Table
class="mt-20px"
ref="tableRef"
v-model:table="tableObject"
border
:data="tableObject.tableList"
:columns="columns"
:schema="schema"
:label-width="100"
:search-table-height="true">
:pagination="pagination"
:search-table-height="true"
@register="register"
>
<template #action="{ row }">
<el-button
link
type="primary"
@click="handleReEmbed(row)">
重新向量化
</el-button>
<el-button
link
type="danger"
@click="tableMethods.delList([row.id])">
删除
</el-button>
<el-button text type="primary" :icon="Refresh" @click="handleReEmbed(row)" />
<el-button text type="primary" :icon="Edit" @click="openEdit(row)" />
<el-button text :icon="Delete" type="danger" @click="tableMethods.delList(row.id, false)" />
</template>
</Table>
<edit-doc ref="editDocRef" @reload="tableMethods.getList()" />
</div>
</template>
<style scoped>
</style>
<style scoped></style>

View File

@ -1,48 +1,79 @@
<script setup lang="ts">
import { Management } from '@element-plus/icons-vue'
<script lang="ts" setup>
import { ref } from 'vue'
import { Document } from '@element-plus/icons-vue'
import { EmbeddingApi } from '@/api/new-ai/embedding'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
const { embeddingSearch, embeddingText } = EmbeddingApi
const route = useRoute()
const content = ref('')
const loading = ref(false)
const list = ref<any>([])
async function onSearch(flag = false) {
if (content.value === '' && flag) {
list.value = []
ElMessage.warning('请先输入搜索内容')
return
}
loading.value = true
try {
list.value = await embeddingSearch({
content: content.value,
knowledgeId: route.params.id
})
console.log(list.value)
} catch (error) {
} finally {
loading.value = false
}
}
onMounted(() => {
onSearch()
})
</script>
<template>
<div class="flex flex-wrap wrap">
<el-card class="mb-20px ">
<template #header>
<el-button class="w-full">向量搜索</el-button>
</template>
<el-input rows="10" type="textarea" />
<div class="h-full w-full">
<div class="flex gap-10px my-10px flex-wrap">
<el-card class="w-[calc(25%-10px)]">
<el-button class="w-full mb-10px" :loading="loading" type="primary" @click="onSearch(true)">向量搜索</el-button>
<el-input
v-model="content"
placeholder="请输入关键词查询向量文本"
:rows="10"
type="textarea"
/>
</el-card>
<el-card v-for="index in 5" :key="index" class="mb-20px ">
<template v-if="list.length > 0">
<el-card
v-for="item in list"
:key="item.index"
class="rounded-lg cursor-pointer w-[calc(25%-10px)] bg-[var(--el-bg-color-pages)]"
shadow="hover"
>
<template #header>
<div class="flex items-center">
<el-icon class="mr-10px ">
<Management class="text-18px color-[var(--el-color-primary)]"/>
<div class="flex items-center gap-1">
<el-icon>
<Document />
</el-icon>
<span class="">首席信息</span>
<el-text class="truncate">{{ item.docsName }}</el-text>
</div>
</template>
<div class="inner"></div>
<el-scrollbar height="200px" class="text-14px">
{{ item.text }}
</el-scrollbar>
</el-card>
</template>
<el-empty v-if="list.length === 0" class="my-4 flex-1" />
</div>
</div>
</template>
<style scoped lang="scss">
.wrap {
&>div {
width: calc((100% - 40px) / 3);
margin-right: 20px;
&:nth-child(3n) {
margin-right: 0px;
}
}
}
.inner {
height: 220px;
}
.el-card:not(:first-child) {
background-color: mix(grey, #fff, 10%);
}
//div {
// display: grid;
// grid-template-columns: repeat(3, 1fr);
// gap: 20px;
<style lang="scss" scoped>
//a{
// color: var(--el-bg-color-page);
//}
</style>

View File

@ -1,11 +1,97 @@
<script setup lang="ts">
import {UploadFile} from '@/components/UploadFile'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Upload } from '@element-plus/icons-vue'
import { EmbeddingApi } from '@/api/new-ai/embedding'
const route = useRoute()
const fileList = ref<any[]>([])
const uploadRef = ref()
const handleUpload = async (file: File) => {
const knowledgeId = route.params.id as string
try {
const formData = new FormData()
formData.append('file', file)
await EmbeddingApi.embeddingDocs(knowledgeId, formData, (event: any) => {
if (event.lengthComputable) {
const progress = Math.round((event.loaded * 100) / event.total)
console.log('Upload progress:', progress)
}
})
ElMessage.success('上传成功,文档解析中...')
} catch (error) {
console.error('Upload failed:', error)
ElMessage.error('上传失败')
}
}
const handleExceed = () => {
ElMessage.warning('每次只能上传一个文件')
}
</script>
<template>
<UploadFile drag />
<div class="upload-container">
<el-upload
ref="uploadRef"
class="upload-dragger"
drag
:auto-upload="false"
:limit="1"
accept=".doc,.docx,.pdf,.txt,.md"
:on-exceed="handleExceed"
:on-change="(file) => handleUpload(file.raw as File)"
>
<el-icon class="el-icon--upload"><Upload /></el-icon>
<div class="el-upload__text">
拖拽文件到此处或 <em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
支持的文件格式.txt.md.docx.doc.pdf
</div>
</template>
</el-upload>
</div>
</template>
<style scoped lang="scss">
<style scoped>
.upload-container {
padding: 20px;
}
.upload-dragger {
width: 100%;
}
.el-upload__tip {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-top: 10px;
}
:deep(.el-upload-dragger) {
width: 100%;
height: 200px;
}
.el-icon--upload {
font-size: 48px;
color: var(--el-text-color-regular);
margin-bottom: 12px;
}
.el-upload__text {
color: var(--el-text-color-regular);
font-size: 16px;
margin: 8px 0;
}
.el-upload__text em {
color: var(--el-color-primary);
font-style: normal;
}
</style>

View File

@ -0,0 +1,117 @@
<!--
- Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
-
- Licensed under the GNU Affero General Public License, Version 3 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- https://www.gnu.org/licenses/agpl-3.0.html
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-->
<script setup lang="ts">
import { nextTick, ref } from 'vue'
import { DocsApi } from '@/api/new-ai/docs'
import { ElMessage } from 'element-plus'
import { FormSchema } from '@/types/form'
import { isNullOrWhitespace } from '@/utils/is'
import { useForm } from '@/hooks/web/useForm'
import { useRoute } from 'vue-router'
const route = useRoute()
const emit = defineEmits(['reload'])
const showModal = ref(false)
const loading = ref(false)
const { methods, elFormRef, register } = useForm()
const formRef = ref()
const editFlag = ref(false)
const formSchema = ref<FormSchema[]>([
{
label: '文档名称',
field: 'name',
component: 'Input',
componentProps: {
placeholder: '请输入文档名称'
},
formItemProps: {
rules: [{ required: true, message: '请输入文档名称', trigger: ['blur'] }]
}
},
{
label: '文档内容',
field: 'content',
component: 'Input',
componentProps: {
placeholder: '请输入文档内容',
type: 'textarea',
autosize: {
minRows: 10,
maxRows: 20
}
},
formItemProps: {
rules: [{ required: true, message: '请输入文档内容', trigger: ['blur'] }]
}
}
])
async function show(knowledgeId: string, id?: string) {
showModal.value = true
await nextTick()
if (id) {
editFlag.value = true
const res = await DocsApi.getDocsById(id)
methods.setValues(res)
}
}
async function handleSubmit() {
await elFormRef.value?.validate()
const formData =await methods.getFormData() as Record<string, any>
console.log(formData)
formData.knowledgeId = route.params.id as string
if (!formData.name) {
ElMessage.error('请完善表单')
return
}
try {
if (isNullOrWhitespace(formData.id)) {
await DocsApi.addDocs(formData)
ElMessage.success('新增成功')
} else {
await DocsApi.updateDocs(formData)
ElMessage.success('修改成功')
}
showModal.value = false
emit('reload')
} catch (error) {
ElMessage.error('操作失败')
}
}
defineExpose({ show })
</script>
<template>
<el-dialog
v-model="showModal"
:title="editFlag ? '编辑文档' : '新增文档'"
width="500px"
:close-on-click-modal="!loading"
:close-on-press-escape="!loading"
destroy-on-close
>
<Form :schema="formSchema" ref="formRef" @register="register" />
<template #footer>
<el-button @click="showModal = false" :loading="loading">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
</template>
</el-dialog>
</template>
<style lang="less" scoped></style>

View File

@ -8,11 +8,24 @@ import DataDocument from "@/views/knowledge/dataset-form/components/data-documen
import DataEmbedding from "@/views/knowledge/dataset-form/components/data-embedding.vue";
import {KnowledgeApi} from '@/api/new-ai/knowledge'
import {ElMessage} from 'element-plus'
interface Knowledge {
id: string
embedStore?: {
id: string
name: string
}
embedModel?: {
id: string
name: string
}
}
const route = useRoute()
const router = useRouter()
const active = ref('1')
const knowledgeData = ref({})
const loading = ref(false)
const knowledgeData = ref<Knowledge>({
id: '',
})
const tabs = ref([
{
@ -42,11 +55,13 @@ const tabs = ref([
])
const loadKnowledge = async () => {
const id = route.query.id as string
const id = route.params.id as string
console.log(id)
if (id) {
try {
const res = await KnowledgeApi.getKnowledge(id)
knowledgeData.value = res.data
loading.value = true;
const res = await KnowledgeApi.getKnowledge(id).finally(() => loading.value = false)
knowledgeData.value = res
} catch (error) {
console.error('Failed to load knowledge:', error)
ElMessage.error('加载知识库失败')
@ -60,14 +75,14 @@ onMounted(() => {
</script>
<template>
<div class="children flex">
<div class="flex flex-col mr-30px w-350px">
<div class="children flex" v-loading="loading">
<div class="flex flex-col mr-30px w-350px bg-white pt-10px">
<el-button
class="w-full mb-10px" plain type="primary"
@click="router.push({ path: '/ai/console/knowledge' })">知识库列表
</el-button>
<el-scrollbar class="flex-1 ">
<div class="py-20px flex items-center border-b-solid border-gray border-1 mb-10px">
<div class="py-20px flex items-center border-b-solid border-gray border-1 mb-10px px-10px">
<div
class="icon-bg bg-[var(--el-color-primary-light-7)] w-40px lh-40px text-center mr-10px">
<el-icon class="font-bold">
@ -76,20 +91,22 @@ onMounted(() => {
</div>
<h3>本地测试</h3>
</div>
<el-form label-position="top">
<el-form label-position="top" class=" px-10px">
<el-form-item label="知识库ID">
<el-input />
<el-input v-model="knowledgeData.id" readonly />
</el-form-item>
<el-form-item label="关联向量数据库">
<el-input />
<el-input v-model="knowledgeData.embedStore.name" readonly v-if="knowledgeData.embedStore"/>
<el-text type="danger" v-else>未关联</el-text>
</el-form-item>
<el-form-item label="关联向量化模型">
<el-input />
<el-input v-model="knowledgeData.embedModel.name" readonly v-if="knowledgeData.embedModel"/>
<el-text type="danger" v-else>未关联</el-text>
</el-form-item>
</el-form>
</el-scrollbar>
</div>
<div class="flex-1">
<div class="flex-1 bg-white ">
<el-tabs v-model="active" class="h-full">
<el-tab-pane class="h-full" v-for="(item, index) in tabs" :key="index" :label="item.label" :name="item.name">
<template #label>
@ -100,7 +117,7 @@ onMounted(() => {
<span>{{item.label}}</span>
</div>
</template>
<el-scrollbar class="inner overflow-y-auto">
<el-scrollbar class="inner overflow-y-auto p-10px" v-if="active === item.name">
<component :is="item.component" />
</el-scrollbar>
</el-tab-pane>

View File

@ -1,7 +1,7 @@
<template>
<div class="knowledge-base-container">
<div class="card-container">
<el-card class="create-card" shadow="hover" @click="toDatasetForm">
<el-card class="create-card" shadow="hover" @click="handleCreate">
<div class="create-content">
<el-icon class="create-icon"><Plus /></el-icon>
<span class="create-text">创建知识库</span>
@ -11,7 +11,8 @@
</div>
</el-card>
<el-card v-for="item in tableData" :key="item.id" class="document-card" shadow="hover"
<el-card
v-for="item in tableData" :key="item.id" class="document-card" shadow="hover"
@click="toDataset(item.id)">
<div class="document-header">
<el-icon>
@ -20,12 +21,12 @@
<span>{{ item.name }}</span>
</div>
<div class="document-info">
<el-tag size="small">{{ item.documentCount || 0 }} 文档</el-tag>
<el-tag size="small" type="info">{{ item.characterCount || 0 }} 千字符</el-tag>
<el-tag size="small" type="warning">{{ item.appCount || 0 }} 关联应用</el-tag>
<el-tag size="small">{{ item.docsNum || 0 }} 文档</el-tag>
<el-tag size="small" type="info">{{ (Number(item.totalSize) / 1000000).toFixed(2) }} MB</el-tag>
<el-tag size="small" type="warning" v-if="item.embedStore"> {{ item.embedStore.name }}</el-tag>
</div>
<p class="document-description">
{{ item.description || '暂无描述' }}
{{ item.des || '暂无描述' }}
</p>
</el-card>
</div>
@ -44,6 +45,7 @@
@current-change="handleCurrentChange"
/>
</div>
<create ref="createRef" @reload="loadData"/>
</div>
</template>
@ -52,21 +54,21 @@ import {ref, onMounted} from 'vue'
import {Plus, Folder} from '@element-plus/icons-vue'
import {useRouter} from 'vue-router'
import {KnowledgeApi} from '@/api/new-ai/knowledge'
import Create from './components/create.vue'
const router = useRouter()
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const tableData = ref([])
const createRef = ref()
const loadData = async () => {
try {
const res = await KnowledgeApi.getKnowledgePage({
pageNo: currentPage.value,
pageSize: pageSize.value
})
tableData.value = res.data.list || []
total.value = res.data.total || 0
tableData.value = res.list || []
total.value = res.total || 0
} catch (error) {
console.error('Failed to load knowledge list:', error)
}
@ -86,10 +88,10 @@ const toDataset = (id: string) => {
router.push(`/ai/console/knowledge/${id}`)
}
const toDatasetForm = () => {
router.push('/ai/console/knowledge/1')
}
const handleCreate = () => {
createRef.value.open()
}
onMounted(() => {
loadData()
})