xiongzhu 2 роки тому
батько
коміт
a5ef405624

+ 7 - 0
src/enums/index.js

@@ -26,3 +26,10 @@ export const UserRole = {
     api: 'API用户',
     org: '企业用户'
 }
+
+export const FileStatus = {
+    pending: '待处理',
+    processing: '处理中',
+    done: '完成',
+    failed: '失败'
+}

+ 8 - 0
src/router/index.js

@@ -165,6 +165,14 @@ const router = createRouter({
                     meta: {
                         title: '知识库管理'
                     }
+                },
+                {
+                    path: 'orgUser',
+                    name: 'orgUser',
+                    component: () => import('../views/OrgUserView.vue'),
+                    meta: {
+                        title: '席位管理'
+                    }
                 }
             ]
         }

+ 94 - 25
src/views/KnowledgeView.vue

@@ -2,14 +2,17 @@
     <PagingTable url="/knowledge/base" :where="where" ref="table">
         <template #filter>
             <ElButton :icon="Plus" @click="onEdit()">添加</ElButton>
+            <ElButton :icon="Refresh" @click="table.refresh()"></ElButton>
         </template>
         <ElTableColumn prop="id" label="#" width="80" />
         <ElTableColumn prop="name" label="名称" />
         <ElTableColumn prop="description" label="描述" />
+        <ElTableColumn prop="orgId" label="企业ID" />
         <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
-        <ElTableColumn label="操作" align="center" width="160">
+        <ElTableColumn label="操作" align="center" width="200">
             <template #default="{ row }">
                 <ElButton @click="onEdit(row)">编辑</ElButton>
+                <ElButton @click="showFiles(row)">文件</ElButton>
                 <ElButton @click="deleteRow(row)" type="danger">删除</ElButton>
             </template>
         </ElTableColumn>
@@ -21,40 +24,43 @@
         <ElFormItem prop="description" label="描述">
             <ElInput v-model="model.description" placeholder="请输入描述" />
         </ElFormItem>
+        <ElFormItem prop="orgId" label="企业ID" v-if="role === 'admin'">
+            <ElInputNumber :controls="false" v-model="model.orgId" placeholder="请输入企业ID" />
+        </ElFormItem>
     </EditDialog>
 
-    <ElDialog>
-        <PagingTable url="/knowledge/base" :where="where" ref="table">
+    <ElDialog v-model="showFileDialog" :title="selectedBase?.name" width="80%">
+        <PagingTable url="/knowledge/file" :where="{ knowledgeId: selectedBase?.id }" ref="fileTable">
             <template #filter>
-                <ElButton :icon="Plus" @click="onEdit()">添加</ElButton>
+                <ElButton :icon="Plus" @click="uploadFile()">添加</ElButton>
+                <ElButton :icon="Refresh" @click="fileTable.refresh()"></ElButton>
             </template>
             <ElTableColumn prop="id" label="#" width="80" />
-            <ElTableColumn prop="name" label="名称" />
-            <ElTableColumn prop="description" label="描述" />
+            <ElTableColumn prop="orgId" label="企业ID" />
+            <ElTableColumn prop="knowledgeId" label="知识库ID" />
+            <ElTableColumn prop="fileName" label="文件名" show-overflow-tooltip />
+            <ElTableColumn prop="fileHash" label="文件Hash" show-overflow-tooltip />
+            <ElTableColumn prop="status" label="状态" align="center">
+                <template #default="{ row }">
+                    <ElTag :type="fileStatusTagType(row.status)">{{
+                        fileStatusFormatter(null, null, row.status)
+                    }}</ElTag>
+                </template>
+            </ElTableColumn>
             <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
-            <ElTableColumn label="操作" align="center" width="160">
+            <ElTableColumn label="操作" align="center" width="100">
                 <template #default="{ row }">
-                    <ElButton @click="onEdit(row)">编辑</ElButton>
-                    <ElButton @click="deleteRow(row)" type="danger">删除</ElButton>
+                    <ElButton @click="deleteFile(row)" type="danger">删除</ElButton>
                 </template>
             </ElTableColumn>
         </PagingTable>
     </ElDialog>
-
-    <EditDialog v-model="showEditDialog" :model="model" :rules="rules" :on-submit="submit" @success="table.refresh()">
-        <ElFormItem prop="name" label="名称">
-            <ElInput v-model="model.name" placeholder="请输入名称" />
-        </ElFormItem>
-        <ElFormItem prop="description" label="描述">
-            <ElInput v-model="model.description" placeholder="请输入描述" />
-        </ElFormItem>
-    </EditDialog>
 </template>
 <script setup>
-import { ref, computed } from 'vue'
+import { ref, computed, nextTick } from 'vue'
 import PagingTable from '@/components/PagingTable.vue'
-import { useTimeFormatter } from '@/utils/formatter'
-import { Plus } from '@vicons/tabler'
+import { useEnumFormatter, useTimeFormatter } from '@/utils/formatter'
+import { Plus, Refresh } from '@vicons/tabler'
 import EditDialog from '@/components/EditDialog.vue'
 import { setupEditDialog } from '@/utils/editDialog'
 import { http } from '@/plugins/http'
@@ -62,11 +68,14 @@ import { ElMessage, ElMessageBox } from 'element-plus'
 import { useClipboard } from '@vueuse/core'
 import { storeToRefs } from 'pinia'
 import { useUserStore } from '@/stores/user'
-
-const user = storeToRefs(useUserStore())
+import { FileStatus } from '@/enums'
+import { useFileDialog } from '@vueuse/core'
+const { user } = storeToRefs(useUserStore())
+const role = computed(() => user.value?.roles[0])
 const where = computed(() => {
+    if (role.value === 'admin') return {}
     return {
-        orgId: user.orgId
+        orgId: user.value.orgId
     }
 })
 const timeFormatter = useTimeFormatter()
@@ -74,10 +83,14 @@ const table = ref(null)
 const model = ref({})
 const rules = {
     name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
-    description: [{ required: true, message: '请输入描述', trigger: 'blur' }]
+    description: [{ required: true, message: '请输入描述', trigger: 'blur' }],
+    orgId: [{ required: true, message: '请输入企业ID', trigger: 'blur' }]
 }
 const { showEditDialog, onEdit } = setupEditDialog(model)
 async function submit() {
+    if (!model.value.orgId) {
+        model.value.orgId = user.value.orgId
+    }
     await http.put(model.value.id ? `/knowledge/base/${model.value.id}` : '/knowledge/base', model.value)
     ElMessage.success('保存成功')
 }
@@ -93,4 +106,60 @@ async function deleteRow(row) {
         if ('cancel' !== error) ElMessage.error(error.message)
     }
 }
+const selectedBase = ref(null)
+const fileTable = ref(null)
+function showFiles(row) {
+    selectedBase.value = row
+    showFileDialog.value = true
+    nextTick(() => {
+        fileTable.value.refresh()
+    })
+}
+const showFileDialog = ref(false)
+const fileStatusFormatter = useEnumFormatter(FileStatus)
+function fileStatusTagType(status) {
+    switch (status) {
+        case 'pending':
+            return 'info'
+        case 'processing':
+            return ''
+        case 'done':
+            return 'success'
+        case 'failed':
+            return 'danger'
+        default:
+            return 'info'
+    }
+}
+const uploading = ref(false)
+function uploadFile() {
+    const { files, open, reset, onChange } = useFileDialog({ accept: 'application/pdf' })
+    onChange(async (files) => {
+        console.log(files[0])
+        uploading.value = true
+        try {
+            const formData = new FormData()
+            formData.append('file', files[0])
+            await http.put(`/knowledge/base/${selectedBase.value.id}/file`, formData)
+        } catch (error) {
+            ElMessage.error(error.message)
+        } finally {
+            uploading.value = false
+            fileTable.value.refresh()
+        }
+    })
+    open()
+}
+async function deleteFile(row) {
+    try {
+        await ElMessageBox.confirm('此操作将永久删除数据, 是否继续?', '提示', {
+            type: 'warning'
+        })
+        await http.delete(`/knowledge/file/${row.id}`)
+        ElMessage.success('删除成功')
+        fileTable.value.refresh()
+    } catch (error) {
+        if ('cancel' !== error) ElMessage.error(error.message)
+    }
+}
 </script>

+ 15 - 20
src/views/MainView.vue

@@ -104,14 +104,19 @@ const menus = computed(() => {
                 ]
             },
             {
-                name: 'org',
+                name: 'org-parent',
                 title: '企业管理',
-                icon: Building
-            },
-            {
-                name: 'knowledge',
-                title: '知识库管理',
-                icon: Notebook
+                icon: Building,
+                children: [
+                    {
+                        name: 'org',
+                        title: '企业管理'
+                    },
+                    {
+                        name: 'knowledge',
+                        title: '知识库管理'
+                    }
+                ]
             },
             {
                 name: 'membership-parent',
@@ -189,25 +194,15 @@ const menus = computed(() => {
                 title: '企业信息',
                 icon: Building
             },
-            {
-                name: 'user',
-                title: '席位管理',
-                icon: Users
-            },
-            {
-                name: 'usageDetail',
-                title: '用量统计',
-                icon: Database
-            },
             {
                 name: 'knowledge',
                 title: '知识库管理',
                 icon: Notebook
             },
             {
-                name: 'librarySetting',
-                title: '知识库管理',
-                icon: Files
+                name: 'orgUser',
+                title: '席位管理',
+                icon: Users
             },
             {
                 name: 'setting',

+ 3 - 3
src/views/OrgEditView.vue

@@ -15,8 +15,8 @@
         <ElFormItem prop="systemPrompt" label="系统提示词">
             <ElInput type="textarea" v-model="model.systemPrompt" placeholder="请输入系统提示词" autosize />
         </ElFormItem>
-        <ElFormItem prop="questionTemplate" label="提问模版">
-            <ElInput type="textarea" v-model="model.questionTemplate" placeholder="请输入提问模版" autosize />
+        <ElFormItem prop="contextTemplate" label="上下文模版">
+            <ElInput type="textarea" v-model="model.contextTemplate" placeholder="请输入上下文模版" autosize />
         </ElFormItem>
         <ElFormItem prop="orgId" label="企业ID" v-if="model.roles && model.roles[0] === 'org'">
             <ElInputNumber :controls="false" v-model="model.orgId" placeholder="请输入企业ID" />
@@ -38,7 +38,7 @@ const rules = {
     assistantName: [{ required: true, message: '请输入助手名称', trigger: 'blur' }],
     description: [{ required: true, message: '请输入描述', trigger: 'blur' }],
     systemPrompt: [{ required: true, message: '请输入系统提示词', trigger: 'blur' }],
-    questionTemplate: [{ required: true, message: '请输入提问模版', trigger: 'blur' }]
+    contextTemplate: [{ required: true, message: '请输入上下文模版', trigger: 'blur' }]
 }
 const saving = ref(false)
 onMounted(() => {

+ 69 - 0
src/views/OrgUserView.vue

@@ -0,0 +1,69 @@
+<template>
+    <PagingTable url="/admin/org/users" :where="where" ref="table">
+        <template #filter>
+            <ElButton :icon="Plus" @click="onEdit()">添加</ElButton>
+        </template>
+        <ElTableColumn prop="id" label="#" width="80" />
+        <ElTableColumn prop="name" label="姓名" min-width="120" />
+        <ElTableColumn prop="phone" label="手机" min-width="120" />
+        <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
+        <ElTableColumn label="操作" align="center" width="160">
+            <template #default="{ row }">
+                <ElButton @click="deleteRow(row)">删除</ElButton>
+            </template>
+        </ElTableColumn>
+    </PagingTable>
+    <EditDialog v-model="showEditDialog" :model="model" :rules="rules" :on-submit="submit" @success="table.refresh()">
+        <ElFormItem prop="name" label="姓名">
+            <ElInput v-model="model.name" placeholder="请输入昵称" />
+        </ElFormItem>
+        <ElFormItem prop="phone" label="手机">
+            <ElInput v-model="model.phone" placeholder="请输入手机" />
+        </ElFormItem>
+    </EditDialog>
+</template>
+<script setup>
+import { ref } from 'vue'
+import PagingTable from '@/components/PagingTable.vue'
+import { useTimeFormatter } from '@/utils/formatter'
+import { Plus } from '@vicons/tabler'
+import EditDialog from '@/components/EditDialog.vue'
+import { setupEditDialog } from '@/utils/editDialog'
+import EnumSelect from '@/components/EnumSelect.vue'
+import { UserRole } from '@/enums'
+import { http } from '@/plugins/http'
+import { ElMessage } from 'element-plus'
+import { useClipboard } from '@vueuse/core'
+import { ElMessageBox } from 'element-plus'
+import { storeToRefs } from 'pinia'
+import { useUserStore } from '@/stores/user'
+
+const { user } = storeToRefs(useUserStore())
+const where = ref({})
+const timeFormatter = useTimeFormatter()
+const table = ref(null)
+const model = ref({})
+const rules = {
+    name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
+    phone: [{ required: true, message: '请输入手机', trigger: 'blur' }],
+    orgId: [{ required: true, message: '请输入企业ID', trigger: 'blur' }]
+}
+const { showEditDialog, onEdit } = setupEditDialog(model)
+async function submit() {
+    model.value.orgId = user.value.orgId
+    await http.put('/admin/org/users', [model.value])
+    ElMessage.success('保存成功')
+}
+async function deleteRow(row) {
+    try {
+        await ElMessageBox.confirm('此操作将永久删除数据, 是否继续?', '提示', {
+            type: 'warning'
+        })
+        await http.delete(`/admin/org/users/${row.id}`)
+        ElMessage.success('删除成功')
+        table.value.refresh()
+    } catch (error) {
+        if ('cancel' !== error) ElMessage.error(error.message)
+    }
+}
+</script>

+ 3 - 3
src/views/OrgView.vue

@@ -44,8 +44,8 @@
         <ElFormItem prop="systemPrompt" label="系统提示词">
             <ElInput type="textarea" v-model="model.systemPrompt" placeholder="请输入系统提示词" autosize />
         </ElFormItem>
-        <ElFormItem prop="questionTemplate" label="提问模版">
-            <ElInput type="textarea" v-model="model.questionTemplate" placeholder="请输入提问模版" autosize />
+        <ElFormItem prop="contextTemplate" label="上下文模版">
+            <ElInput type="textarea" v-model="model.contextTemplate" placeholder="请输入上下文模版" autosize />
         </ElFormItem>
 
         <ElFormItem prop="orgId" label="企业ID" v-if="model.roles && model.roles[0] === 'org'">
@@ -76,7 +76,7 @@ const rules = {
     assistantName: [{ required: true, message: '请输入助手名称', trigger: 'blur' }],
     description: [{ required: true, message: '请输入描述', trigger: 'blur' }],
     systemPrompt: [{ required: true, message: '请输入系统提示词', trigger: 'blur' }],
-    questionTemplate: [{ required: true, message: '请输入提问模版', trigger: 'blur' }]
+    contextTemplate: [{ required: true, message: '请输入上下文模版', trigger: 'blur' }]
 }
 const { showEditDialog, onEdit } = setupEditDialog(model)
 async function submit() {