fix: 知识库key格式统一、过滤逻辑修复、布局宽度稳定

This commit is contained in:
simonkoson
2026-05-27 14:06:16 +08:00
parent a00bcd6c73
commit fff2cfe395
2 changed files with 94 additions and 70 deletions
+70 -52
View File
@@ -27,6 +27,30 @@ class KnowledgeService:
"报题单": "baoti",
}
# 来源大类固定显示顺序(制片人 Obsidian 习惯)
SOURCE_TYPE_ORDER = [
"manuscript", # 节目文稿
"military_report", # 杂志文章
"baoti", # 报题单
"manual", # 其他
]
# 二级分组维度映射(与前端 useKnowledgeGrouping.js 的 SECONDARY_GROUP_FIELD 保持一致)
# key = source_type, value = 用来做二级分组的字段名,None = 不建二级节点
SECONDARY_GROUP_FIELD = {
"manuscript": "author", # 节目文稿 → 按作者(编导)
"military_report": "source_detail", # 杂志文章 → 按出处
"baoti": None, # 报题单 → 不分组
"manual": None, # 其他 → 不分组
}
SOURCE_TYPE_LABEL = {
"military_report": "杂志文章",
"manuscript": "节目文稿",
"baoti": "报题单",
"manual": "其他",
}
def __init__(self):
self.embedder = EmbeddingService()
@@ -243,46 +267,22 @@ class KnowledgeService:
def get_grouped_items(self) -> list[dict]:
"""
按 source_type → source_detail 两层聚合,返回树形结构数据。
按 source_type → 二级字段(author / source_detail两层聚合,返回树形结构数据。
按 SOURCE_TYPE_ORDER 固定顺序排列。
返回格式:
[
{
"key": "全部",
"label": "全部(N条)",
"count": 总条目数,
"children": [
{
"key": "manuscript",
"label": "节目文稿(n条)",
"count": n,
"children": [ # source_detail 为 null 时直接为空数组,不造空节点
{"key": "...", "label": "具体出处", "count": n}
]
},
...
]
}
]
二级节点 key 格式:`{source_type}|{二级字段名}|{字段值}`
例:manuscript|author|左鑫
military_report|source_detail|航空知识 2026年第1期
注意:source_detail 为 null 的条目(如节目文稿、报题单),
归入对应 source_type 大类下,但不单独成子节点(大类的 children 直接为其条目列表,
但前端 Tree 组件渲染时,若 children 为空则不展示二级节点,大类直接可点击)。
二级字段值为 null / 空字串 → 归入对应大类,不造空节点。
"""
SOURCE_TYPE_LABEL = {
"military_report": "杂志文章",
"manuscript": "节目文稿",
"baoti": "报题单",
"manual": "其他",
}
with Session(engine) as session:
items = session.exec(select(KnowledgeItem)).all()
total_count = len(items)
# 按 source_type 分组
type_groups: dict = {}
# 按 source_type 分组,初始化所有已知类别(保证空类别也按顺序出现)
type_groups: dict = {st: [] for st in self.SOURCE_TYPE_ORDER}
for item in items:
st = item.source_type or "manual"
if st not in type_groups:
@@ -290,30 +290,48 @@ class KnowledgeService:
type_groups[st].append(item)
children = []
for st, st_items in type_groups.items():
# 按 source_detail 细分
detail_groups: dict = {}
for item in st_items:
tags = item.tags or {}
sd = tags.get("source_detail") if isinstance(tags, dict) else None
if sd not in detail_groups:
detail_groups[sd] = []
detail_groups[sd].append(item)
for st in self.SOURCE_TYPE_ORDER:
st_items = type_groups.get(st, [])
if not st_items:
# 空类别也按顺序出现(0条)
children.append({
"key": st,
"label": f"{self.SOURCE_TYPE_LABEL.get(st, st)}0条)",
"count": 0,
"children": [],
})
continue
# 构建二级节点:仅 source_detail 非 null 的才建子节点
secondary_field = self.SECONDARY_GROUP_FIELD.get(st)
grandchildren = []
for sd, sd_items in detail_groups.items():
if sd is not None:
grandchildren.append({
"key": f"{st}|{sd}",
"label": f"{sd}{len(sd_items)}条)",
"count": len(sd_items),
})
label = SOURCE_TYPE_LABEL.get(st, st)
if secondary_field is not None:
# 按二级字段分组
detail_groups: dict = {}
for item in st_items:
# 取二级字段值:author 直接取,source_detail 从 tags(JSONB) 取
if secondary_field == "source_detail":
tags = item.tags or {}
sd = tags.get("source_detail") if isinstance(tags, dict) else None
field_val = sd
else:
field_val = (getattr(item, secondary_field, None) or "").strip() or None
if field_val not in detail_groups:
detail_groups[field_val] = []
detail_groups[field_val].append(item)
for sd, sd_items in detail_groups.items():
if sd is not None:
grandchildren.append({
"key": f"{st}|{secondary_field}|{sd}",
"label": f"{sd}{len(sd_items)}条)",
"count": len(sd_items),
})
children.append({
"key": st,
"label": f"{label}{len(st_items)}条)",
"label": f"{self.SOURCE_TYPE_LABEL.get(st, st)}{len(st_items)}条)",
"count": len(st_items),
"children": grandchildren,
})
@@ -323,4 +341,4 @@ class KnowledgeService:
"label": f"全部({total_count}条)",
"count": total_count,
"children": children,
}]
}]