chore: 添加 bug 修复发布流程和更新 CallStatsComponent 及 KnowledgeStatsComponent 以优化数据展示
This commit is contained in:
parent
b8bfc97b80
commit
bf3e8cdedf
@ -58,6 +58,43 @@ test: 添加测试
|
|||||||
chore: 构建过程或辅助工具的变动
|
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 测试
|
::: tip 测试
|
||||||
|
|||||||
@ -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")
|
@graph.get("/lightrag/stats")
|
||||||
async def get_lightrag_stats(
|
async def get_lightrag_stats(
|
||||||
db_id: str = Query(..., description="数据库ID"), current_user: User = Depends(get_admin_user)
|
db_id: str = Query(..., description="数据库ID"), current_user: User = Depends(get_admin_user)
|
||||||
|
|||||||
@ -52,7 +52,7 @@ const props = defineProps({
|
|||||||
const callStatsData = ref(null)
|
const callStatsData = ref(null)
|
||||||
const callStatsLoading = ref(false)
|
const callStatsLoading = ref(false)
|
||||||
const callTimeRange = ref('14days')
|
const callTimeRange = ref('14days')
|
||||||
const callDataType = ref('models')
|
const callDataType = ref('agents')
|
||||||
const timeRangeOptions = [
|
const timeRangeOptions = [
|
||||||
{ value: '14hours', label: '近14小时' },
|
{ value: '14hours', label: '近14小时' },
|
||||||
{ value: '14days', label: '近14天' },
|
{ value: '14days', label: '近14天' },
|
||||||
|
|||||||
@ -55,7 +55,16 @@
|
|||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<h4>文件类型分布</h4>
|
<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>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@ -114,6 +123,12 @@ const fileTypeChartRef = ref(null)
|
|||||||
let dbTypeChart = null
|
let dbTypeChart = null
|
||||||
let fileTypeChart = 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 formattedStorageSize = computed(() => {
|
||||||
const size = props.knowledgeStats?.total_storage_size || 0
|
const size = props.knowledgeStats?.total_storage_size || 0
|
||||||
@ -219,7 +234,14 @@ const initFileTypeChart = () => {
|
|||||||
const data = Object.entries(fileTypesData).map(([type, count]) => ({
|
const data = Object.entries(fileTypesData).map(([type, count]) => ({
|
||||||
name: type || '未知',
|
name: type || '未知',
|
||||||
value: count
|
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 = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@ -232,28 +254,33 @@ const initFileTypeChart = () => {
|
|||||||
},
|
},
|
||||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
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: [{
|
series: [{
|
||||||
name: '文件类型',
|
name: '文件类型',
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
radius: ['30%', '70%'],
|
radius: ['45%', '75%'], // 调整为更大的环,为中心信息留出更多空间
|
||||||
center: ['50%', '60%'],
|
center: ['50%', '45%'], // 向上移动,为中心和底部图例留出空间
|
||||||
avoidLabelOverlap: false,
|
avoidLabelOverlap: true, // 避免标签重叠
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderRadius: 6,
|
borderRadius: 8,
|
||||||
borderColor: '#fff',
|
borderColor: '#fff',
|
||||||
borderWidth: 2
|
borderWidth: 2
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: false // 隐藏饼图上的标签,使用图例代替
|
||||||
formatter: '{b}: {c}'
|
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
fontSize: '14',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#333'
|
|
||||||
},
|
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
shadowBlur: 10,
|
shadowBlur: 10,
|
||||||
shadowOffsetX: 0,
|
shadowOffsetX: 0,
|
||||||
@ -261,7 +288,7 @@ const initFileTypeChart = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
labelLine: {
|
labelLine: {
|
||||||
show: true
|
show: false // 隐藏标签线
|
||||||
},
|
},
|
||||||
data: data,
|
data: data,
|
||||||
color: getColorPalette()
|
color: getColorPalette()
|
||||||
@ -270,6 +297,11 @@ const initFileTypeChart = () => {
|
|||||||
|
|
||||||
fileTypeChart.setOption(option)
|
fileTypeChart.setOption(option)
|
||||||
} else {
|
} else {
|
||||||
|
// 清空轮播数据
|
||||||
|
fileTypeData.value = []
|
||||||
|
totalFiles.value = 0
|
||||||
|
stopCarousel()
|
||||||
|
|
||||||
// 如果没有文件类型数据,显示一个占位图表
|
// 如果没有文件类型数据,显示一个占位图表
|
||||||
const option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@ -285,25 +317,18 @@ const initFileTypeChart = () => {
|
|||||||
series: [{
|
series: [{
|
||||||
name: '文件类型',
|
name: '文件类型',
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
radius: ['30%', '70%'],
|
radius: ['45%', '75%'],
|
||||||
center: ['50%', '60%'],
|
center: ['50%', '45%'],
|
||||||
avoidLabelOverlap: false,
|
avoidLabelOverlap: true,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderRadius: 6,
|
borderRadius: 8,
|
||||||
borderColor: '#fff',
|
borderColor: '#fff',
|
||||||
borderWidth: 2
|
borderWidth: 2
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: false
|
||||||
formatter: '{b}: {c}'
|
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
fontSize: '14',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#333'
|
|
||||||
},
|
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
shadowBlur: 10,
|
shadowBlur: 10,
|
||||||
shadowOffsetX: 0,
|
shadowOffsetX: 0,
|
||||||
@ -311,7 +336,7 @@ const initFileTypeChart = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
labelLine: {
|
labelLine: {
|
||||||
show: true
|
show: false
|
||||||
},
|
},
|
||||||
data: [
|
data: [
|
||||||
{ name: '暂无数据', value: 1 }
|
{ 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 = () => {
|
const updateCharts = () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@ -351,6 +397,7 @@ onMounted(() => {
|
|||||||
// 组件卸载时清理
|
// 组件卸载时清理
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
window.removeEventListener('resize', handleResize)
|
window.removeEventListener('resize', handleResize)
|
||||||
|
stopCarousel() // 停止轮播
|
||||||
if (dbTypeChart) {
|
if (dbTypeChart) {
|
||||||
dbTypeChart.dispose()
|
dbTypeChart.dispose()
|
||||||
dbTypeChart = null
|
dbTypeChart = null
|
||||||
@ -409,5 +456,55 @@ defineExpose({
|
|||||||
.chart--thin {
|
.chart--thin {
|
||||||
height: 80px;
|
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>
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user