diff --git a/docs/changelog/contributing.md b/docs/changelog/contributing.md index fced7c3b..9f77ead9 100644 --- a/docs/changelog/contributing.md +++ b/docs/changelog/contributing.md @@ -58,6 +58,43 @@ test: 添加测试 chore: 构建过程或辅助工具的变动 ``` + +## 🐞 Bug 修复发布流程 + +如果在发布 `v0.3.0` 后发现 bug: + +### ✅ 情况 1:main 上没有未完成的新功能 + +直接在 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 +``` + +### ⚙️ 情况 2:main 上已有新功能未完成 + +从上一个 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 测试 diff --git a/server/routers/graph_router.py b/server/routers/graph_router.py index e3da687d..3c024650 100644 --- a/server/routers/graph_router.py +++ b/server/routers/graph_router.py @@ -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) diff --git a/web/src/components/dashboard/CallStatsComponent.vue b/web/src/components/dashboard/CallStatsComponent.vue index 03b20f01..d0542977 100644 --- a/web/src/components/dashboard/CallStatsComponent.vue +++ b/web/src/components/dashboard/CallStatsComponent.vue @@ -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天' }, diff --git a/web/src/components/dashboard/KnowledgeStatsComponent.vue b/web/src/components/dashboard/KnowledgeStatsComponent.vue index 5b22304b..2607303c 100644 --- a/web/src/components/dashboard/KnowledgeStatsComponent.vue +++ b/web/src/components/dashboard/KnowledgeStatsComponent.vue @@ -55,7 +55,16 @@

文件类型分布

-
+
+ +
@@ -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}
{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; + } + } + } + } } \ No newline at end of file