diff --git a/docker-compose.yml b/docker-compose.yml
index 5cfd58be..f2edd849 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -6,7 +6,7 @@ services:
args:
http_proxy: ${HTTP_PROXY:-}
https_proxy: ${HTTPS_PROXY:-}
- image: yuxi-api:0.1.0
+ image: yuxi-api:0.2.0.dev
container_name: api-dev
working_dir: /app
volumes:
@@ -68,7 +68,7 @@ services:
context: .
dockerfile: docker/web.Dockerfile
target: development
- image: yuxi-web:0.1.0
+ image: yuxi-web:0.2.0.dev
container_name: web-dev
volumes:
- ./web:/app
diff --git a/docker/web.Dockerfile b/docker/web.Dockerfile
index 766b4bf2..da6c99fb 100644
--- a/docker/web.Dockerfile
+++ b/docker/web.Dockerfile
@@ -1,21 +1,25 @@
# 开发阶段
-FROM node:latest AS development
+FROM node:20-alpine AS development
WORKDIR /app
-
ARG http_proxy
ARG https_proxy
-ENV http_proxy=$http_proxy \
- https_proxy=$https_proxy \
- TZ=Asia/Shanghai
+ENV TZ=Asia/Shanghai
-# 复制 package.json 和 package-lock.json(如果存在)
+
+# 只有当代理变量不为空时才设置代理
+RUN if [ -n "$http_proxy" ]; then echo "export http_proxy=$http_proxy" >> /etc/environment; fi
+RUN if [ -n "$https_proxy" ]; then echo "export https_proxy=$https_proxy" >> /etc/environment; fi
+
+# 安装 pnpm
+RUN npm install -g pnpm@latest
+
+# 复制 package.json 和 pnpm-lock.yaml
COPY ./web/package*.json ./
+COPY ./web/pnpm-lock.yaml* ./
# 安装依赖
-RUN npm install -g pnpm@latest-10
RUN pnpm install
-# RUN npm install --registry http://mirrors.cloud.tencent.com/npm/ --verbose --force
# 复制源代码
COPY ./web .
@@ -26,15 +30,22 @@ EXPOSE 5173
# 启动开发服务器的命令在 docker-compose 文件中定义
# 生产阶段
-FROM node:latest AS build-stage
+FROM node:20-alpine AS build-stage
WORKDIR /app
-COPY ./web/package*.json ./
-RUN npm install --force
-# RUN npm install --registry https://registry.npmmirror.com --force
+# 安装 pnpm
+RUN npm install -g pnpm@latest
+# 复制依赖文件
+COPY ./web/package*.json ./
+COPY ./web/pnpm-lock.yaml* ./
+
+# 安装依赖
+RUN pnpm install --frozen-lockfile
+
+# 复制源代码并构建
COPY ./web .
-RUN npm run build
+RUN pnpm run build
# 生产环境运行阶段
FROM nginx:alpine AS production
diff --git a/server/routers/__init__.py b/server/routers/__init__.py
index 017bb2b5..b8f2c610 100644
--- a/server/routers/__init__.py
+++ b/server/routers/__init__.py
@@ -3,9 +3,11 @@ from server.routers.chat_router import chat
from server.routers.data_router import data
from server.routers.base_router import base
from server.routers.auth_router import auth
+from server.routers.graph_router import graph
router = APIRouter()
router.include_router(base)
router.include_router(chat)
router.include_router(data)
router.include_router(auth)
+router.include_router(graph)
diff --git a/server/routers/graph_router.py b/server/routers/graph_router.py
new file mode 100644
index 00000000..88972f42
--- /dev/null
+++ b/server/routers/graph_router.py
@@ -0,0 +1,258 @@
+import json
+import os
+import asyncio
+import traceback
+from typing import List, Dict, Any, Optional
+from fastapi import APIRouter, Query, HTTPException, Depends
+from server.utils.auth_middleware import get_admin_user
+from server.models.user_model import User
+
+from src import knowledge_base
+from src.utils.logging_config import logger
+
+graph = APIRouter()
+
+
+@graph.get("/graph/subgraph")
+async def get_subgraph(
+ db_id: str = Query(..., description="数据库ID"),
+ node_label: str = Query(..., description="节点标签或实体名称"),
+ max_depth: int = Query(2, description="最大深度", ge=1, le=5),
+ max_nodes: int = Query(100, description="最大节点数", ge=1, le=1000),
+ current_user: User = Depends(get_admin_user)
+):
+ """
+ 使用 LightRAG 原生方法获取知识图谱子图
+
+ Args:
+ db_id: LightRAG 数据库实例ID
+ node_label: 节点标签,用于查找起始节点,使用 "*" 获取全图
+ max_depth: 子图的最大深度
+ max_nodes: 返回的最大节点数量
+
+ Returns:
+ 包含节点和边的知识图谱数据
+ """
+ try:
+ logger.info(f"获取子图数据 - db_id: {db_id}, node_label: {node_label}, max_depth: {max_depth}, max_nodes: {max_nodes}")
+
+ # 获取 LightRAG 实例
+ rag_instance = await knowledge_base._get_lightrag_instance(db_id)
+ if not rag_instance:
+ raise HTTPException(status_code=404, detail=f"数据库 {db_id} 不存在")
+
+ # 使用 LightRAG 的原生 get_knowledge_graph 方法
+ knowledge_graph = await rag_instance.get_knowledge_graph(
+ node_label=node_label,
+ max_depth=max_depth,
+ max_nodes=max_nodes
+ )
+
+ # 将 LightRAG 的 KnowledgeGraph 格式转换为前端需要的格式
+ nodes = []
+ for node in knowledge_graph.nodes:
+ nodes.append({
+ "id": node.id,
+ "labels": node.labels,
+ "entity_type": node.properties.get("entity_type", "unknown"),
+ "properties": node.properties
+ })
+
+ edges = []
+ for edge in knowledge_graph.edges:
+ edges.append({
+ "id": edge.id,
+ "source": edge.source,
+ "target": edge.target,
+ "type": edge.type,
+ "properties": edge.properties
+ })
+
+ result = {
+ "success": True,
+ "data": {
+ "nodes": nodes,
+ "edges": edges,
+ "is_truncated": knowledge_graph.is_truncated,
+ "total_nodes": len(nodes),
+ "total_edges": len(edges)
+ }
+ }
+
+ logger.info(f"成功获取子图 - 节点数: {len(nodes)}, 边数: {len(edges)}")
+ return result
+
+ 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("/graph/labels")
+async def get_graph_labels(
+ db_id: str = Query(..., description="数据库ID"),
+ current_user: User = Depends(get_admin_user)
+):
+ """
+ 获取知识图谱中的所有标签
+
+ Args:
+ db_id: LightRAG 数据库实例ID
+
+ Returns:
+ 图谱中所有可用的标签列表
+ """
+ try:
+ logger.info(f"获取图谱标签 - db_id: {db_id}")
+
+ # 获取 LightRAG 实例
+ rag_instance = await knowledge_base._get_lightrag_instance(db_id)
+ if not rag_instance:
+ raise HTTPException(status_code=404, detail=f"数据库 {db_id} 不存在")
+
+ # 使用 LightRAG 的原生方法获取所有标签
+ labels = await rag_instance.get_graph_labels()
+
+ return {
+ "success": True,
+ "data": {
+ "labels": labels
+ }
+ }
+
+ 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("/graph/databases")
+async def get_available_databases(
+ current_user: User = Depends(get_admin_user)
+):
+ """
+ 获取所有可用的 LightRAG 数据库
+
+ Returns:
+ 可用的数据库列表
+ """
+ try:
+ databases = knowledge_base.get_databases()
+ return {
+ "success": True,
+ "data": databases
+ }
+
+ except Exception as e:
+ logger.error(f"获取数据库列表失败: {e}")
+ raise HTTPException(status_code=500, detail=f"获取数据库列表失败: {str(e)}")
+
+
+# 保留原有的直接数据库查询方法作为备用(如果需要的话)
+@graph.get("/graph/nodes")
+async def get_graph_nodes_legacy(
+ db_id: str = Query(..., description="数据库ID"),
+ limit: int = Query(500, description="最大节点数量", ge=1, le=2000),
+ offset: int = Query(0, description="偏移量", ge=0),
+ entity_type: Optional[str] = Query(None, description="实体类型筛选"),
+ search: Optional[str] = Query(None, description="搜索关键词"),
+ current_user: User = Depends(get_admin_user)
+):
+ """
+ 直接查询数据库获取节点数据(备用方法)
+ 建议使用 /graph/subgraph 接口
+ """
+ try:
+ # 这里可以添加直接数据库查询的逻辑
+ # 但建议用户使用 get_subgraph 接口
+ return {
+ "success": False,
+ "message": "建议使用 /graph/subgraph 接口获取图谱数据",
+ "data": {
+ "nodes": [],
+ "total": 0
+ }
+ }
+
+ except Exception as e:
+ logger.error(f"获取图节点数据失败: {e}")
+ raise HTTPException(status_code=500, detail=f"获取图节点数据失败: {str(e)}")
+
+
+@graph.get("/graph/edges")
+async def get_graph_edges_legacy(
+ db_id: str = Query(..., description="数据库ID"),
+ limit: int = Query(500, description="最大边数量", ge=1, le=2000),
+ offset: int = Query(0, description="偏移量", ge=0),
+ min_weight: Optional[float] = Query(None, description="最小权重筛选"),
+ current_user: User = Depends(get_admin_user)
+):
+ """
+ 直接查询数据库获取边数据(备用方法)
+ 建议使用 /graph/subgraph 接口
+ """
+ try:
+ # 这里可以添加直接数据库查询的逻辑
+ # 但建议用户使用 get_subgraph 接口
+ return {
+ "success": False,
+ "message": "建议使用 /graph/subgraph 接口获取图谱数据",
+ "data": {
+ "edges": [],
+ "total": 0
+ }
+ }
+
+ except Exception as e:
+ logger.error(f"获取图边数据失败: {e}")
+ raise HTTPException(status_code=500, detail=f"获取图边数据失败: {str(e)}")
+
+
+@graph.get("/graph/stats")
+async def get_graph_stats(
+ db_id: str = Query(..., description="数据库ID"),
+ current_user: User = Depends(get_admin_user)
+):
+ """
+ 获取知识图谱统计信息
+ """
+ try:
+ logger.info(f"获取图谱统计信息 - db_id: {db_id}")
+
+ # 获取 LightRAG 实例
+ rag_instance = await knowledge_base._get_lightrag_instance(db_id)
+ if not rag_instance:
+ raise HTTPException(status_code=404, detail=f"数据库 {db_id} 不存在")
+
+ # 通过获取全图来统计节点和边的数量
+ knowledge_graph = await rag_instance.get_knowledge_graph(
+ node_label="*",
+ max_depth=1,
+ max_nodes=10000 # 设置较大值以获取完整统计
+ )
+
+ # 统计实体类型分布
+ entity_types = {}
+ for node in knowledge_graph.nodes:
+ entity_type = node.properties.get("entity_type", "unknown")
+ entity_types[entity_type] = entity_types.get(entity_type, 0) + 1
+
+ entity_types_list = [
+ {"type": k, "count": v}
+ for k, v in sorted(entity_types.items(), key=lambda x: x[1], reverse=True)
+ ]
+
+ return {
+ "success": True,
+ "data": {
+ "total_nodes": len(knowledge_graph.nodes),
+ "total_edges": len(knowledge_graph.edges),
+ "entity_types": entity_types_list,
+ "is_truncated": knowledge_graph.is_truncated
+ }
+ }
+
+ except Exception as e:
+ logger.error(f"获取图谱统计信息失败: {e}")
+ logger.error(f"Traceback: {traceback.format_exc()}")
+ raise HTTPException(status_code=500, detail=f"获取图谱统计信息失败: {str(e)}")
\ No newline at end of file
diff --git a/web/package.json b/web/package.json
index 02915579..bf91194a 100644
--- a/web/package.json
+++ b/web/package.json
@@ -1,6 +1,6 @@
{
"name": "yuxi-know-frontend",
- "version": "0.0.0",
+ "version": "0.2.0.dev",
"private": true,
"scripts": {
"dev": "vite",
@@ -14,6 +14,8 @@
"dependencies": {
"@ant-design/icons-vue": "^6.1.0",
"@antv/g6": "^5.0.47",
+ "@sigma/edge-curve": "^3.1.0",
+ "@sigma/node-border": "^3.0.0",
"@vueuse/core": "^13.2.0",
"ant-design-vue": "^4.2.6",
"axios": "^1.9.0",
@@ -21,6 +23,8 @@
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"echarts-gl": "^2.0.9",
+ "graphology": "^0.26.0",
+ "graphology-generators": "^0.11.2",
"highlight.js": "^11.11.1",
"less": "^4.3.0",
"lucide-vue-next": "^0.511.0",
@@ -28,6 +32,7 @@
"marked-highlight": "^2.2.1",
"md-editor-v3": "^5.5.1",
"pinia": "^3.0.2",
+ "sigma": "^3.0.1",
"vue": "^3.5.14",
"vue-router": "^4.5.1"
},
diff --git a/web/src/apis/graph_api.js b/web/src/apis/graph_api.js
new file mode 100644
index 00000000..0e490ca3
--- /dev/null
+++ b/web/src/apis/graph_api.js
@@ -0,0 +1,238 @@
+import { apiGet } from './base'
+
+/**
+ * 图数据API调用 - 基于LightRAG的新接口
+ */
+
+/**
+ * 获取所有可用的数据库
+ * @returns {Promise} - 数据库列表
+ */
+export const getAvailableDatabases = async () => {
+ return await apiGet('/api/graph/databases', {}, true)
+}
+
+/**
+ * 获取图标签列表
+ * @param {string} dbId - 数据库ID
+ * @returns {Promise} - 标签列表
+ */
+export const getGraphLabels = async (dbId) => {
+ if (!dbId) {
+ throw new Error('db_id is required')
+ }
+
+ const queryParams = new URLSearchParams({
+ db_id: dbId
+ })
+
+ return await apiGet(`/api/graph/labels?${queryParams.toString()}`, {}, true)
+}
+
+/**
+ * 获取子图数据 - 主要接口
+ * @param {Object} params - 查询参数
+ * @param {string} params.db_id - 数据库ID
+ * @param {string} params.node_label - 节点标签 (使用 "*" 获取全图)
+ * @param {number} params.max_depth - 最大深度
+ * @param {number} params.max_nodes - 最大节点数
+ * @returns {Promise} - 子图数据
+ */
+export const getSubgraph = async (params) => {
+ const { db_id, node_label = "*", max_depth = 2, max_nodes = 100 } = params
+
+ if (!db_id) {
+ throw new Error('db_id is required')
+ }
+
+ const queryParams = new URLSearchParams({
+ db_id: db_id,
+ node_label: node_label,
+ max_depth: max_depth.toString(),
+ max_nodes: max_nodes.toString()
+ })
+
+ return await apiGet(`/api/graph/subgraph?${queryParams.toString()}`, {}, true)
+}
+
+/**
+ * 获取图统计信息
+ * @param {string} dbId - 数据库ID
+ * @returns {Promise} - 统计数据
+ */
+export const getGraphStats = async (dbId) => {
+ if (!dbId) {
+ throw new Error('db_id is required')
+ }
+
+ const queryParams = new URLSearchParams({
+ db_id: dbId
+ })
+
+ return await apiGet(`/api/graph/stats?${queryParams.toString()}`, {}, true)
+}
+
+/**
+ * 获取完整图数据 - 使用新的子图接口
+ * @param {Object} params - 查询参数
+ * @param {string} params.db_id - 数据库ID
+ * @param {string} params.node_label - 节点标签筛选
+ * @param {number} params.max_nodes - 最大节点数
+ * @param {number} params.max_depth - 最大深度
+ * @returns {Promise} - 完整图数据
+ */
+export const getFullGraph = async (params = {}) => {
+ const { db_id, node_label = "*", max_nodes = 200, max_depth = 2 } = params
+
+ if (!db_id) {
+ throw new Error('db_id is required for graph operations')
+ }
+
+ try {
+ // 使用子图接口获取数据
+ const response = await getSubgraph({
+ db_id,
+ node_label,
+ max_nodes,
+ max_depth
+ })
+
+ if (!response.success) {
+ throw new Error('获取图数据失败')
+ }
+
+ return {
+ success: true,
+ data: {
+ nodes: response.data.nodes,
+ edges: response.data.edges,
+ is_truncated: response.data.is_truncated,
+ stats: {
+ total_nodes: response.data.total_nodes,
+ total_edges: response.data.total_edges,
+ displayed_nodes: response.data.nodes.length,
+ displayed_edges: response.data.edges.length
+ }
+ }
+ }
+ } catch (error) {
+ console.error('获取完整图数据失败:', error)
+ throw error
+ }
+}
+
+/**
+ * 根据特定标签获取子图
+ * @param {Object} params - 查询参数
+ * @param {string} params.db_id - 数据库ID
+ * @param {string} params.entity_type - 实体类型
+ * @param {number} params.max_nodes - 最大节点数
+ * @param {number} params.max_depth - 最大深度
+ * @returns {Promise} - 子图数据
+ */
+export const getGraphByEntityType = async (params = {}) => {
+ const { db_id, entity_type, max_nodes = 100, max_depth = 2 } = params
+
+ if (!db_id) {
+ throw new Error('db_id is required')
+ }
+
+ return await getSubgraph({
+ db_id,
+ node_label: entity_type || "*",
+ max_nodes,
+ max_depth
+ })
+}
+
+/**
+ * 展开指定节点的邻居
+ * @param {Object} params - 查询参数
+ * @param {string} params.db_id - 数据库ID
+ * @param {string} params.node_label - 节点标签
+ * @param {number} params.max_depth - 最大深度
+ * @param {number} params.max_nodes - 最大节点数
+ * @returns {Promise} - 邻居节点数据
+ */
+export const expandNodeNeighbors = async (params) => {
+ const { db_id, node_label, max_depth = 1, max_nodes = 50 } = params
+
+ if (!db_id || !node_label) {
+ throw new Error('db_id and node_label are required')
+ }
+
+ return await getSubgraph({
+ db_id,
+ node_label,
+ max_depth,
+ max_nodes
+ })
+}
+
+// ==================== 兼容性方法 ====================
+
+/**
+ * 获取图节点数据 (已弃用,建议使用 getSubgraph)
+ * @deprecated 请使用 getSubgraph 替代
+ */
+export const getGraphNodes = async (params = {}) => {
+ console.warn('getGraphNodes is deprecated, please use getSubgraph instead')
+ const { db_id } = params
+ if (!db_id) {
+ throw new Error('db_id is required. Please provide db_id parameter.')
+ }
+ return await getSubgraph({ ...params, node_label: "*" })
+}
+
+/**
+ * 获取图边数据 (已弃用,建议使用 getSubgraph)
+ * @deprecated 请使用 getSubgraph 替代
+ */
+export const getGraphEdges = async (params = {}) => {
+ console.warn('getGraphEdges is deprecated, please use getSubgraph instead')
+ const { db_id } = params
+ if (!db_id) {
+ throw new Error('db_id is required. Please provide db_id parameter.')
+ }
+ return await getSubgraph({ ...params, node_label: "*" })
+}
+
+// ==================== 工具函数 ====================
+
+/**
+ * 根据实体类型获取颜色
+ * @param {string} entityType - 实体类型
+ * @returns {string} - 颜色值
+ */
+export const getEntityTypeColor = (entityType) => {
+ const colorMap = {
+ 'person': '#FF6B6B', // 红色 - 人物
+ 'organization': '#4ECDC4', // 青色 - 组织
+ 'location': '#45B7D1', // 蓝色 - 地点
+ 'geo': '#45B7D1', // 蓝色 - 地理位置
+ 'event': '#96CEB4', // 绿色 - 事件
+ 'category': '#FFEAA7', // 黄色 - 分类
+ 'equipment': '#DDA0DD', // 紫色 - 设备
+ 'athlete': '#FF7675', // 红色 - 运动员
+ 'record': '#FD79A8', // 粉色 - 记录
+ 'year': '#FDCB6E', // 橙色 - 年份
+ 'UNKNOWN': '#B2BEC3', // 灰色 - 未知
+ 'unknown': '#B2BEC3' // 灰色 - 未知
+ }
+
+ return colorMap[entityType] || colorMap['unknown']
+}
+
+/**
+ * 根据权重计算边的粗细
+ * @param {number} weight - 权重值
+ * @param {number} minWeight - 最小权重
+ * @param {number} maxWeight - 最大权重
+ * @returns {number} - 边的粗细
+ */
+export const calculateEdgeWidth = (weight, minWeight = 1, maxWeight = 10) => {
+ const minWidth = 1
+ const maxWidth = 5
+ const normalizedWeight = (weight - minWeight) / (maxWeight - minWeight)
+ return minWidth + normalizedWeight * (maxWidth - minWidth)
+}
\ No newline at end of file
diff --git a/web/src/assets/base.css b/web/src/assets/css/base.css
similarity index 100%
rename from web/src/assets/base.css
rename to web/src/assets/css/base.css
diff --git a/web/src/assets/main.css b/web/src/assets/css/main.css
similarity index 90%
rename from web/src/assets/main.css
rename to web/src/assets/css/main.css
index 7553fcab..d24ccb5c 100644
--- a/web/src/assets/main.css
+++ b/web/src/assets/css/main.css
@@ -1,4 +1,6 @@
@import './base.css';
+@import './sigma.css';
+@import './shorts.css';
:root {
--header-height: 60px;
diff --git a/web/src/assets/css/shorts.css b/web/src/assets/css/shorts.css
new file mode 100644
index 00000000..49b1db5d
--- /dev/null
+++ b/web/src/assets/css/shorts.css
@@ -0,0 +1,46 @@
+.m-2 {
+ margin: 8px;
+}
+
+.mt-2 {
+ margin-top: 8px;
+}
+
+.mb-2 {
+ margin-bottom: 8px;
+}
+
+.ml-2 {
+ margin-left: 8px;
+}
+
+.mr-2 {
+ margin-right: 8px;
+}
+
+
+.m-3 {
+ margin: 12px;
+}
+
+.mt-3 {
+ margin-top: 12px;
+}
+
+.mb-3 {
+ margin-bottom: 12px;
+}
+
+.ml-3 {
+ margin-left: 12px;
+}
+
+.mr-3 {
+ margin-right: 12px;
+}
+
+
+.bn-1px {
+ position: relative;
+ bottom: -1px;
+}
\ No newline at end of file
diff --git a/web/src/assets/css/sigma.css b/web/src/assets/css/sigma.css
new file mode 100644
index 00000000..ec73888e
--- /dev/null
+++ b/web/src/assets/css/sigma.css
@@ -0,0 +1,88 @@
+/* Sigma.js 基础样式 */
+.sigma-container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ background: #fafafa;
+}
+
+.sigma-container canvas {
+ outline: none;
+}
+
+/* 节点标签样式 */
+.sigma-labels {
+ pointer-events: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+
+/* 高亮样式 */
+.node-highlighted {
+ stroke: #ff0000 !important;
+ stroke-width: 3px !important;
+}
+
+.edge-highlighted {
+ stroke: #ff0000 !important;
+ stroke-width: 3px !important;
+}
+
+/* 选中样式 */
+.node-selected {
+ stroke: #1890ff !important;
+ stroke-width: 4px !important;
+}
+
+.edge-selected {
+ stroke: #1890ff !important;
+ stroke-width: 4px !important;
+}
+
+/* 悬停样式 */
+.node-focused {
+ stroke: #52c41a !important;
+ stroke-width: 2px !important;
+}
+
+.edge-focused {
+ stroke: #52c41a !important;
+ stroke-width: 2px !important;
+}
+
+/* 加载动画 */
+.sigma-loading {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 1000;
+}
+
+/* 工具提示 */
+.sigma-tooltip {
+ position: absolute;
+ background: rgba(0, 0, 0, 0.8);
+ color: white;
+ padding: 8px 12px;
+ border-radius: 4px;
+ font-size: 12px;
+ pointer-events: none;
+ z-index: 1000;
+ white-space: nowrap;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+ .sigma-container {
+ font-size: 10px;
+ }
+
+ .sigma-tooltip {
+ font-size: 10px;
+ padding: 6px 8px;
+ }
+}
\ No newline at end of file
diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue
index ee30cc8c..35e42875 100644
--- a/web/src/components/AgentChatComponent.vue
+++ b/web/src/components/AgentChatComponent.vue
@@ -1317,7 +1317,7 @@ const mergeMessageChunk = (chunks) => {
\ No newline at end of file
diff --git a/web/src/layouts/AppLayout.vue b/web/src/layouts/AppLayout.vue
index 9555da41..619556fb 100644
--- a/web/src/layouts/AppLayout.vue
+++ b/web/src/layouts/AppLayout.vue
@@ -207,7 +207,7 @@ const mainList = [{