chore: 添加 bug 修复发布流程和更新 CallStatsComponent 及 KnowledgeStatsComponent 以优化数据展示

This commit is contained in:
Wenjie Zhang 2025-11-07 02:55:35 +08:00
parent b8bfc97b80
commit bf3e8cdedf
4 changed files with 163 additions and 175 deletions

View File

@ -58,6 +58,43 @@ test: 添加测试
chore: 构建过程或辅助工具的变动
```
## 🐞 Bug 修复发布流程
如果在发布 `v0.3.0` 后发现 bug
### ✅ 情况 1main 上没有未完成的新功能
直接在 main 修复并发布:
```bash
git commit -m "fix: resolve config parser crash"
git tag -a v0.3.1 -m "Hotfix v0.3.1"
git push origin main --tags
```
### ⚙️ 情况 2main 上已有新功能未完成
从上一个 tag 建立 hotfix 分支:
```bash
git checkout -b hotfix/0.3.1 v0.3.0
# 修复问题
git commit -m "fix: resolve config parser crash"
git push origin hotfix/0.3.1
# 测试后合并回 main 并打 tag
git checkout main
git merge --no-ff hotfix/0.3.1
git tag -a v0.3.1 -m "Hotfix v0.3.1"
git push origin main --tags
# 删除临时分支
git branch -d hotfix/0.3.1
git push origin --delete hotfix/0.3.1
```
### 测试要求
::: tip 测试

View File

@ -221,152 +221,6 @@ async def get_neo4j_node(
# =============================================================================
@graph.get("/lightrag/test-data")
async def create_test_graph_data(
db_id: str = Query(..., description="数据库ID"), current_user: User = Depends(get_admin_user)
):
"""
创建测试图谱数据临时API用于演示图谱功能
"""
try:
logger.info(f"创建测试图谱数据 - db_id: {db_id}")
# 检查是否是 LightRAG 数据库
if not knowledge_base.is_lightrag_database(db_id):
raise HTTPException(
status_code=400, detail=f"数据库 {db_id} 不是 LightRAG 类型,图谱功能仅支持 LightRAG 知识库"
)
# 获取 LightRAG 实例
rag_instance = await knowledge_base._get_lightrag_instance(db_id)
if not rag_instance:
raise HTTPException(status_code=404, detail=f"LightRAG 数据库 {db_id} 不存在或无法访问")
# 创建测试图谱数据
test_nodes = [
{
'id': '宋晓宁',
'labels': ['人物'],
'entity_type': 'person',
'properties': {
'entity_id': '宋晓宁',
'entity_type': 'person',
'description': '人工智能学院项目负责人',
'file_path': 'test'
}
},
{
'id': '江南大学',
'labels': ['机构'],
'entity_type': 'organization',
'properties': {
'entity_id': '江南大学',
'entity_type': 'organization',
'description': '江南大学',
'file_path': 'test'
}
},
{
'id': '食品安全知识图谱',
'labels': ['项目'],
'entity_type': 'project',
'properties': {
'entity_id': '食品安全知识图谱',
'entity_type': 'project',
'description': '食品安全知识图谱和监管控制平台的构建与应用示范',
'file_path': 'test'
}
},
{
'id': '无锡君桥汽车租赁有限公司',
'labels': ['公司'],
'entity_type': 'company',
'properties': {
'entity_id': '无锡君桥汽车租赁有限公司',
'entity_type': 'company',
'description': '汽车租赁服务公司',
'file_path': 'test'
}
},
{
'id': '租车费',
'labels': ['费用'],
'entity_type': 'expense',
'properties': {
'entity_id': '租车费',
'entity_type': 'expense',
'description': '租车费用300元',
'file_path': 'test'
}
}
]
test_edges = [
{
'id': '宋晓宁-江南大学-工作于',
'source': '宋晓宁',
'target': '江南大学',
'type': 'WORKS_AT',
'properties': {
'description': '宋晓宁工作于江南大学',
'keywords': '工作关系'
}
},
{
'id': '宋晓宁-食品安全知识图谱-负责',
'source': '宋晓宁',
'target': '食品安全知识图谱',
'type': 'RESPONSIBLE_FOR',
'properties': {
'description': '宋晓宁负责食品安全知识图谱项目',
'keywords': '负责关系'
}
},
{
'id': '宋晓宁-租车费-产生',
'source': '宋晓宁',
'target': '租车费',
'type': 'INCURRED',
'properties': {
'description': '宋晓宁产生租车费用',
'keywords': '费用关系'
}
},
{
'id': '租车费-无锡君桥汽车租赁有限公司-支付给',
'source': '租车费',
'target': '无锡君桥汽车租赁有限公司',
'type': 'PAID_TO',
'properties': {
'description': '租车费支付给无锡君桥汽车租赁有限公司',
'keywords': '支付关系'
}
}
]
result = {
"success": True,
"data": {
"nodes": test_nodes,
"edges": test_edges,
"is_truncated": False,
"total_nodes": len(test_nodes),
"total_edges": len(test_edges),
},
}
logger.info(f"成功创建测试图谱 - 节点数: {len(test_nodes)}, 边数: {len(test_edges)}")
return result
except HTTPException:
# 重新抛出 HTTP 异常
raise
except Exception as e:
logger.error(f"创建测试图谱数据失败: {e}")
logger.error(f"Traceback: {traceback.format_exc()}")
raise HTTPException(status_code=500, detail=f"创建测试图谱数据失败: {str(e)}")
@graph.get("/lightrag/stats")
async def get_lightrag_stats(
db_id: str = Query(..., description="数据库ID"), current_user: User = Depends(get_admin_user)

View File

@ -52,7 +52,7 @@ const props = defineProps({
const callStatsData = ref(null)
const callStatsLoading = ref(false)
const callTimeRange = ref('14days')
const callDataType = ref('models')
const callDataType = ref('agents')
const timeRangeOptions = [
{ value: '14hours', label: '近14小时' },
{ value: '14days', label: '近14天' },

View File

@ -55,7 +55,16 @@
<a-col :span="24">
<div class="chart-container">
<h4>文件类型分布</h4>
<div ref="fileTypeChartRef" class="chart"></div>
<div ref="fileTypeChartRef" class="chart donut-chart-container">
<div class="carousel-info" v-if="fileTypeData.length > 0">
<div class="carousel-item" :class="{ active: currentCarouselIndex === index }"
v-for="(item, index) in fileTypeData" :key="item.name">
<div class="carousel-label">{{ item.name }}</div>
<div class="carousel-value">{{ item.value }}</div>
<div class="carousel-percent">{{ ((item.value / totalFiles) * 100).toFixed(1) }}%</div>
</div>
</div>
</div>
</div>
</a-col>
</a-row>
@ -114,6 +123,12 @@ const fileTypeChartRef = ref(null)
let dbTypeChart = null
let fileTypeChart = null
// File type chart data for carousel
const fileTypeData = ref([])
const totalFiles = ref(0)
const currentCarouselIndex = ref(0)
let carouselTimer = null
//
const formattedStorageSize = computed(() => {
const size = props.knowledgeStats?.total_storage_size || 0
@ -219,7 +234,14 @@ const initFileTypeChart = () => {
const data = Object.entries(fileTypesData).map(([type, count]) => ({
name: type || '未知',
value: count
}))
})).sort((a, b) => b.value - a.value) //
//
fileTypeData.value = data
totalFiles.value = data.reduce((sum, item) => sum + item.value, 0)
//
startCarousel()
const option = {
tooltip: {
@ -232,28 +254,33 @@ const initFileTypeChart = () => {
},
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'horizontal',
bottom: '5%',
left: 'center',
itemGap: 16,
itemWidth: 10,
itemHeight: 10,
textStyle: {
fontSize: 11,
color: '#666'
}
},
series: [{
name: '文件类型',
type: 'pie',
radius: ['30%', '70%'],
center: ['50%', '60%'],
avoidLabelOverlap: false,
radius: ['45%', '75%'], //
center: ['50%', '45%'], //
avoidLabelOverlap: true, //
itemStyle: {
borderRadius: 6,
borderRadius: 8,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {c}'
show: false // 使
},
emphasis: {
label: {
show: true,
fontSize: '14',
fontWeight: 'bold',
color: '#333'
},
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
@ -261,7 +288,7 @@ const initFileTypeChart = () => {
}
},
labelLine: {
show: true
show: false // 线
},
data: data,
color: getColorPalette()
@ -270,6 +297,11 @@ const initFileTypeChart = () => {
fileTypeChart.setOption(option)
} else {
//
fileTypeData.value = []
totalFiles.value = 0
stopCarousel()
//
const option = {
tooltip: {
@ -285,25 +317,18 @@ const initFileTypeChart = () => {
series: [{
name: '文件类型',
type: 'pie',
radius: ['30%', '70%'],
center: ['50%', '60%'],
avoidLabelOverlap: false,
radius: ['45%', '75%'],
center: ['50%', '45%'],
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 6,
borderRadius: 8,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {c}'
show: false
},
emphasis: {
label: {
show: true,
fontSize: '14',
fontWeight: 'bold',
color: '#333'
},
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
@ -311,7 +336,7 @@ const initFileTypeChart = () => {
}
},
labelLine: {
show: true
show: false
},
data: [
{ name: '暂无数据', value: 1 }
@ -324,6 +349,27 @@ const initFileTypeChart = () => {
}
}
//
const startCarousel = () => {
stopCarousel() //
if (fileTypeData.value.length <= 1) return
//
currentCarouselIndex.value = 0
// 3
carouselTimer = setInterval(() => {
currentCarouselIndex.value = (currentCarouselIndex.value + 1) % fileTypeData.value.length
}, 3000)
}
const stopCarousel = () => {
if (carouselTimer) {
clearInterval(carouselTimer)
carouselTimer = null
}
}
//
const updateCharts = () => {
nextTick(() => {
@ -351,6 +397,7 @@ onMounted(() => {
//
const cleanup = () => {
window.removeEventListener('resize', handleResize)
stopCarousel() //
if (dbTypeChart) {
dbTypeChart.dispose()
dbTypeChart = null
@ -409,5 +456,55 @@ defineExpose({
.chart--thin {
height: 80px;
}
//
.donut-chart-container {
position: relative;
.carousel-info {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
pointer-events: none;
z-index: 10;
.carousel-item {
opacity: 0;
transition: opacity 0.5s ease-in-out;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
white-space: nowrap;
&.active {
opacity: 1;
}
.carousel-label {
font-size: 14px;
font-weight: 600;
color: #666;
margin-bottom: 4px;
}
.carousel-value {
font-size: 24px;
font-weight: 700;
color: #333;
margin-bottom: 2px;
line-height: 1;
}
.carousel-percent {
font-size: 12px;
color: #999;
font-weight: 500;
}
}
}
}
}
</style>