Browse Source

【功能新增】AI:知识库文档上传:60%,SplitStep 完成度更高

YunaiV 5 tháng trước cách đây
mục cha
commit
de41b6cdba

+ 6 - 1
src/api/ai/knowledge/document/index.ts

@@ -24,11 +24,16 @@ export const KnowledgeDocumentApi = {
     return await request.get({ url: `/ai/knowledge/document/get?id=` + id })
   },
 
-  // 新增知识库文档
+  // 新增知识库文档(单个)
   createKnowledgeDocument: async (data: KnowledgeDocumentVO) => {
     return await request.post({ url: `/ai/knowledge/document/create`, data })
   },
 
+  // 新增知识库文档(批量)
+  createKnowledgeDocumentList: async (data: any) => {
+    return await request.post({ url: `/ai/knowledge/document/create-list`, data })
+  },
+
   // // 修改AI 知识库文档
   // updateKnowledgeDocument: async (data: KnowledgeDocumentVO) => {
   //   return await request.put({ url: `/ai/knowledge/document/update`, data })

+ 13 - 1
src/router/modules/remaining.ts

@@ -633,7 +633,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       },
       {
         path: 'console/knowledge/document/create',
-        component: () => import('@/views/ai/knowledge/document/create/index.vue'),
+        component: () => import('@/views/ai/knowledge/document/form/index.vue'),
         name: 'AiKnowledgeDocumentCreate',
         meta: {
           title: '创建文档',
@@ -642,6 +642,18 @@ const remainingRouter: AppRouteRecordRaw[] = [
           hidden: true,
           activeMenu: '/ai/console/knowledge/document'
         }
+      },
+      {
+        path: 'console/knowledge/document/update',
+        component: () => import('@/views/ai/knowledge/document/form/index.vue'),
+        name: 'AiKnowledgeDocumentUpdate',
+        meta: {
+          title: '修改文档',
+          icon: 'ep:edit',
+          noCache: true,
+          hidden: true,
+          activeMenu: '/ai/console/knowledge/document'
+        }
       }
     ]
   },

+ 0 - 0
src/views/ai/knowledge/document/create/ProcessStep.vue → src/views/ai/knowledge/document/form/ProcessStep.vue


+ 54 - 19
src/views/ai/knowledge/document/create/SplitStep.vue → src/views/ai/knowledge/document/form/SplitStep.vue

@@ -78,17 +78,24 @@
 
     <!-- 添加底部按钮 -->
     <div class="mt-20px flex justify-between">
-      <el-button @click="handlePrevStep">上一步</el-button>
-      <el-button type="primary" @click="handleNextStep">保存并处理</el-button>
+      <div>
+        <el-button v-if="!modelData.id" @click="handlePrevStep">上一步</el-button>
+      </div>
+      <div>
+        <el-button type="primary" :loading="submitLoading" @click="handleSave">
+          保存并处理
+        </el-button>
+      </div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { PropType, ref, computed, inject, onMounted, getCurrentInstance } from 'vue'
+import { computed, getCurrentInstance, inject, onMounted, PropType, ref } from 'vue'
 import { Icon } from '@/components/Icon'
 import { KnowledgeSegmentApi } from '@/api/ai/knowledge/segment'
 import { useMessage } from '@/hooks/web/useMessage'
+import { KnowledgeDocumentApi } from '@/api/ai/knowledge/document'
 
 const props = defineProps({
   modelValue: {
@@ -108,6 +115,7 @@ const modelData = computed({
 
 const splitLoading = ref(false) // 分段加载状态
 const currentFile = ref<any>(null) // 当前选中的文件
+const submitLoading = ref(false) // 提交按钮加载状态
 
 /** 选择文件 */
 const selectFile = async (index: number) => {
@@ -124,8 +132,11 @@ const splitContent = async (file: any) => {
 
   splitLoading.value = true
   try {
-    const data = await KnowledgeSegmentApi.splitContent(file.url, modelData.value.segmentMaxTokens) // 调用后端分段接口,获取文档的分段内容、字符数和 Token 数
-    file.segments = data
+    // 调用后端分段接口,获取文档的分段内容、字符数和 Token 数
+    file.segments = await KnowledgeSegmentApi.splitContent(
+      file.url,
+      modelData.value.segmentMaxTokens
+    )
   } catch (error) {
     console.error('获取分段内容失败:', file, error)
     message.error('获取分段内容失败')
@@ -158,22 +169,46 @@ const handlePrevStep = () => {
   }
 }
 
-/** 下一步按钮处理 */
-const handleNextStep = () => {
-  const parentEl = parent || getCurrentInstance()?.parent
-  if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') {
-    parentEl.exposed.goToNextStep()
-  }
-}
-
-/** 组件激活时自动调用分段接口 TODO 芋艿:需要看下 */
-const activated = async () => {
-  if (!currentFile.value && modelData.value.list && modelData.value.list.length > 0) {
-    currentFile.value = modelData.value.list[0] // 如果没有选中文件,默认选中第一个
+/** 保存操作 */
+const handleSave = async () => {
+  // 保存前验证
+  if (!currentFile?.value?.segments || currentFile.value.segments.length === 0) {
+    message.warning('请先预览分段内容')
+    return
   }
 
-  if (currentFile.value) {
-    await splitContent(currentFile.value) // 如果有选中的文件,获取分段内容
+  // 设置按钮加载状态
+  submitLoading.value = true
+  try {
+    if (modelData.value.id) {
+      // 修改场景
+      modelData.value.ids = await KnowledgeDocumentApi.createKnowledgeDocumentList({
+        id: modelData.value.id,
+        segmentMaxTokens: modelData.value.segmentMaxTokens
+      })
+    } else {
+      // 新增场景
+      modelData.value.ids = await KnowledgeDocumentApi.createKnowledgeDocumentList({
+        knowledgeId: modelData.value.knowledgeId,
+        segmentMaxTokens: modelData.value.segmentMaxTokens,
+        list: modelData.value.list.map((item: any) => ({
+          name: item.name,
+          url: item.url
+        }))
+      })
+    }
+
+    // 进入下一步
+    const parentEl = parent || getCurrentInstance()?.parent
+    if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') {
+      parentEl.exposed.goToNextStep()
+    }
+  } catch (error: any) {
+    console.error('保存失败:', modelData.value, error)
+    message.error(error.message)
+  } finally {
+    // 关闭按钮加载状态
+    submitLoading.value = false
   }
 }
 

+ 0 - 0
src/views/ai/knowledge/document/create/UploadStep.vue → src/views/ai/knowledge/document/form/UploadStep.vue


+ 35 - 65
src/views/ai/knowledge/document/create/index.vue → src/views/ai/knowledge/document/form/index.vue

@@ -8,8 +8,8 @@
         <!-- 左侧标题 -->
         <div class="w-200px flex items-center overflow-hidden">
           <Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="handleBack" />
-          <span class="ml-10px text-16px truncate" :title="formData.name || '创建知识库文档'">
-            {{ formData.name || '创建知识库文档' }}
+          <span class="ml-10px text-16px truncate">
+            {{ formData.id ? '编辑知识库文档' : '创建知识库文档' }}
           </span>
         </div>
 
@@ -68,29 +68,25 @@
 
 <script lang="ts" setup>
 import { useRoute, useRouter } from 'vue-router'
-import { useMessage } from '@/hooks/web/useMessage'
 import { useTagsViewStore } from '@/store/modules/tagsView'
 import UploadStep from './UploadStep.vue'
 import SplitStep from './SplitStep.vue'
 import ProcessStep from './ProcessStep.vue'
+import { KnowledgeDocumentApi } from '@/api/ai/knowledge/document'
 
-const router = useRouter()
 const { delView } = useTagsViewStore() // 视图操作
-const route = useRoute()
-const message = useMessage()
+const route = useRoute() // 路由
+const router = useRouter() // 路由
 
 // 组件引用
 const uploadDocumentRef = ref()
 const documentSegmentRef = ref()
 const processCompleteRef = ref()
-
 const currentStep = ref(0) // 步骤控制
 const steps = [{ title: '上传文档' }, { title: '文档分段' }, { title: '处理并完成' }]
-
-// 表单数据
 const formData = ref({
-  knowlegeId: undefined, // 知识库编号
-  id: undefined, // 文档编号(documentId)
+  knowledgeId: undefined, // 知识库编号
+  id: undefined, // 编辑的文档编号(documentId)
   segmentMaxTokens: 500, // 分段最大 token 数
   list: [] as Array<{
     name: string
@@ -101,17 +97,35 @@ const formData = ref({
       tokens?: number
     }>
   }>, // 用于存储上传的文件列表
+  documentIds: [], // 最终提交的创建/修改的文档编号,用于 ProcessStep 组件的轮询
   status: 0 // 0: 草稿, 1: 处理中, 2: 已完成
-})
+}) // 表单数据
+
+provide('parent', getCurrentInstance()) // 提供 parent 给子组件使用
 
 /** 初始化数据 */
 const initData = async () => {
-  // TODO @芋艿:knowlegeId 解析
-  const documentId = route.params.id as string
+  // 【新增场景】从路由参数中获取知识库 ID
+  if (route.query.knowledgeId) {
+    formData.value.knowledgeId = route.query.knowledgeId as any
+  }
+
+  // 【修改场景】从路由参数中获取文档 ID
+  const documentId = route.query.id
   if (documentId) {
-    // 修改场景
-    // 这里需要调用API获取文档数据
-    // formData.value = await DocumentApi.getDocument(documentId)
+    // 获取文档信息
+    formData.value.id = documentId as any
+    const document = await KnowledgeDocumentApi.getKnowledgeDocument(documentId as any)
+    formData.value.segmentMaxTokens = document.segmentMaxTokens
+    formData.value.list = [
+      {
+        name: document.name,
+        url: document.url,
+        segments: []
+      }
+    ]
+    // 进入下一步
+    goToNextStep()
   }
 
   // TODO @芋艿:为了开发方便,强制设置
@@ -141,52 +155,12 @@ const goToPrevStep = () => {
   }
 }
 
-/** 保存操作 */
-const handleSave = async () => {
-  try {
-    // 更新表单数据
-    const documentData = {
-      ...formData.value
-    }
-
-    if (formData.value.id) {
-      // 修改场景
-      // await DocumentApi.updateDocument(documentData)
-      message.success('修改成功')
-    } else {
-      // 新增场景
-      // formData.value.id = await DocumentApi.createDocument(documentData)
-      message.success('新增成功')
-      try {
-        await message.confirm('创建文档成功,是否继续编辑?')
-        // 用户点击继续编辑,跳转到编辑页面
-        await nextTick()
-        // 先删除当前页签
-        delView(unref(router.currentRoute))
-        // 跳转到编辑页面
-        await router.push({
-          name: 'AiKnowledgeDocumentUpdate',
-          params: { id: formData.value.id }
-        })
-      } catch {
-        // 先删除当前页签
-        delView(unref(router.currentRoute))
-        // 用户点击返回列表
-        await router.push({ name: 'AiKnowledgeDocument' })
-      }
-    }
-  } catch (error: any) {
-    console.error('保存失败:', error)
-    message.warning(error.message || '请完善所有步骤的必填信息')
-  }
-}
-
 /** 返回列表页 */
 const handleBack = () => {
   // 先删除当前页签
   delView(unref(router.currentRoute))
   // 跳转到列表页
-  router.push({ name: 'AiKnowledgeDocument' })
+  router.push({ name: 'AiKnowledgeDocument', query: { knowledgeId: formData.value.knowledgeId } })
 }
 
 /** 初始化 */
@@ -194,10 +168,7 @@ onMounted(async () => {
   await initData()
 })
 
-// 提供parent给子组件使用
-provide('parent', getCurrentInstance())
-
-// 添加组件卸载前的清理代码
+/** 添加组件卸载前的清理代码 */
 onBeforeUnmount(() => {
   // 清理所有的引用
   uploadDocumentRef.value = null
@@ -205,11 +176,10 @@ onBeforeUnmount(() => {
   processCompleteRef.value = null
 })
 
-// 暴露方法给子组件使用
+/** 暴露方法给子组件使用 */
 defineExpose({
   goToNextStep,
-  goToPrevStep,
-  handleSave
+  goToPrevStep
 })
 </script>
 

+ 22 - 11
src/views/ai/knowledge/document/index.vue

@@ -68,7 +68,7 @@
           <el-button
             link
             type="primary"
-            @click="openForm('update', scope.row.id)"
+            @click="handleUpdate(scope.row.id)"
             v-hasPermi="['ai:knowledge:update']"
           >
             编辑
@@ -148,15 +148,20 @@ const resetQuery = () => {
   handleQuery()
 }
 
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
-}
-
 /** 跳转到创建文档页面 */
 const handleCreate = () => {
-  router.push({ name: 'AiKnowledgeDocumentCreate' })
+  router.push({
+    name: 'AiKnowledgeDocumentCreate',
+    query: { knowledgeId: queryParams.knowledgeId }
+  })
+}
+
+/** 跳转到更新文档页面 */
+const handleUpdate = (id: number) => {
+  router.push({
+    name: 'AiKnowledgeDocumentUpdate',
+    query: { id, knowledgeId: queryParams.knowledgeId }
+  })
 }
 
 /** 删除按钮操作 */
@@ -174,10 +179,16 @@ const handleDelete = async (id: number) => {
 
 /** 初始化 **/
 onMounted(() => {
-  // 从路由参数中获取知识库 ID
-  if (route.query.knowledgeId) {
-    queryParams.knowledgeId = route.query.knowledgeId as any
+  // 如果知识库 ID 不存在,显示错误提示并关闭页面
+  if (!route.query.knowledgeId) {
+    message.error('知识库 ID 不存在,无法查看文档列表')
+    // 关闭当前路由,返回到知识库列表页面
+    router.push({ name: 'AiKnowledge' })
+    return
   }
+
+  // 从路由参数中获取知识库 ID
+  queryParams.knowledgeId = route.query.knowledgeId as any
   getList()
 })
 </script>