feat: 简化知识库导入——只输入NodeToken,自动获取标题和SpaceID
- admin_knowledge.py: /knowledge/import 改为单条导入,自动获取 title+space_id - feishu_service.py: 新增 get_node_info() 公开方法 - App.vue: 简化 UI,只保留 NodeToken 输入框和导入按钮 - api.ts: 更新类型定义
This commit is contained in:
@@ -37,7 +37,7 @@ const chatDetailOpen = ref(false);
|
|||||||
const recordTab = ref("chats");
|
const recordTab = ref("chats");
|
||||||
const editingModelId = ref<number | null>(null);
|
const editingModelId = ref<number | null>(null);
|
||||||
const editingKnowledgeId = ref<number | null>(null);
|
const editingKnowledgeId = ref<number | null>(null);
|
||||||
const batchParentToken = ref("");
|
const batchNodeToken = ref("");
|
||||||
const batchImporting = ref(false);
|
const batchImporting = ref(false);
|
||||||
const agentDebugging = ref(false);
|
const agentDebugging = ref(false);
|
||||||
const agentPreviewMessages = ref<{ role: "user" | "assistant" | "system"; content: string }[]>([
|
const agentPreviewMessages = ref<{ role: "user" | "assistant" | "system"; content: string }[]>([
|
||||||
@@ -592,16 +592,20 @@ function resetKnowledgeForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function batchImportKnowledge() {
|
async function batchImportKnowledge() {
|
||||||
const parentToken = batchParentToken.value.trim();
|
const token = batchNodeToken.value.trim();
|
||||||
if (!parentToken) {
|
if (!token) {
|
||||||
ElMessage.warning("请输入父节点 NodeToken");
|
ElMessage.warning("请输入 NodeToken");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
batchImporting.value = true;
|
batchImporting.value = true;
|
||||||
try {
|
try {
|
||||||
const result = await api.importKnowledgeFromSpace({ parent_node_token: parentToken });
|
const result = await api.importKnowledgeFromSpace({ node_token: token });
|
||||||
ElMessage.success(`导入完成:新增 ${result.imported} 个,跳过 ${result.skipped} 个`);
|
if (result.imported > 0) {
|
||||||
batchParentToken.value = "";
|
ElMessage.success(`导入成功:${result.name}(id=${result.id})`);
|
||||||
|
} else {
|
||||||
|
ElMessage.info(`该节点已存在:${result.name}(id=${result.id})`);
|
||||||
|
}
|
||||||
|
batchNodeToken.value = "";
|
||||||
await loadCurrentMenu();
|
await loadCurrentMenu();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
ElMessage.error(e.message || "导入失败");
|
ElMessage.error(e.message || "导入失败");
|
||||||
@@ -1125,8 +1129,8 @@ function buildChatQuery() {
|
|||||||
|
|
||||||
<el-divider />
|
<el-divider />
|
||||||
<div class="batch-import-row">
|
<div class="batch-import-row">
|
||||||
<el-input v-model="batchParentToken" placeholder="输入父节点 NodeToken(如 wikcnXXX),递归导入所有子节点" style="max-width: 400px" />
|
<el-input v-model="batchNodeToken" placeholder="输入 NodeToken(如 wikcnXXX),自动获取标题与 SpaceID" style="max-width: 400px" />
|
||||||
<el-button type="primary" @click="batchImportKnowledge" :loading="batchImporting">递归导入</el-button>
|
<el-button type="primary" @click="batchImportKnowledge" :loading="batchImporting">导入</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="knowledge" stripe>
|
<el-table :data="knowledge" stripe>
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
|
|||||||
@@ -106,8 +106,8 @@ export const api = {
|
|||||||
updateKnowledge: (id: number, payload: Record<string, unknown>) =>
|
updateKnowledge: (id: number, payload: Record<string, unknown>) =>
|
||||||
request<KnowledgeItem>(`/admin/knowledge/${id}`, { method: "PUT", body: JSON.stringify(payload) }),
|
request<KnowledgeItem>(`/admin/knowledge/${id}`, { method: "PUT", body: JSON.stringify(payload) }),
|
||||||
deleteKnowledge: (id: number) => request<null>(`/admin/knowledge/${id}`, { method: "DELETE" }),
|
deleteKnowledge: (id: number) => request<null>(`/admin/knowledge/${id}`, { method: "DELETE" }),
|
||||||
importKnowledgeFromSpace: (body: Record<string, string>) =>
|
importKnowledgeFromSpace: (body: { node_token: string }) =>
|
||||||
request<{ imported: number; skipped: number; nodes: { name: string; nodeToken: string; status: string; id?: number }[] }>(
|
request<{ imported: number; skipped: number; id: number; name: string }>(
|
||||||
"/admin/knowledge/import",
|
"/admin/knowledge/import",
|
||||||
{ method: "POST", body: JSON.stringify(body) },
|
{ method: "POST", body: JSON.stringify(body) },
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -28,46 +28,38 @@ def import_knowledge_from_space(
|
|||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_admin: Admin = Depends(get_current_admin),
|
current_admin: Admin = Depends(get_current_admin),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
parent_node_token = payload.get("parent_node_token", "").strip()
|
node_token = payload.get("node_token", "").strip()
|
||||||
if not parent_node_token:
|
if not node_token:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="父节点 NodeToken 不能为空")
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="NodeToken 不能为空")
|
||||||
|
|
||||||
nodes = FeishuKnowledgeService.import_nodes_from_parent(parent_node_token, db)
|
info = FeishuKnowledgeService.get_node_info(node_token, db)
|
||||||
if not nodes:
|
space_id = info.get("space_id", "")
|
||||||
return api_success({"imported": 0, "skipped": 0, "nodes": []})
|
if not space_id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=f"无法获取节点 {node_token} 的 SpaceID",
|
||||||
|
)
|
||||||
|
|
||||||
# 获取已存在的 node_id,避免重复导入
|
# 检查是否已存在
|
||||||
existing_nodes = {
|
existing = db.scalar(select(Knowledge).where(Knowledge.feishu_node_id == node_token))
|
||||||
row.feishu_node_id for row in db.scalars(select(Knowledge)).all() if row.feishu_node_id
|
if existing is not None:
|
||||||
}
|
return api_success({"imported": 0, "skipped": 1, "id": existing.id, "name": existing.name})
|
||||||
|
|
||||||
imported = 0
|
|
||||||
skipped = 0
|
|
||||||
results = []
|
|
||||||
for node in nodes:
|
|
||||||
node_token = node["node_token"]
|
|
||||||
if node_token in existing_nodes:
|
|
||||||
skipped += 1
|
|
||||||
results.append({"name": node["name"], "nodeToken": node_token, "status": "skipped"})
|
|
||||||
continue
|
|
||||||
|
|
||||||
knowledge = Knowledge(
|
knowledge = Knowledge(
|
||||||
name=node["name"],
|
name=info.get("title", node_token),
|
||||||
feishu_space_id=space_id,
|
feishu_space_id=space_id,
|
||||||
feishu_node_id=node_token,
|
feishu_node_id=node_token,
|
||||||
status=1,
|
status=1,
|
||||||
remark=f"obj_type={node.get('obj_type', '')}",
|
remark=f"obj_type={info.get('obj_type', '')}",
|
||||||
)
|
)
|
||||||
db.add(knowledge)
|
db.add(knowledge)
|
||||||
db.flush()
|
db.flush()
|
||||||
imported += 1
|
|
||||||
results.append({"name": node["name"], "nodeToken": node_token, "status": "imported", "id": knowledge.id})
|
|
||||||
OperationLogService.write(
|
OperationLogService.write(
|
||||||
db, admin_id=current_admin.id, module="knowledge", action="import", target_id=knowledge.id
|
db, admin_id=current_admin.id, module="knowledge", action="import", target_id=knowledge.id
|
||||||
)
|
)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
return api_success({"imported": imported, "skipped": skipped, "nodes": results})
|
db.refresh(knowledge)
|
||||||
|
return api_success({"imported": 1, "skipped": 0, "id": knowledge.id, "name": knowledge.name})
|
||||||
|
|
||||||
|
|
||||||
@router.post("/knowledge")
|
@router.post("/knowledge")
|
||||||
|
|||||||
@@ -330,6 +330,17 @@ class FeishuKnowledgeService:
|
|||||||
cls._content_cache.clear()
|
cls._content_cache.clear()
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_node_info(cls, node_token: str, db: Session | None = None) -> dict:
|
||||||
|
"""获取 Wiki 节点基本信息(space_id、标题、obj_type 等)"""
|
||||||
|
config = _feishu_retrieval_config(db)
|
||||||
|
if not config.app_id or not config.app_secret:
|
||||||
|
raise ExternalServiceError(
|
||||||
|
"未配置飞书应用凭证,无法读取飞书知识库。",
|
||||||
|
provider="feishu",
|
||||||
|
)
|
||||||
|
return _get_wiki_node_info(node_token, config)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def import_nodes_from_parent(
|
def import_nodes_from_parent(
|
||||||
cls,
|
cls,
|
||||||
|
|||||||
Reference in New Issue
Block a user