CzRger 2 weeks ago
parent
commit
8d3ce00493

+ 5 - 0
src/api/modules/knowledge/test.ts

@@ -0,0 +1,5 @@
+import { get, post, put, del } from '@/api/request'
+
+// 知识库召回测试-测试
+export const datasetsCallback = (params) =>
+  post(`/datasets/callback`, params, {})

+ 10 - 0
src/types/knowledge.ts

@@ -0,0 +1,10 @@
+export enum SearchMethodType {
+  Vector = 'VECTOR',
+  Global = 'FULL_TEXT',
+  Mix = 'HYBRID',
+}
+export const SearchMethodTypeMap = new Map([
+  [SearchMethodType.Vector, '向量检索'],
+  [SearchMethodType.Global, '全文检索'],
+  [SearchMethodType.Mix, '混合检索'],
+])

+ 8 - 1
src/views/manage/knowledge/documents/index.vue

@@ -31,6 +31,7 @@
       v-if="state.knowledge.ID"
       :is="menus.filter((v) => v.value === state.menu)[0]?.com"
       :knowledge="state.knowledge"
+      @refresh="initDetail"
     />
   </div>
 </template>
@@ -72,7 +73,12 @@ const menus = [
     icon: 'back_1',
     com: defineAsyncComponent(() => import('./test/index.vue')),
   },
-  // { label: '设置', value: 'config', icon: 'config' },
+  {
+    label: '设置',
+    value: 'config',
+    icon: 'config',
+    com: defineAsyncComponent(() => import('./setting/index.vue')),
+  },
 ]
 const state: any = reactive({
   ID: route.params.id,
@@ -81,6 +87,7 @@ const state: any = reactive({
 })
 provide('ID', state.ID)
 const initDetail = () => {
+  console.log(123)
   if (state.ID) {
     const m = route.query.menu
     if (m && menus.some((v) => v.value === m)) {

+ 207 - 0
src/views/manage/knowledge/documents/setting/index.vue

@@ -0,0 +1,207 @@
+<template>
+  <div class="bm-main-box">
+    <div class="bm-main-box-title items-center" style="flex-direction: row">
+      设置
+      <CzrButton
+        title="保存"
+        type="primary"
+        class="mr-4 ml-auto"
+        @click="onSubmit"
+      />
+    </div>
+    <div class="bm-form mt-4 overflow-y-auto" v-loading="state.loading">
+      <CzrForm ref="ref_form" label-width="6.1rem">
+        <CzrFormColumn
+          required
+          :span="24"
+          label="知识库名称"
+          v-model:param="state.form.name"
+        />
+        <CzrFormColumn
+          required
+          :span="24"
+          label="知识库描述"
+          v-model:param="state.form.description"
+          type="textarea"
+          :rows="4"
+          placeholder="描述知识库的内容,详尽的描述将帮助AI能深入理解该知识库的内容,能更准确的检索到内容,提高该知识库的命中率。"
+        />
+        <CzrFormColumn
+          required
+          :span="24"
+          label="知识分组"
+          v-model:param="state.form.groupId"
+          link="select"
+          :options="DictionaryStore.knowledgeGroups.list"
+        />
+        <modelConfig ref="ref_modelConfig" />
+      </CzrForm>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { getCurrentInstance, nextTick, onMounted, reactive, ref } from 'vue'
+import { v4 } from 'uuid'
+import modelConfig from '@/views/manage/knowledge/model-config.vue'
+import { SearchMethodTypeMap } from '@/types/knowledge'
+import { datasetsCallback } from '@/api/modules/knowledge/test'
+import { ElMessage } from 'element-plus'
+import { useAppStore, useDialogStore, useDictionaryStore } from '@/stores'
+import { datasetsCreate, datasetsUpdate } from '@/api/modules/knowledge'
+
+const AppStore = useAppStore()
+const DialogStore = useDialogStore()
+const DictionaryStore = useDictionaryStore()
+const emit = defineEmits(['refresh'])
+const props = defineProps({
+  knowledge: <any>{},
+  modelConfig,
+})
+const { proxy }: any = getCurrentInstance()
+const state: any = reactive({
+  loading: false,
+  form: {},
+})
+const ref_modelConfig = ref()
+const ref_form = ref()
+const onSubmit = () => {
+  ref_form.value
+    .submit()
+    .then(() => {
+      DialogStore.confirm({
+        content: `请确认是否保存?`,
+        onSubmit: () => {
+          state.loading = true
+          datasetsUpdate({
+            ...state.form,
+            ...ref_modelConfig.value.getData(),
+            tenantId: AppStore.tenantInfo?.id,
+          })
+            .then(({ data }: any) => {
+              ElMessage.success(`保存成功!`)
+              emit('refresh')
+            })
+            .catch(() => {})
+            .finally(() => {
+              state.loading = false
+            })
+        },
+      })
+    })
+    .catch((e) => {
+      ElMessage({
+        message: e[0].message,
+        grouping: true,
+        type: 'warning',
+      })
+    })
+}
+onMounted(() => {
+  initDictionary()
+  state.form = JSON.parse(JSON.stringify(props.knowledge))
+  ref_modelConfig.value.init(state.form)
+})
+
+const initDictionary = () => {
+  DictionaryStore.initKnowledgeGroups(AppStore.tenantInfo?.id)
+}
+</script>
+
+<style lang="scss" scoped>
+.bm-main-box {
+  padding-left: 0;
+  padding-right: 0;
+  padding-bottom: 0;
+}
+.card {
+  background-color: #ffffff;
+  box-shadow: 0rem 0.06rem 0.25rem 0rem rgba(38, 110, 255, 0.2);
+  border: 0.06rem solid #e6e8ea;
+  padding: 1rem;
+}
+.text-main {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  border-radius: 0.25rem;
+  .text-head {
+    display: flex;
+    align-items: center;
+    padding: 0 1.5rem;
+    background-image: url('@/assets/images/knowledge-back-test.png');
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
+    height: 3.75rem;
+    border-radius: 0.25rem 0.25rem 0 0;
+    border: var(--czr-border);
+    font-weight: bold;
+    font-size: 1.25rem;
+    color: #303133;
+    > div {
+      margin-left: auto;
+      width: 5.63rem;
+      height: 1.75rem;
+      background: #ebf2ff;
+      border-radius: 0.25rem;
+      border: 1px solid rgba(var(--czr-main-color-rgb), 0.3);
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 0.75rem;
+      color: var(--czr-main-color);
+    }
+  }
+  .text-content {
+    flex: 1;
+    position: relative;
+    :deep(.czr-form-column) {
+      width: 100%;
+      height: 100%;
+      .el-form-item {
+        width: 100%;
+        height: 100%;
+        .el-textarea__inner {
+          width: 100%;
+          height: 100%;
+          padding: 0.75rem;
+          resize: none;
+          border-top-left-radius: 0;
+          border-top-right-radius: 0;
+        }
+      }
+    }
+    .test {
+      position: absolute;
+      right: 1.5rem;
+      bottom: 0.75rem;
+    }
+  }
+}
+
+.back-item {
+  border-radius: 0.5rem;
+  width: 100%;
+  padding: 2rem 1rem 1rem;
+  position: relative;
+  .back-item-head {
+    position: absolute;
+    top: 0;
+    left: 0;
+    display: flex;
+    align-items: center;
+    font-size: 0.75rem;
+    color: #576275;
+    gap: 0.5rem;
+    > div:first-child {
+      width: 2.5rem;
+      height: 1.63rem;
+      box-shadow: 0 0.13rem 0.13rem 0 rgba(0, 0, 0, 0.25);
+      border-radius: 0.25rem 0 0.25rem 0;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+  }
+}
+</style>

+ 83 - 29
src/views/manage/knowledge/documents/test/index.vue

@@ -6,17 +6,12 @@
     </div>
     <div class="mt-[var(--czr-gap)] flex flex-1 overflow-hidden">
       <div class="card flex flex-1 flex-col gap-[1rem] rounded-bl-[0.5rem]">
-        <div class="text-main">
+        <div class="text-main" v-loading="state.loading">
           <div class="text-head">
             源文本
-            <el-popover :width="800" trigger="click">
-              <template #reference>
-                <div class="__hover">向量检索</div>
-              </template>
-              <div>
-                <modelConfig :index-method="false" :embedding="false" />
-              </div>
-            </el-popover>
+            <div class="__hover" @click="onModelConfig">
+              {{ SearchMethodTypeMap.get(state.modelConfig.indexConfig?.type) }}
+            </div>
           </div>
           <div class="text-content">
             <CzrFormColumn
@@ -27,7 +22,12 @@
               :transparent="true"
               :clearable="false"
             />
-            <CzrButton class="test" type="primary" title="测试" />
+            <CzrButton
+              class="test"
+              type="primary"
+              title="测试"
+              @click="onTest"
+            />
           </div>
         </div>
         <div class="flex flex-1 flex-col">
@@ -50,6 +50,7 @@
       </div>
       <div
         class="card flex w-[26.25rem] flex-col space-y-4 rounded-br-[0.5rem]"
+        v-loading="state.loading"
       >
         <div class="__czr-title_1">{{ state.backList.length }} 个召回段落</div>
         <div class="flex flex-1 flex-col gap-[1rem] overflow-y-auto">
@@ -68,15 +69,15 @@
                 >
                   <SvgIcon name="box" color="#ffffff" size="15" />
                 </div>
-                Chunk-138·18 字符
+                Chunk-{{ index + 1 }}·{{ item.wordCount }} 字符
               </div>
               <div class="mt-[0.5rem] text-[1.25rem] font-bold text-[#21262D]">
-                {{ item.p1 }}
+                {{ item.text }}
               </div>
               <div
                 class="mt-[var(--czr-gap)] flex flex-wrap gap-2 text-[0.88rem] text-[#576275]"
               >
-                <template v-for="s in item.p2">
+                <template v-for="s in item.keywords">
                   <span>#{{ s }}</span>
                 </template>
               </div>
@@ -90,9 +91,10 @@
                 class="mt-[1rem] flex items-center gap-[0.5rem] text-[0.75rem] text-[#576275]"
               >
                 <img
-                  src="@/assets/images/file-excel.png"
+                  :src="DictionaryStore.getFileIcon(item.name)"
                   class="h-[1rem]"
-                />口岸服务网事项清单.xlsx
+                />
+                {{ item.name }}
               </div>
             </div>
           </template>
@@ -100,20 +102,43 @@
       </div>
     </div>
   </div>
+  <CzrDialog
+    :show="state.modelConfig.show"
+    title="检索方式"
+    @onClose="state.modelConfig.show = false"
+    @onSubmit="onSubmitModelConfig"
+    width="62.5rem"
+    height="auto"
+    max-height="90%"
+  >
+    <CzrForm ref="ref_form" label-width="6.1rem">
+      <modelConfig
+        :index-method="false"
+        :embedding="false"
+        ref="ref_modelConfig"
+      />
+    </CzrForm>
+  </CzrDialog>
 </template>
 
 <script setup lang="ts">
-import { getCurrentInstance, onMounted, reactive, ref } from 'vue'
+import { getCurrentInstance, nextTick, onMounted, reactive, ref } from 'vue'
 import { v4 } from 'uuid'
 import modelConfig from '@/views/manage/knowledge/model-config.vue'
+import { SearchMethodTypeMap } from '@/types/knowledge'
+import { datasetsCallback } from '@/api/modules/knowledge/test'
+import { ElMessage } from 'element-plus'
+import { useDictionaryStore } from '@/stores'
 
-const emit = defineEmits([])
+const DictionaryStore = useDictionaryStore()
+const emit = defineEmits(['refresh'])
 const props = defineProps({
   knowledge: <any>{},
 })
 const { proxy }: any = getCurrentInstance()
 const colors = ['46, 155, 62', '0, 159, 188', '119, 69, 222', '255, 162, 84']
 const state: any = reactive({
+  loading: false,
   text: '',
   backList: [],
   query: {
@@ -132,17 +157,13 @@ const state: any = reactive({
       data: [],
     },
   },
+  modelConfig: {
+    show: false,
+    indexConfig: null,
+  },
 })
-const initBack = () => {
-  const arr: any = []
-  for (let i = 0; i < 20; i++) {
-    arr.push({
-      p1: '事项名称":"从事海员外派业务审批”',
-      p2: ['业务', '从事', '事项', '业务', '从事', '事项'],
-    })
-  }
-  state.backList = arr
-}
+const ref_modelConfig = ref()
+const ref_form = ref()
 const onPage = (pageNum, pageSize) => {
   state.query.page = {
     pageNum: pageNum,
@@ -167,10 +188,43 @@ const onPage = (pageNum, pageSize) => {
     state.query.loading = false
   }, 1000)
 }
+const onModelConfig = () => {
+  state.modelConfig.show = true
+  nextTick(() => {
+    ref_modelConfig.value.init({ indexConfig: state.modelConfig.indexConfig })
+  })
+}
+const onSubmitModelConfig = () => {
+  ref_form.value.submit().then(() => {
+    state.modelConfig.indexConfig = ref_modelConfig.value.getData().indexConfig
+    state.modelConfig.show = false
+  })
+}
+const onTest = () => {
+  if (!state.text.trim()) {
+    ElMessage.warning('请输入源文本')
+    return
+  }
+  state.loading = true
+  datasetsCallback({
+    datasetId: props.knowledge.id,
+    indexConfig: JSON.stringify(state.modelConfig.indexConfig),
+    context: state.text,
+    token: 0,
+  })
+    .then(({ data }: any) => {
+      state.backList = data
+    })
+    .catch(() => {})
+    .finally(() => {
+      state.loading = false
+    })
+}
 onMounted(() => {
-  initBack()
   onPage(1, 10)
-  console.log(props.knowledge)
+  state.modelConfig.indexConfig = JSON.parse(
+    JSON.stringify(props.knowledge.indexConfig),
+  )
 })
 </script>
 

+ 13 - 9
src/views/manage/knowledge/model-config.vue

@@ -92,7 +92,9 @@
             src="@/assets/images/model-icon-4.png"
             class="mr-[var(--czr-gap)] h-[3.25rem] w-[3.25rem]"
           />
-          <div class="text-[1.25rem] font-bold text-[#2E3238]">向量检索</div>
+          <div class="text-[1.25rem] font-bold text-[#2E3238]">
+            {{ SearchMethodTypeMap.get(SearchMethodType.Vector) }}
+          </div>
         </div>
         <div
           class="mt-[0.5rem] text-[0.88rem] text-[#606266]"
@@ -209,7 +211,9 @@
             src="@/assets/images/model-icon-5.png"
             class="mr-[var(--czr-gap)] h-[3.25rem] w-[3.25rem]"
           />
-          <div class="text-[1.25rem] font-bold text-[#2E3238]">全文检索</div>
+          <div class="text-[1.25rem] font-bold text-[#2E3238]">
+            {{ SearchMethodTypeMap.get(SearchMethodType.Global) }}
+          </div>
         </div>
         <div
           class="mt-[0.5rem] text-[0.88rem] text-[#606266]"
@@ -240,6 +244,7 @@
               v-if="state.searchMethod[SearchMethodType.Global].isRerank"
             >
               <CzrFormColumn
+                required
                 class="__czr-table-form-column"
                 :span="24"
                 label-width="0px"
@@ -328,7 +333,9 @@
             src="@/assets/images/model-icon-6.png"
             class="mr-[var(--czr-gap)] h-[3.25rem] w-[3.25rem]"
           />
-          <div class="text-[1.25rem] font-bold text-[#2E3238]">混合检索</div>
+          <div class="text-[1.25rem] font-bold text-[#2E3238]">
+            {{ SearchMethodTypeMap.get(SearchMethodType.Mix) }}
+          </div>
           <img
             src="@/assets/images/model-icon-3.png"
             class="h-[1.27rem] w-[2.88rem]"
@@ -446,6 +453,7 @@
               "
             >
               <CzrFormColumn
+                required
                 class="__czr-table-form-column"
                 :span="24"
                 label-width="0px"
@@ -529,8 +537,9 @@ import {
   watch,
 } from 'vue'
 import { useDictionaryStore } from '@/stores'
-import { pluginGetListByType, pluginInstanceByType } from '@/api/modules/model'
+import { pluginGetListByType } from '@/api/modules/model'
 import { ElMessage } from 'element-plus'
+import { SearchMethodType, SearchMethodTypeMap } from '@/types/knowledge'
 
 const DictionaryStore = useDictionaryStore()
 const emit = defineEmits([])
@@ -540,11 +549,6 @@ const props = defineProps({
   searchMethod: { default: true },
 })
 const { proxy }: any = getCurrentInstance()
-enum SearchMethodType {
-  Vector = 'VECTOR',
-  Global = 'FULL_TEXT',
-  Mix = 'HYBRID',
-}
 const state: any = reactive({
   optionsEmbedding: [],
   optionsRerank: [],