parent
6327866533
commit
05609d63c2
|
@ -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}` })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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}` })
|
||||
}
|
||||
}
|
|
@ -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}` })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue