feat:知识库精细化管理 (#96)
* feat(知识库管理): 新增知识库创建人选择功能 (#96) * "feat(知识库管理): 新增知识库权限修改功能并扩展操作列宽度 (#97)" * feat(文档解析): 添加embedding向量维度校验,确保维度为1024并提示使用bge-m3模型
This commit is contained in:
parent
98f3547521
commit
d0d7a24297
|
@ -576,6 +576,12 @@ def perform_parse(doc_id, doc_info, file_info, embedding_config):
|
||||||
embedding_data = embedding_resp.json()
|
embedding_data = embedding_resp.json()
|
||||||
q_1024_vec = embedding_data["data"][0]["embedding"]
|
q_1024_vec = embedding_data["data"][0]["embedding"]
|
||||||
print(f"[Parser-INFO] 获取embedding成功,长度: {len(q_1024_vec)}")
|
print(f"[Parser-INFO] 获取embedding成功,长度: {len(q_1024_vec)}")
|
||||||
|
# 检查向量维度是否为1024
|
||||||
|
if len(q_1024_vec) != 1024:
|
||||||
|
error_msg = f"[Parser-ERROR] Embedding向量维度不是1024,实际维度: {len(q_1024_vec)}, 建议使用bge-m3模型"
|
||||||
|
print(error_msg)
|
||||||
|
update_progress(-1, error_msg)
|
||||||
|
raise ValueError(error_msg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Parser-ERROR] 获取embedding失败: {e}")
|
print(f"[Parser-ERROR] 获取embedding失败: {e}")
|
||||||
raise Exception(f"[Parser-ERROR] 获取embedding失败: {e}")
|
raise Exception(f"[Parser-ERROR] 获取embedding失败: {e}")
|
||||||
|
|
|
@ -154,6 +154,7 @@ class KnowledgebaseService:
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_knowledgebase(cls, **data):
|
def create_knowledgebase(cls, **data):
|
||||||
"""创建知识库"""
|
"""创建知识库"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 检查知识库名称是否已存在
|
# 检查知识库名称是否已存在
|
||||||
exists = cls._check_name_exists(data['name'])
|
exists = cls._check_name_exists(data['name'])
|
||||||
|
@ -163,36 +164,42 @@ class KnowledgebaseService:
|
||||||
conn = cls._get_db_connection()
|
conn = cls._get_db_connection()
|
||||||
cursor = conn.cursor(dictionary=True)
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
|
||||||
# 获取最早的用户ID作为tenant_id和created_by
|
# 使用传入的 creator_id 作为 tenant_id 和 created_by
|
||||||
tenant_id = None
|
tenant_id = data.get('creator_id')
|
||||||
created_by = None
|
created_by = data.get('creator_id')
|
||||||
try:
|
|
||||||
query_earliest_user = """
|
if not tenant_id:
|
||||||
SELECT id FROM user
|
# 如果没有提供 creator_id,则使用默认值
|
||||||
WHERE create_time = (SELECT MIN(create_time) FROM user)
|
print("未提供 creator_id,尝试获取最早用户 ID")
|
||||||
LIMIT 1
|
try:
|
||||||
"""
|
query_earliest_user = """
|
||||||
cursor.execute(query_earliest_user)
|
SELECT id FROM user
|
||||||
earliest_user = cursor.fetchone()
|
WHERE create_time = (SELECT MIN(create_time) FROM user)
|
||||||
|
LIMIT 1
|
||||||
if earliest_user:
|
"""
|
||||||
tenant_id = earliest_user['id']
|
cursor.execute(query_earliest_user)
|
||||||
created_by = earliest_user['id'] # 使用最早用户ID作为created_by
|
earliest_user = cursor.fetchone()
|
||||||
print(f"使用创建时间最早的用户ID作为tenant_id和created_by: {tenant_id}")
|
|
||||||
else:
|
if earliest_user:
|
||||||
# 如果找不到用户,使用默认值
|
tenant_id = earliest_user['id']
|
||||||
|
created_by = earliest_user['id']
|
||||||
|
print(f"使用创建时间最早的用户ID作为tenant_id和created_by: {tenant_id}")
|
||||||
|
else:
|
||||||
|
# 如果找不到用户,使用默认值
|
||||||
|
tenant_id = "system"
|
||||||
|
created_by = "system"
|
||||||
|
print(f"未找到用户, 使用默认值作为tenant_id和created_by: {tenant_id}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取用户ID失败: {str(e)},使用默认值")
|
||||||
tenant_id = "system"
|
tenant_id = "system"
|
||||||
created_by = "system"
|
created_by = "system"
|
||||||
print(f"未找到用户, 使用默认值作为tenant_id和created_by: {tenant_id}")
|
else:
|
||||||
except Exception as e:
|
print(f"使用传入的 creator_id 作为 tenant_id 和 created_by: {tenant_id}")
|
||||||
print(f"获取用户ID失败: {str(e)},使用默认值")
|
|
||||||
tenant_id = "system"
|
|
||||||
created_by = "system"
|
|
||||||
|
|
||||||
|
|
||||||
# --- 获取动态 embd_id ---
|
# --- 获取动态 embd_id ---
|
||||||
dynamic_embd_id = None
|
dynamic_embd_id = None
|
||||||
default_embd_id = 'bge-m3___VLLM@VLLM' # Fallback default
|
default_embd_id = 'bge-m3' # Fallback default
|
||||||
try:
|
try:
|
||||||
query_embedding_model = """
|
query_embedding_model = """
|
||||||
SELECT llm_name
|
SELECT llm_name
|
||||||
|
@ -316,6 +323,10 @@ class KnowledgebaseService:
|
||||||
if 'description' in data:
|
if 'description' in data:
|
||||||
update_fields.append("description = %s")
|
update_fields.append("description = %s")
|
||||||
params.append(data['description'])
|
params.append(data['description'])
|
||||||
|
|
||||||
|
if 'permission' in data:
|
||||||
|
update_fields.append("permission = %s")
|
||||||
|
params.append(data['permission'])
|
||||||
|
|
||||||
# 更新时间
|
# 更新时间
|
||||||
current_time = datetime.now()
|
current_time = datetime.now()
|
||||||
|
|
|
@ -29,6 +29,7 @@ export function createKnowledgeBaseApi(data: {
|
||||||
description?: string
|
description?: string
|
||||||
language?: string
|
language?: string
|
||||||
permission?: string
|
permission?: string
|
||||||
|
creator_id: string
|
||||||
}) {
|
}) {
|
||||||
return request({
|
return request({
|
||||||
url: "/api/v1/knowledgebases",
|
url: "/api/v1/knowledgebases",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { FormInstance, UploadUserFile } from "element-plus"
|
import type { FormInstance, UploadUserFile } from "element-plus"
|
||||||
import { batchDeleteFilesApi, deleteFileApi, getFileListApi, uploadFileApi } from "@@/apis/files"
|
import { batchDeleteFilesApi, deleteFileApi, getFileListApi, uploadFileApi } from "@@/apis/files"
|
||||||
|
import { getTableDataApi } from "@@/apis/tables"
|
||||||
import { usePagination } from "@@/composables/usePagination"
|
import { usePagination } from "@@/composables/usePagination"
|
||||||
import { Delete, Download, Refresh, Search, Upload } from "@element-plus/icons-vue"
|
import { Delete, Download, Refresh, Search, Upload } from "@element-plus/icons-vue"
|
||||||
import { ElLoading, ElMessage, ElMessageBox } from "element-plus"
|
import { ElLoading, ElMessage, ElMessageBox } from "element-plus"
|
||||||
|
@ -393,7 +394,7 @@ onActivated(() => {
|
||||||
{{ (paginationData.currentPage - 1) * paginationData.pageSize + scope.$index + 1 }}
|
{{ (paginationData.currentPage - 1) * paginationData.pageSize + scope.$index + 1 }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="name" label="文档名" align="center" sortable="custom"/>
|
<el-table-column prop="name" label="文档名" align="center" sortable="custom" />
|
||||||
<el-table-column label="大小" align="center" width="120" sortable="custom">
|
<el-table-column label="大小" align="center" width="120" sortable="custom">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatFileSize(scope.row.size) }}
|
{{ formatFileSize(scope.row.size) }}
|
||||||
|
|
|
@ -18,13 +18,16 @@ import {
|
||||||
getSystemEmbeddingConfigApi,
|
getSystemEmbeddingConfigApi,
|
||||||
setSystemEmbeddingConfigApi
|
setSystemEmbeddingConfigApi
|
||||||
} from "@@/apis/kbs/knowledgebase"
|
} from "@@/apis/kbs/knowledgebase"
|
||||||
|
import { getTableDataApi } from "@@/apis/tables"
|
||||||
import { usePagination } from "@@/composables/usePagination"
|
import { usePagination } from "@@/composables/usePagination"
|
||||||
import { CaretRight, Delete, Loading, Plus, Refresh, Search, Setting, View } from "@element-plus/icons-vue"
|
import { CaretRight, Delete, Edit, Loading, Plus, Refresh, Search, Setting, View } from "@element-plus/icons-vue"
|
||||||
|
|
||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
import { ElMessage, ElMessageBox } from "element-plus"
|
import { ElMessage, ElMessageBox } from "element-plus"
|
||||||
import { nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, reactive, ref, watch } from "vue"
|
import { nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, reactive, ref, watch } from "vue"
|
||||||
import "element-plus/dist/index.css"
|
import "element-plus/dist/index.css"
|
||||||
import "element-plus/theme-chalk/el-message-box.css"
|
import "element-plus/theme-chalk/el-message-box.css"
|
||||||
|
|
||||||
import "element-plus/theme-chalk/el-message.css"
|
import "element-plus/theme-chalk/el-message.css"
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
@ -88,7 +91,8 @@ const knowledgeBaseForm = reactive({
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
language: "Chinese",
|
language: "Chinese",
|
||||||
permission: "me"
|
permission: "me",
|
||||||
|
creator_id: ""
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义API返回数据的接口
|
// 定义API返回数据的接口
|
||||||
|
@ -116,6 +120,9 @@ const knowledgeBaseFormRules = {
|
||||||
],
|
],
|
||||||
description: [
|
description: [
|
||||||
{ max: 200, message: "描述不能超过200个字符", trigger: "blur" }
|
{ max: 200, message: "描述不能超过200个字符", trigger: "blur" }
|
||||||
|
],
|
||||||
|
creator_id: [
|
||||||
|
{ required: true, message: "请选择创建人", trigger: "change" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +153,43 @@ const fileSortData = reactive({
|
||||||
sortOrder: "desc" // 默认排序顺序
|
sortOrder: "desc" // 默认排序顺序
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const editDialogVisible = ref(false)
|
||||||
|
const editForm = reactive({
|
||||||
|
id: "",
|
||||||
|
name: "",
|
||||||
|
permission: "me"
|
||||||
|
})
|
||||||
|
const editLoading = ref(false)
|
||||||
|
|
||||||
|
// 处理修改知识库
|
||||||
|
function handleEdit(row: KnowledgeBaseData) {
|
||||||
|
editDialogVisible.value = true
|
||||||
|
editForm.id = row.id
|
||||||
|
editForm.name = row.name
|
||||||
|
editForm.permission = row.permission
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交修改
|
||||||
|
function submitEdit() {
|
||||||
|
editLoading.value = true
|
||||||
|
// 调用修改知识库API
|
||||||
|
axios.put(`/api/v1/knowledgebases/${editForm.id}`, {
|
||||||
|
permission: editForm.permission
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
ElMessage.success("知识库权限修改成功")
|
||||||
|
editDialogVisible.value = false
|
||||||
|
// 刷新知识库列表
|
||||||
|
getTableData()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
ElMessage.error(`修改知识库权限失败: ${error?.message || "未知错误"}`)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
editLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 存储多选的表格数据
|
// 存储多选的表格数据
|
||||||
const multipleSelection = ref<KnowledgeBaseData[]>([])
|
const multipleSelection = ref<KnowledgeBaseData[]>([])
|
||||||
|
|
||||||
|
@ -186,6 +230,31 @@ function resetSearch() {
|
||||||
// 打开新建知识库对话框
|
// 打开新建知识库对话框
|
||||||
function handleCreate() {
|
function handleCreate() {
|
||||||
createDialogVisible.value = true
|
createDialogVisible.value = true
|
||||||
|
getUserList() // 获取用户列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户列表
|
||||||
|
function getUserList() {
|
||||||
|
userLoading.value = true
|
||||||
|
// 复用用户管理页面的API
|
||||||
|
getTableDataApi({
|
||||||
|
currentPage: 1,
|
||||||
|
size: 1000, // 获取足够多的用户
|
||||||
|
username: "",
|
||||||
|
email: "",
|
||||||
|
sort_by: "create_date",
|
||||||
|
sort_order: "desc"
|
||||||
|
}).then(({ data }) => {
|
||||||
|
userList.value = data.list.map((user: any) => ({
|
||||||
|
id: user.id,
|
||||||
|
username: user.username
|
||||||
|
}))
|
||||||
|
}).catch(() => {
|
||||||
|
userList.value = []
|
||||||
|
ElMessage.error("获取用户列表失败")
|
||||||
|
}).finally(() => {
|
||||||
|
userLoading.value = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交新建知识库
|
// 提交新建知识库
|
||||||
|
@ -982,6 +1051,10 @@ function isLoadingStatus(status: string) {
|
||||||
function shouldShowProgressCount(status: string) {
|
function shouldShowProgressCount(status: string) {
|
||||||
return !["starting", "not_found"].includes(status)
|
return !["starting", "not_found"].includes(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用户列表相关状态
|
||||||
|
const userList = ref<{ id: number, username: string }[]>([])
|
||||||
|
const userLoading = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -1037,8 +1110,8 @@ function shouldShowProgressCount(status: string) {
|
||||||
{{ (paginationData.currentPage - 1) * paginationData.pageSize + scope.$index + 1 }}
|
{{ (paginationData.currentPage - 1) * paginationData.pageSize + scope.$index + 1 }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="name" label="知识库名称" align="center" min-width="120" sortable="custom"/>
|
<el-table-column prop="name" label="知识库名称" align="center" min-width="120" sortable="custom" />
|
||||||
<el-table-column prop="description" label="描述" align="center" min-width="180" show-overflow-tooltip />
|
<el-table-column prop="description" label="描述" align="center" min-width="180" show-overflow-tooltip />
|
||||||
<el-table-column prop="doc_num" label="文档数量" align="center" width="120" />
|
<el-table-column prop="doc_num" label="文档数量" align="center" width="120" />
|
||||||
<el-table-column label="语言" align="center" width="100">
|
<el-table-column label="语言" align="center" width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
|
@ -1060,7 +1133,7 @@ function shouldShowProgressCount(status: string) {
|
||||||
{{ scope.row.create_date }}
|
{{ scope.row.create_date }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column fixed="right" label="操作" width="180" align="center">
|
<el-table-column fixed="right" label="操作" width="300" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
@ -1072,6 +1145,16 @@ function shouldShowProgressCount(status: string) {
|
||||||
>
|
>
|
||||||
查看
|
查看
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
text
|
||||||
|
bg
|
||||||
|
size="small"
|
||||||
|
:icon="Edit"
|
||||||
|
@click="handleEdit(scope.row)"
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="danger"
|
type="danger"
|
||||||
text
|
text
|
||||||
|
@ -1268,6 +1351,22 @@ function shouldShowProgressCount(status: string) {
|
||||||
<el-option label="英文" value="English" />
|
<el-option label="英文" value="English" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="创建人" prop="creator_id">
|
||||||
|
<el-select
|
||||||
|
v-model="knowledgeBaseForm.creator_id"
|
||||||
|
placeholder="请选择创建人"
|
||||||
|
style="width: 100%"
|
||||||
|
filterable
|
||||||
|
:loading="userLoading"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="user in userList"
|
||||||
|
:key="user.id"
|
||||||
|
:label="user.username"
|
||||||
|
:value="user.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="权限" prop="permission">
|
<el-form-item label="权限" prop="permission">
|
||||||
<el-select v-model="knowledgeBaseForm.permission" placeholder="请选择权限">
|
<el-select v-model="knowledgeBaseForm.permission" placeholder="请选择权限">
|
||||||
<el-option label="个人" value="me" />
|
<el-option label="个人" value="me" />
|
||||||
|
@ -1289,6 +1388,45 @@ function shouldShowProgressCount(status: string) {
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 修改知识库对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="editDialogVisible"
|
||||||
|
title="修改知识库权限"
|
||||||
|
width="40%"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<el-form-item label="知识库名称">
|
||||||
|
<span>{{ editForm.name }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="权限设置">
|
||||||
|
<el-select v-model="editForm.permission" placeholder="请选择权限">
|
||||||
|
<el-option label="个人" value="me" />
|
||||||
|
<el-option label="团队" value="team" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<div style="color: #909399; font-size: 12px; line-height: 1.5;">
|
||||||
|
个人权限:仅自己可见和使用<br>
|
||||||
|
团队权限:团队成员可见和使用
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="editDialogVisible = false">
|
||||||
|
取消
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="editLoading"
|
||||||
|
@click="submitEdit"
|
||||||
|
>
|
||||||
|
确认修改
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 文档对话框 -->
|
<!-- 文档对话框 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="addDocumentDialogVisible"
|
v-model="addDocumentDialogVisible"
|
||||||
|
@ -1314,11 +1452,11 @@ function shouldShowProgressCount(status: string) {
|
||||||
{{ formatFileType(scope.row.type) }}
|
{{ formatFileType(scope.row.type) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="create_date" label="创建时间" align="center" width="180" sortable="custom">
|
<el-table-column prop="create_date" label="创建时间" align="center" width="180" sortable="custom">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ scope.row.create_date }}
|
{{ scope.row.create_date }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 分页控件 -->
|
<!-- 分页控件 -->
|
||||||
|
@ -1554,4 +1692,4 @@ function shouldShowProgressCount(status: string) {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
animation: rotating 2s linear infinite;
|
animation: rotating 2s linear infinite;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue