diff --git a/docs/knowledge-graph/README.md b/docs/knowledge-graph/README.md new file mode 100644 index 0000000..f3e88e0 --- /dev/null +++ b/docs/knowledge-graph/README.md @@ -0,0 +1,223 @@ +# DataMate 知识图谱实现方案 + +## 📋 项目概述 + +DataMate 知识图谱旨在构建企业级数据处理平台的知识网络,通过图结构揭示数据资产之间的关系,支持智能推荐、影响分析、血缘追踪等高级功能。 + +## 🎯 核心目标 + +1. **数据血缘追踪**:追踪数据从源到目标的完整流转路径 +2. **影响分析**:评估数据变更对下游任务的影响范围 +3. **智能推荐**:基于历史使用模式推荐相关数据集和工作流 +4. **知识发现**:挖掘隐藏的数据关系和模式 + +## 🏗️ 技术架构 + +### 技术栈 + +``` +存储层:MySQL (元数据) + Neo4j (图结构) + Milvus (向量) +后端:Spring Boot (kg-service) + FastAPI (kg-ingestion) +前端:React + AntV G6 +抽取:LangChain LLMGraphTransformer +``` + +### 架构设计 + +``` +┌─────────────────────────────────────────────────┐ +│ 前端层 │ +│ React + AntV G6 (图谱可视化 + 编辑) │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ 服务层 │ +│ kg-service (Spring Boot) │ +│ - 图查询 API │ +│ - 权限过滤 │ +│ - 缓存层 (Redis) │ +│ │ +│ rag-query-service (增强) │ +│ - 混合检索 (Milvus + Neo4j) │ +│ - GraphRAG │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ 摄入层 │ +│ kg-ingestion (FastAPI) │ +│ - LangChain LLMGraphTransformer │ +│ - 实体对齐 │ +│ - 关系生成 │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ 存储层 │ +│ MySQL + Neo4j + Milvus │ +└─────────────────────────────────────────────────┘ +``` + +## 📊 数据模型 + +> 详细定义参见 [实体文档](./schema/entities.md) 和 [关系文档](./schema/relationships.md) + +### 核心实体(8 类) + +- **Dataset**:数据集 +- **Field**:字段 +- **LabelTask**:标注任务 +- **Workflow**:工作流 +- **Job**:作业 +- **User**:用户 +- **Org**:组织 +- **KnowledgeSet**:知识集 + +### 核心关系(10 类) + +- **HAS_FIELD**:Dataset → Field,数据集包含字段 +- **DERIVED_FROM**:Dataset → Dataset,数据集血缘派生 +- **USES_DATASET**:Job/LabelTask/Workflow → Dataset,使用数据集 +- **PRODUCES**:Job → Dataset,作业产出数据集 +- **ASSIGNED_TO**:LabelTask/Job → User,任务分配给用户 +- **BELONGS_TO**:User/Dataset → Org,组织归属 +- **TRIGGERS**:Workflow → Job,工作流触发作业 +- **DEPENDS_ON**:Job → Job,作业执行依赖 +- **IMPACTS**:Field → Field,字段级影响 +- **SOURCED_FROM**:KnowledgeSet → Dataset,知识溯源 + +### 节点公共属性 + +```json +{ + "id": "UUID,全局唯一标识符", + "name": "实体名称", + "type": "实体类型(Dataset / Field / LabelTask 等)", + "description": "实体描述", + "graph_id": "所属图谱 ID(多租户隔离)", + "source_id": "来源记录 ID", + "source_type": "来源类型:SYNC / EXTRACTION / MANUAL", + "confidence": "置信度 0.0-1.0", + "created_at": "创建时间" +} +``` + +### 边公共属性 + +```json +{ + "id": "UUID,关系唯一标识符", + "relation_type": "语义关系类型", + "graph_id": "所属图谱 ID", + "weight": "关系权重 0.0-1.0", + "confidence": "置信度 0.0-1.0", + "source_id": "来源记录 ID", + "properties_json": "扩展属性 JSON", + "created_at": "创建时间" +} +``` + +## 🚀 实施路线图 + +### 第 0 阶段:基础设施(1周)✅ 已完成 + +- ✅ 搭建 Neo4j(docker-compose) +- ✅ 更新 Makefile +- ✅ 创建 knowledge-graph-service(Spring Boot) +- ✅ 创建 kg_extraction 模块(Python) +- ✅ 代码审查和修复(3 轮审查,2 轮修复) + +**成果**: +- Neo4j 配置:`deployment/docker/neo4j/docker-compose.yml` +- Java 服务:`backend/services/knowledge-graph-service/`(11 个文件) +- Python 模块:`runtime/datamate-python/app/module/kg_extraction/`(3 个文件) +- Makefile 命令:`neo4j-up`, `neo4j-down`, `neo4j-logs`, `neo4j-shell` + +### 第 1 阶段:MVP(2-3周)⏳ 进行中 + +**目标**:实现基础的图谱构建和查询功能 + +**任务**: +1. 实现 Python 抽取器的 FastAPI 接口 + - 创建 `/api/kg/extract` 端点 + - 支持文本输入,输出节点和边 + - 集成到 FastAPI 路由 + +2. 实现 Java 服务的关系(Relation)功能 + - 补充 Relation 的 Repository/Service/Controller + - 实现关系的 CRUD 操作 + - 支持关系查询和遍历 + +3. 定义核心实体和关系模型 + - 确定 5-8 类核心实体 + - 定义实体之间的关系 + - 设计 Schema 版本管理 + +4. 实现基础的图谱构建流程 + - 从 MySQL 同步元数据到 Neo4j + - 实现增量更新机制 + - 支持手动触发构建 + +**验收标准**: +- ✅ 能够从文本抽取实体和关系 +- ✅ 能够存储到 Neo4j +- ✅ 能够查询和遍历图谱 +- ✅ 支持基础的权限控制 + +### 第 2 阶段:GraphRAG 融合(3-4周) + +**目标**:将知识图谱与现有 RAG 系统深度融合 + +**任务**: +1. 在 rag-query-service 中增加"混合检索"模式 +2. 查询时同时检索 Milvus(向量)+ Neo4j(图结构) +3. 将 2-hop 子图的三元组文本化后作为 Context 喂给 LLM +4. 实现 GraphRAG 的评估和优化 + +**验收标准**: +- ✅ 混合检索性能优于单一检索 +- ✅ 支持可配置的检索策略 +- ✅ 有完整的评估指标 + +### 第 3 阶段:可视化与优化(4-6周) + +**目标**:提供友好的图谱可视化和编辑功能 + +**任务**: +1. 前端图谱浏览器(React + AntV G6) +2. Human-in-the-loop 编辑功能 +3. 性能优化(索引、缓存、离线计算) +4. 监控和运维(Prometheus + Grafana) + +**验收标准**: +- ✅ 支持大规模图谱可视化(10000+ 节点) +- ✅ 支持实时编辑和反馈 +- ✅ 查询响应时间 < 1s + +## 🔑 核心原则 + +1. **先做"窄而深"的场景**:不追求"大而全本体",先聚焦 2-3 个高价值场景 +2. **最终一致性**:MySQL 为主库,Neo4j 为专用存储,通过对账机制保证一致性 +3. **双重防御**:Controller 格式校验 + Service 业务校验 +4. **权限隔离**:所有操作都在正确的 graph_id 范围内 +5. **性能优先**:限制遍历深度、使用缓存、离线计算 + +## 📚 相关文档 + +- [架构设计](./architecture.md) +- [数据模型 - 实体定义](./schema/entities.md) +- [数据模型 - 关系定义](./schema/relationships.md) +- [数据模型 - ER 图](./schema/er-diagram.md) +- [实施计划](./implementation.md) +- [Gemini 分析结果](./analysis/gemini.md) +- [Codex 分析结果](./analysis/codex.md) +- [Claude 分析结果](./analysis/claude.md) + +## 🔗 快速链接 + +- Neo4j Browser: http://localhost:7474 +- Bolt URI: bolt://localhost:7687 +- 默认密码: datamate123(生产环境请修改) + +## 📝 更新日志 + +- 2026-02-17:完成基础设施搭建(第 0 阶段) +- 2026-02-17:创建项目文档 diff --git a/docs/knowledge-graph/analysis/claude.md b/docs/knowledge-graph/analysis/claude.md new file mode 100644 index 0000000..88b9b73 --- /dev/null +++ b/docs/knowledge-graph/analysis/claude.md @@ -0,0 +1,289 @@ +# Claude 知识图谱分析结果 + +## 分析时间 +2026-02-17 + +## 核心建议 + +### 1. 技术选型 + +**图数据库**:Neo4j(社区版或企业版) + +**存储架构**:MySQL + Neo4j 双存储 +- **MySQL**:元数据主库,保持现有业务逻辑 +- **Neo4j**:图结构专用存储,支持复杂查询 + +**同步策略**:最终一致性 + 对账机制 + +### 2. 架构设计(复用现有基础设施) + +**核心原则**: +- 复用现有的服务架构 +- 最小化对现有系统的影响 +- 渐进式集成 + +**集成方式**: +``` +现有服务 → MySQL(主库) + ↓ 同步 + Neo4j(图库) + ↓ 查询 + kg-service(新服务) +``` + +### 3. 数据建模(Schema 先行 + 版本管理) + +#### Schema 设计原则 +1. **先行设计**:明确定义实体和关系 +2. **版本管理**:支持 Schema 演进 +3. **向后兼容**:新版本兼容旧版本 +4. **文档化**:详细记录每个版本的变更 + +#### 实体属性设计 +```json +{ + "id": "UUID", + "name": "名称", + "type": "类型", + "description": "描述", + "tenant_id": "租户ID", + "schema_version": "1.0", + "created_at": "创建时间", + "updated_at": "更新时间" +} +``` + +#### 关系属性设计 +```json +{ + "source": "源节点ID", + "target": "目标节点ID", + "type": "关系类型", + "confidence": "置信度(0-1)", + "source": "来源(manual/auto)", + "valid_from": "生效时间", + "valid_to": "失效时间" +} +``` + +### 4. 实施路线图(4 阶段) + +#### 第 0 阶段:基础设施(1周)✅ +- 搭建 Neo4j +- 创建基础服务 +- 定义 Schema + +#### 第 1 阶段:核心功能(2-3周) +- 实现同步机制 +- 实现基础查询 +- 集成到现有系统 + +#### 第 2 阶段:高级功能(3-4周) +- 实现 GraphRAG +- 实现可视化 +- 性能优化 + +#### 第 3 阶段:持续优化 +- 扩展功能 +- 优化性能 +- 提升体验 + +### 5. 挑战解决方案 + +#### 数据一致性 +**问题**:MySQL 和 Neo4j 数据可能不一致 + +**解决方案**: +- **最终一致性**:允许短暂的不一致 +- **对账机制**:定期对比并修复 +- **事件驱动**:通过事件同步变更 + +**实现**: +```java +@Scheduled(cron = "0 0 2 * * *") // 每天凌晨 2 点 +public void reconcile() { + // 1. 查询 MySQL 中的所有实体 + List datasets = datasetRepository.findAll(); + + // 2. 查询 Neo4j 中的所有实体 + List graphEntities = graphEntityRepository.findAll(); + + // 3. 对比并找出差异 + List diffs = compare(datasets, graphEntities); + + // 4. 修复差异 + for (Diff diff : diffs) { + if (diff.getType() == DiffType.MISSING_IN_NEO4J) { + syncToNeo4j(diff.getEntity()); + } else if (diff.getType() == DiffType.OUTDATED_IN_NEO4J) { + updateNeo4j(diff.getEntity()); + } + } + + // 5. 记录日志 + log.info("Reconciliation completed: {} diffs fixed", diffs.size()); +} +``` + +#### 性能优化 +**问题**:大规模图谱查询性能下降 + +**解决方案**: +- **索引策略**:在高频字段上创建索引 +- **限制遍历深度**:最大 3 跳 +- **Redis 缓存**:缓存热点数据 +- **离线计算**:预计算常用子图 + +**索引创建**: +```cypher +// 实体 ID 索引 +CREATE INDEX entity_id IF NOT EXISTS FOR (n:Entity) ON (n.id); + +// 租户 ID 索引 +CREATE INDEX entity_tenant_id IF NOT EXISTS FOR (n:Entity) ON (n.tenant_id); + +// 复合索引 +CREATE INDEX entity_id_graph_id IF NOT EXISTS +FOR (n:Entity) ON (n.id, n.graph_id); +``` + +#### 前端可视化 +**问题**:大规模图谱难以可视化 + +**解决方案**: +- **分层加载**:先加载核心节点,再加载周边 +- **子图裁剪**:只显示相关子图 +- **WebGL 渲染**:使用 WebGL 提升性能 +- **虚拟滚动**:只渲染可见区域 + +**推荐库**: +- Cytoscape.js(功能丰富) +- AntV G6(国产,文档友好) +- vis.js(简单易用) + +### 6. 最佳实践 + +#### 开发实践 +1. **API 规范一致**:遵循 RESTful 规范 +2. **复用现有模式**:使用现有的 DTO、ErrorCode +3. **事件驱动解耦**:通过事件同步变更 +4. **Cypher 注入防护**:使用参数化查询 + +#### 运维实践 +1. **Neo4j 备份**:每天全量备份 +2. **监控告警**:Prometheus + Grafana +3. **性能调优**:定期分析慢查询 +4. **容量规划**:根据数据增长预测资源需求 + +#### 部署实践 +1. **Docker 部署**:使用 docker-compose +2. **Kubernetes 扩展**:使用 Helm Chart +3. **灰度发布**:先在小范围验证 +4. **回滚机制**:支持快速回滚 + +### 7. 代码实现细节 + +#### 双重防御示例 +```java +// Controller 层:格式校验 +@GetMapping("/{graphId}/entities/{entityId}") +public GraphEntity getEntity( + @PathVariable @Pattern(regexp = UUID_REGEX, message = "graphId 格式无效") + String graphId, + @PathVariable @Pattern(regexp = UUID_REGEX, message = "entityId 格式无效") + String entityId +) { + return entityService.getEntity(graphId, entityId); +} + +// Service 层:业务校验 +public GraphEntity getEntity(String graphId, String entityId) { + // 1. 校验 graphId 格式 + validateGraphId(graphId); + + // 2. 查询实体(同时校验 graphId 和 entityId) + return entityRepository.findByIdAndGraphId(entityId, graphId) + .orElseThrow(() -> BusinessException.of( + KnowledgeGraphErrorCode.ENTITY_NOT_FOUND + )); +} + +// Repository 层:数据访问 +@Query("MATCH (n:Entity {id: $id, graph_id: $graphId}) RETURN n") +Optional findByIdAndGraphId( + @Param("id") String id, + @Param("graphId") String graphId +); +``` + +#### 查询限流示例 +```java +public List getNeighbors( + String graphId, + String entityId, + int depth, + int limit +) { + // Clamp 参数到配置的最大值 + int actualDepth = Math.min(depth, properties.getMaxDepth()); + int actualLimit = Math.min(limit, properties.getMaxNodesPerQuery()); + + // 查询 + return entityRepository.findNeighbors( + graphId, entityId, actualDepth, actualLimit + ); +} +``` + +### 8. 建议的下一步 + +**立即行动**: +1. 实现 Relation 的完整功能 +2. 实现 MySQL → Neo4j 同步 +3. 补充单元测试 + +**短期目标**(1-2周): +1. 完成 MVP 功能 +2. 集成到现有系统 +3. 进行性能测试 + +**中期目标**(1-2月): +1. 实现 GraphRAG +2. 实现可视化 +3. 上线第一个场景 + +## 与其他工具的对比 + +| 维度 | Claude | Codex | Gemini | +|------|--------|-------|--------| +| **技术选型** | Neo4j | Neo4j/JanusGraph | Neo4j | +| **架构重点** | 复用现有基础设施 | 3个新模块 | GraphRAG 融合 | +| **数据建模** | Schema先行+版本管理 | 10类实体+6类关系 | 灵活Schema+embedding | +| **实现路径** | 4阶段 | 4阶段(0-3) | 3阶段(MVP优先) | +| **独特优势** | 深度集成现有系统 | 详细的领域模型 | LangChain+RAG融合 | + +## 关键洞察 + +1. **深度集成**:Claude 强调复用现有基础设施,最小化影响 +2. **最终一致性**:提出了实用的数据同步和对账方案 +3. **详细的代码示例**:提供了可直接使用的代码片段 +4. **运维实践**:关注生产环境的监控、备份、部署 + +## 建议采纳度 + +**强烈推荐**: +- ✅ MySQL + Neo4j 双存储架构 +- ✅ 最终一致性 + 对账机制 +- ✅ 双重防御(Controller + Service) +- ✅ 查询限流 +- ✅ 运维实践(备份、监控) + +**可选**: +- ⚠️ 事件驱动同步(可以先用定时任务) + +## 相关文档 + +- [总体方案](../README.md) +- [架构设计](../architecture.md) +- [Gemini 分析结果](./gemini.md) +- [Codex 分析结果](./codex.md) diff --git a/docs/knowledge-graph/analysis/codex.md b/docs/knowledge-graph/analysis/codex.md new file mode 100644 index 0000000..cd3129c --- /dev/null +++ b/docs/knowledge-graph/analysis/codex.md @@ -0,0 +1,201 @@ +# Codex 知识图谱分析结果 + +## 分析时间 +2026-02-17 + +## 核心建议 + +### 1. 技术选型 + +**图数据库**: +- **首选**:Neo4j(成熟稳定,社区活跃) +- **备选**:JanusGraph(分布式场景) + +**理由**: +- Neo4j 的 Cypher 查询语言简洁强大 +- Spring Data Neo4j 集成良好 +- 丰富的图算法库 +- 适合中小规模图谱(< 1000万节点) + +### 2. 架构设计(3 个新模块) + +#### kg-ingestion (FastAPI) +**职责**:知识抽取和预处理 +- 文本 → 实体 + 关系 +- 实体对齐和消歧 +- 置信度评分 + +#### kg-service (Spring Boot) +**职责**:图谱查询和管理 +- 图查询 API +- 权限控制 +- 缓存管理 + +#### kg-ui (React) +**职责**:图谱可视化 +- AntV G6 可视化 +- 交互式查询 +- 编辑功能 + +### 3. 数据建模(10 类实体 + 6 类关系) + +#### 核心实体(10 类) +1. **Dataset**:数据集 +2. **Field**:字段 +3. **LabelTask**:标注任务 +4. **Workflow**:工作流 +5. **Job**:作业 +6. **Rule**:规则 +7. **User**:用户 +8. **Org**:组织 +9. **Model**:模型 +10. **Issue**:问题 + +#### 核心关系(6 类) +1. **HAS_FIELD**:数据集包含字段 +2. **TRIGGERS**:触发关系 +3. **USES_RULE**:使用规则 +4. **ASSIGNED_TO**:分配给 +5. **PRODUCED_BY**:产生于 +6. **IMPACTS**:影响 + +### 4. 实施路线图(4 阶段) + +#### 第 0 阶段:场景确定(1-2周) +- 确定 2 个高价值场景 +- 定义核心实体和关系 +- 设计 Schema + +#### 第 1 阶段:PoC(2-4周) +- 搭建基础设施 +- 实现基础抽取 +- 验证技术可行性 + +#### 第 2 阶段:生产化(4-8周) +- 完善功能 +- 性能优化 +- 集成到现有系统 + +#### 第 3 阶段:持续优化 +- 扩展实体和关系 +- 优化算法 +- 提升用户体验 + +### 5. 潜在挑战 + +#### 数据质量 +**问题**:元数据不完整或不准确 + +**解决方案**: +- 数据清洗和标准化 +- 人工审核机制 +- 置信度评分 + +#### 性能瓶颈 +**问题**:大规模图谱查询性能下降 + +**解决方案**: +- 索引优化 +- 查询限流 +- 缓存热点数据 +- 离线计算 + +#### 多租户隔离 +**问题**:不同租户的数据需要隔离 + +**解决方案**: +- 所有节点包含 tenant_id +- 查询时自动过滤 +- 权限控制 + +### 6. 最佳实践 + +#### Schema 设计 +- **先行设计**:明确定义实体和关系 +- **版本管理**:支持 Schema 演进 +- **文档化**:详细记录每个实体和关系 + +#### 查询优化 +- **限制深度**:最大 3 跳 +- **限制数量**:最大 1000 个节点 +- **使用索引**:在高频字段上创建索引 +- **缓存结果**:缓存热点查询 + +#### 安全性 +- **参数化查询**:防止 Cypher 注入 +- **权限控制**:基于角色的访问控制 +- **审计日志**:记录所有操作 + +### 7. 代码审查发现的问题 + +#### P0 - 严重问题 +1. **主应用未声明依赖**:已修复 +2. **Neo4j 凭据硬编码**:已修复 +3. **graphId 参数未校验**:已修复 + +#### P1 - 重要问题 +4. **异常处理不规范**:已修复 +5. **查询未限流**:已修复 +6. **异常码体系未对齐**:已修复 + +#### P2 - 中等问题 +7. **关系建模未打通**:待实现 +8. **列表接口缺分页**:待实现 +9. **Python 模块未接入路由**:待实现 +10. **密钥处理不规范**:待实现 + +#### P3 - 次要问题 +11. **Neo4j 镜像浮动 tag**:待修复 +12. **测试覆盖为空**:待补充 + +### 8. 建议的下一步 + +**立即行动**: +1. 补充 P2 问题(关系功能、分页、Python 路由) +2. 定义核心实体和关系模型 +3. 实现 MySQL → Neo4j 同步 + +**短期目标**(1-2周): +1. 完成 MVP 功能 +2. 补充单元测试 +3. 进行性能测试 + +**中期目标**(1-2月): +1. 集成到现有系统 +2. 实现 GraphRAG +3. 上线第一个场景 + +## 与其他工具的对比 + +| 维度 | Codex | Gemini | Claude | +|------|-------|--------|--------| +| **技术选型** | Neo4j/JanusGraph | Neo4j | Neo4j | +| **架构重点** | 3个新模块 | GraphRAG 融合 | 复用现有基础设施 | +| **数据建模** | 10类实体+6类关系 | 灵活Schema+embedding | Schema先行+版本管理 | +| **实现路径** | 4阶段(0-3) | 3阶段(MVP优先) | 4阶段 | +| **独特优势** | 详细的领域模型 | LangChain+RAG融合 | 深度集成现有系统 | + +## 关键洞察 + +1. **详细的领域模型**:Codex 提供了最详细的实体和关系定义 +2. **严格的代码审查**:发现了 12 个问题,确保代码质量 +3. **实用的最佳实践**:提供了具体的优化建议 +4. **分阶段实施**:强调先做 PoC,验证可行性 + +## 建议采纳度 + +**强烈推荐**: +- ✅ 10 类实体 + 6 类关系的数据模型 +- ✅ 代码审查发现的问题修复 +- ✅ 最佳实践(查询优化、安全性) +- ✅ 4 阶段实施路线 + +**可选**: +- ⚠️ JanusGraph(如果需要分布式) + +## 相关文档 + +- [总体方案](../README.md) +- [架构设计](../architecture.md) +- [Gemini 分析结果](./gemini.md) +- [Claude 分析结果](./claude.md) diff --git a/docs/knowledge-graph/analysis/gemini.md b/docs/knowledge-graph/analysis/gemini.md new file mode 100644 index 0000000..a3f6c44 --- /dev/null +++ b/docs/knowledge-graph/analysis/gemini.md @@ -0,0 +1,154 @@ +# Gemini 知识图谱分析结果 + +## 分析时间 +2026-02-17 + +## 核心建议 + +### 1. GraphRAG 融合方案(独特贡献) + +**创新点**:将知识图谱与现有 RAG 系统深度融合 + +**实现方案**: +- 在 `rag-query-service` 中增加"混合检索"模式 +- 查询时同时检索 Milvus(向量)+ Neo4j(图结构) +- 将 2-hop 子图的三元组文本化后作为 Context 喂给 LLM + +**优势**: +- 充分利用现有的 Milvus 向量检索能力 +- 结合向量相似度和图结构关系 +- 提供更丰富的上下文信息 + +### 2. LangChain 集成方案 + +**技术路径**: +- 利用 LangChain 的 `LLMGraphTransformer` 实现自动抽取 +- 在 `runtime/datamate-python` 中实现 +- API: `POST /graph/extract`,输入文本,输出节点和边 + +**实现细节**: +```python +from langchain_experimental.graph_transformers import LLMGraphTransformer + +transformer = LLMGraphTransformer( + llm=llm, + allowed_nodes=["Dataset", "Field", "Workflow"], + allowed_relationships=["HAS_FIELD", "USES"] +) + +graph_documents = transformer.convert_to_graph_documents([document]) +``` + +### 3. 数据建模增强 + +**核心元模型**: +- **Entity**:增加 `embedding` 字段(节点的向量表示) +- **Document**:新增节点类型,用于溯源 +- **关系**:`(Entity)-[MENTIONED_IN]->(Document)` + +**优势**: +- 支持向量检索与图检索的混合 +- 方便溯源,追踪实体来源 +- 提升检索准确性 + +### 4. 实施路线图(3 阶段) + +#### 第一阶段:基础设施与基础抽取 (MVP) +1. 环境搭建:在 `deployment/docker/` 下新建 neo4j 目录 +2. Python 抽取器:利用 LangChain 的 LLMGraphTransformer +3. 简单存储:直接存入 Neo4j + +#### 第二阶段:图谱服务与 RAG 融合 +1. Java 服务:创建 `knowledge-graph-service` +2. GraphRAG:在 `rag-query-service` 中增加"混合检索"模式 + - 查询时同时检索 Milvus 和 Neo4j(2-hop 子图) + - 将三元组文本化后作为 Context 喂给 LLM + +#### 第三阶段:可视化与高级功能 +1. 前端可视化:知识图谱浏览器 +2. 图谱编辑:Human-in-the-loop 修正 + +### 5. 潜在挑战与应对 + +#### 实体歧义 +**问题**:同名实体可能指代不同对象 + +**解决方案**: +- 实体对齐步骤 +- 利用 LLM 或向量相似度合并 +- 人工审核机制 + +#### 信息过载(Super Nodes) +**问题**:某些节点连接过多,查询性能下降 + +**解决方案**: +- 限制跳数(最大 3 跳) +- 限制最大边数(最大 1000 条) +- 分页返回结果 + +#### 幻觉与错误抽取 +**问题**:LLM 可能产生不存在的实体或关系 + +**解决方案**: +- 置信度评分 +- 人工审核 +- 对比多个模型的结果 + +### 6. 首要行动 + +**基础设施搭建**: +1. 在 `deployment/docker/` 下创建 neo4j 目录 +2. 编写 docker-compose.yml +3. 更新 Makefile 支持 Neo4j 的启动 + +**示例配置**: +```yaml +version: '3.8' +services: + neo4j: + image: neo4j:latest + ports: + - "7474:7474" + - "7687:7687" + environment: + - NEO4J_AUTH=neo4j/datamate123 + volumes: + - neo4j_data:/data +volumes: + neo4j_data: +``` + +## 与其他工具的对比 + +| 维度 | Gemini | Codex | Claude | +|------|--------|-------|--------| +| **技术选型** | Neo4j | Neo4j/JanusGraph | Neo4j | +| **架构重点** | GraphRAG 融合 | 3个新模块 | 复用现有基础设施 | +| **数据建模** | 灵活Schema+embedding | 10类实体+6类关系 | Schema先行+版本管理 | +| **实现路径** | 3阶段(MVP优先) | 4阶段(0-3) | 4阶段 | +| **独特优势** | LangChain+RAG融合 | 详细的领域模型 | 深度集成现有系统 | + +## 关键洞察 + +1. **GraphRAG 是核心创新**:Gemini 提出的混合检索方案特别适合 DataMate 现有的 RAG 架构 +2. **LangChain 简化开发**:利用现成的 LLMGraphTransformer 可以快速实现抽取功能 +3. **向量 + 图结构**:embedding 字段的引入使得向量检索和图检索可以无缝结合 +4. **MVP 优先**:强调先做基础设施,再逐步扩展功能 + +## 建议采纳度 + +**强烈推荐**: +- ✅ GraphRAG 融合方案 +- ✅ LangChain 集成 +- ✅ embedding 字段 +- ✅ Document 节点 + +**可选**: +- ⚠️ 3 阶段实施路线(可与其他工具的 4 阶段结合) + +## 相关文档 + +- [总体方案](../README.md) +- [架构设计](../architecture.md) +- [Codex 分析结果](./codex.md) +- [Claude 分析结果](./claude.md) diff --git a/docs/knowledge-graph/architecture.md b/docs/knowledge-graph/architecture.md new file mode 100644 index 0000000..c3d149a --- /dev/null +++ b/docs/knowledge-graph/architecture.md @@ -0,0 +1,397 @@ +# DataMate 知识图谱架构设计 + +## 🏗️ 整体架构 + +### 分层架构 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 前端层 (Frontend) │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ React + AntV G6 │ │ +│ │ - 图谱可视化(分层加载、子图裁剪) │ │ +│ │ - 图谱编辑(Human-in-the-loop) │ │ +│ │ - 查询界面(Cypher 查询构建器) │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ↓ HTTP/REST +┌─────────────────────────────────────────────────────────────┐ +│ 服务层 (Service) │ +│ ┌──────────────────────┐ ┌──────────────────────────┐ │ +│ │ kg-service │ │ rag-query-service │ │ +│ │ (Spring Boot) │ │ (FastAPI) │ │ +│ │ │ │ │ │ +│ │ - 图查询 API │ │ - 混合检索 │ │ +│ │ - 权限过滤 │ │ - GraphRAG │ │ +│ │ - 缓存层 (Redis) │ │ - 向量检索 + 图检索 │ │ +│ └──────────────────────┘ └──────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ 摄入层 (Ingestion) │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ kg-ingestion (FastAPI) │ │ +│ │ - LangChain LLMGraphTransformer │ │ +│ │ - 实体对齐(向量相似度 + LLM) │ │ +│ │ - 关系生成(规则 + LLM) │ │ +│ │ - 置信度评分 │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ 存储层 (Storage) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ MySQL │ │ Neo4j │ │ Milvus │ │ +│ │ (元数据) │ │ (图结构) │ │ (向量) │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 🔧 技术选型 + +### 图数据库:Neo4j + +**选择理由**: +- ✅ 成熟稳定,社区活跃 +- ✅ Cypher 查询语言简洁强大 +- ✅ Spring Data Neo4j 集成良好 +- ✅ 支持 ACID 事务 +- ✅ 丰富的图算法库 + +**版本**:Neo4j 社区版(生产环境可升级企业版) + +**配置**: +- 端口:7474 (HTTP), 7687 (Bolt) +- 内存:heap 512MB, page cache 512MB(可根据数据量调整) +- 持久化:Docker volume + +### 后端框架 + +#### knowledge-graph-service (Spring Boot) + +**职责**: +- 图谱查询 API +- 权限控制和租户隔离 +- 缓存管理 +- 与其他服务集成 + +**技术栈**: +- Spring Boot 3.x +- Spring Data Neo4j +- Spring Security(权限控制) +- Redis(缓存) + +**DDD 分层**: +``` +com.datamate.knowledgegraph/ +├── application/ # 应用服务层 +│ └── GraphEntityService.java +├── domain/ # 领域层 +│ ├── model/ +│ │ ├── GraphEntity.java +│ │ └── GraphRelation.java +│ └── repository/ +│ └── GraphEntityRepository.java +├── infrastructure/ # 基础设施层 +│ ├── neo4j/ +│ │ └── KnowledgeGraphProperties.java +│ └── exception/ +│ └── KnowledgeGraphErrorCode.java +└── interfaces/ # 接口层 + ├── rest/ + │ └── GraphEntityController.java + └── dto/ + ├── CreateEntityRequest.java + ├── UpdateEntityRequest.java + └── CreateRelationRequest.java +``` + +#### kg-ingestion (FastAPI) + +**职责**: +- 知识抽取(文本 → 实体 + 关系) +- 实体对齐和消歧 +- 关系生成和验证 +- 置信度评分 + +**技术栈**: +- FastAPI +- LangChain +- LangChain LLMGraphTransformer +- Pydantic(数据验证) + +**模块结构**: +``` +kg_extraction/ +├── __init__.py +├── models.py # 数据模型 +├── extractor.py # 抽取器 +└── aligner.py # 实体对齐(待实现) +``` + +### 前端框架 + +**技术栈**: +- React 18 +- AntV G6(图可视化) +- TypeScript +- Ant Design(UI 组件) + +**核心功能**: +- 图谱可视化(支持 10000+ 节点) +- 交互式查询构建器 +- 实时编辑和反馈 +- 导出和分享 + +## 🔐 安全设计 + +### 多租户隔离 + +**策略**: +- 所有实体和关系都包含 `graph_id` 属性 +- 查询时自动添加 `graph_id` 过滤条件 +- Neo4j 索引包含 `graph_id` + +**实现**: +```cypher +// 创建索引 +CREATE INDEX entity_graph_id IF NOT EXISTS FOR (n:Entity) ON (n.graph_id); + +// 查询时自动过滤 +MATCH (n:Entity {graph_id: $graphId}) +WHERE n.id = $entityId +RETURN n; +``` + +### 权限控制 + +**graphId 双重防御**: +1. **Controller 层**:`@Pattern(regexp = UUID_REGEX)` 格式校验 +2. **Service 层**:`validateGraphId()` 业务校验 + +**实现**: +```java +// Controller 层 +@GetMapping("/{graphId}/entities/{entityId}") +public GraphEntity getEntity( + @PathVariable @Pattern(regexp = UUID_REGEX) String graphId, + @PathVariable @Pattern(regexp = UUID_REGEX) String entityId +) { + return entityService.getEntity(graphId, entityId); +} + +// Service 层 +public GraphEntity getEntity(String graphId, String entityId) { + validateGraphId(graphId); + return entityRepository.findByIdAndGraphId(entityId, graphId) + .orElseThrow(() -> BusinessException.of( + KnowledgeGraphErrorCode.ENTITY_NOT_FOUND + )); +} +``` + +### Cypher 注入防护 + +**策略**: +- 使用参数化查询 +- 禁止拼接 Cypher 字符串 +- 输入验证和转义 + +**示例**: +```java +// ✅ 正确:参数化查询 +@Query("MATCH (n:Entity {id: $id, graph_id: $graphId}) RETURN n") +Optional findByIdAndGraphId( + @Param("id") String id, + @Param("graphId") String graphId +); + +// ❌ 错误:字符串拼接 +String cypher = "MATCH (n:Entity {id: '" + id + "'}) RETURN n"; +``` + +## 📊 数据同步策略 + +### MySQL → Neo4j 同步 + +**策略**:最终一致性 + 对账机制 + +**同步方式**: +1. **实时同步**:通过 CDC(Change Data Capture)捕获 MySQL 变更 +2. **批量同步**:定时任务(每小时/每天)全量同步 +3. **手动同步**:提供 API 触发同步 + +**对账机制**: +- 每天凌晨对比 MySQL 和 Neo4j 的数据 +- 发现不一致时记录日志并告警 +- 提供修复工具 + +**实现**: +```java +@Scheduled(cron = "0 0 * * * *") // 每小时 +public void syncFromMySQL() { + // 1. 查询 MySQL 中的变更 + List changedDatasets = datasetRepository + .findByUpdatedAtAfter(lastSyncTime); + + // 2. 转换为图实体 + List entities = changedDatasets.stream() + .map(this::toGraphEntity) + .collect(Collectors.toList()); + + // 3. 批量写入 Neo4j + graphEntityRepository.saveAll(entities); + + // 4. 更新同步时间 + lastSyncTime = Instant.now(); +} +``` + +## ⚡ 性能优化 + +### 查询优化 + +**策略**: +1. **限制遍历深度**:最大 3 跳 +2. **限制返回节点数**:最大 1000 个 +3. **使用索引**:在高频查询字段上创建索引 +4. **缓存热点数据**:使用 Redis 缓存 + +**实现**: +```java +public List getNeighbors( + String graphId, + String entityId, + int depth, + int limit +) { + // Clamp 参数 + int actualDepth = Math.min(depth, properties.getMaxDepth()); + int actualLimit = Math.min(limit, properties.getMaxNodesPerQuery()); + + // 查询 + return entityRepository.findNeighbors( + graphId, entityId, actualDepth, actualLimit + ); +} +``` + +### 索引策略 + +**必需索引**: +```cypher +// 实体 ID 索引 +CREATE INDEX entity_id IF NOT EXISTS FOR (n:Entity) ON (n.id); + +// 图 ID 索引 +CREATE INDEX entity_graph_id IF NOT EXISTS FOR (n:Entity) ON (n.graph_id); + +// 复合索引 +CREATE INDEX entity_id_graph_id IF NOT EXISTS +FOR (n:Entity) ON (n.id, n.graph_id); +``` + +### 缓存策略 + +**缓存层次**: +1. **L1 缓存**:Spring Cache(本地缓存) +2. **L2 缓存**:Redis(分布式缓存) +3. **L3 缓存**:Neo4j 内置缓存 + +**缓存内容**: +- 热点实体(访问频率 > 100/小时) +- 常用子图(2-hop 邻居) +- 查询结果(TTL 5 分钟) + +## 🔄 GraphRAG 融合 + +### 混合检索架构 + +``` +用户查询 + ↓ +┌─────────────────────────────────────┐ +│ 查询理解和改写 │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ 并行检索 │ +│ ┌───────────┐ ┌─────────────┐ │ +│ │ Milvus │ │ Neo4j │ │ +│ │ 向量检索 │ │ 图检索 │ │ +│ │ Top-K │ │ 2-hop 子图 │ │ +│ └───────────┘ └─────────────┘ │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ 结果融合和排序 │ +│ - 向量相似度 × 0.6 │ +│ - 图结构相关性 × 0.4 │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Context 构建 │ +│ - 文档片段(Milvus) │ +│ - 三元组文本化(Neo4j) │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ LLM 生成 │ +└─────────────────────────────────────┘ +``` + +### 三元组文本化 + +**策略**:将图结构转换为自然语言 + +**示例**: +```python +# 图结构 +(Dataset:用户行为数据)-[HAS_FIELD]->(Field:user_id) +(Dataset:用户行为数据)-[USED_BY]->(Workflow:用户画像构建) + +# 文本化 +""" +数据集"用户行为数据"包含字段"user_id"。 +数据集"用户行为数据"被工作流"用户画像构建"使用。 +""" +``` + +## 📈 监控和运维 + +### 监控指标 + +**Neo4j 指标**: +- 节点数量 +- 关系数量 +- 查询响应时间 +- 内存使用率 +- 磁盘使用率 + +**服务指标**: +- API 响应时间 +- 错误率 +- 吞吐量 +- 缓存命中率 + +**工具**: +- Prometheus(指标采集) +- Grafana(可视化) +- Neo4j Metrics(Neo4j 专用指标) + +### 备份策略 + +**Neo4j 备份**: +- 每天凌晨全量备份 +- 保留最近 7 天的备份 +- 备份到对象存储(S3/OSS) + +**恢复测试**: +- 每月进行一次恢复演练 +- 验证备份的完整性和可用性 + +## 🔗 相关文档 + +- [总体方案](./README.md) +- [实施计划](./implementation.md) +- [AI 分析结果](./analysis/) diff --git a/docs/knowledge-graph/implementation.md b/docs/knowledge-graph/implementation.md new file mode 100644 index 0000000..869f352 --- /dev/null +++ b/docs/knowledge-graph/implementation.md @@ -0,0 +1,395 @@ +# DataMate 知识图谱实施计划 + +## 📅 总体时间线 + +``` +第 0 阶段:基础设施(1周) ✅ 已完成 +第 1 阶段:MVP(2-3周) ⏳ 进行中 +第 2 阶段:GraphRAG 融合(3-4周) ⏳ 待开始 +第 3 阶段:可视化与优化(4-6周) ⏳ 待开始 +``` + +**总计**:10-14 周 + +--- + +## ✅ 第 0 阶段:基础设施(已完成) + +### 目标 +搭建知识图谱的基础设施,包括 Neo4j、Java 服务、Python 模块。 + +### 已完成任务 + +#### 1. Neo4j Docker Compose 配置 +- ✅ 创建 `deployment/docker/neo4j/docker-compose.yml` +- ✅ 配置 Neo4j 社区版 +- ✅ 端口:7474 (HTTP), 7687 (Bolt) +- ✅ 数据持久化:Docker volume +- ✅ 环境变量化密码 + +#### 2. Makefile 更新 +- ✅ 添加 `neo4j-up`:启动 Neo4j +- ✅ 添加 `neo4j-down`:停止 Neo4j +- ✅ 添加 `neo4j-logs`:查看日志 +- ✅ 添加 `neo4j-shell`:进入 Cypher Shell + +#### 3. knowledge-graph-service(Spring Boot) +- ✅ 创建完整的 DDD 分层架构 +- ✅ 实现 GraphEntity 的 CRUD +- ✅ 实现 graphId 双重防御 +- ✅ 实现查询限流 +- ✅ 统一异常处理体系 + +**文件清单**(11 个 Java 文件): +- `KnowledgeGraphServiceConfiguration.java` +- `GraphEntityService.java` +- `GraphEntity.java`, `GraphRelation.java` +- `GraphEntityRepository.java` +- `KnowledgeGraphErrorCode.java` +- `KnowledgeGraphProperties.java` +- `GraphEntityController.java` +- `CreateEntityRequest.java`, `UpdateEntityRequest.java`, `CreateRelationRequest.java` +- `application-knowledgegraph.yml` + +#### 4. kg_extraction 模块(Python) +- ✅ 创建 `KnowledgeGraphExtractor` 类 +- ✅ 集成 LangChain LLMGraphTransformer +- ✅ 支持异步/同步/批量抽取 +- ✅ 支持 schema-guided 模式 +- ✅ 兼容 OpenAI 及自部署模型 + +**文件清单**(3 个 Python 文件): +- `__init__.py` +- `models.py`:Pydantic 数据模型 +- `extractor.py`:抽取器实现 + +#### 5. 代码审查和修复 +- ✅ 3 轮 Codex 审查 +- ✅ 2 轮 Claude 修复 +- ✅ 所有 P0 和 P1 问题已解决 +- ✅ 编译通过,无阻塞性问题 + +### 成果 +- Commit: `5a553dd` +- 文件变更:22 个文件,1007 行新增 +- 分支:`lsf` + +--- + +## ⏳ 第 1 阶段:MVP(2-3周) + +### 目标 +实现基础的图谱构建和查询功能,支持 2-3 个高价值场景。 + +### 任务列表 + +#### 任务 1.1:实现 Python 抽取器的 FastAPI 接口(3天) + +**子任务**: +1. 创建 `kg_extraction/interface.py` + - 定义 FastAPI 路由 + - 实现 `/api/kg/extract` 端点 + - 支持文本输入,输出节点和边 + +2. 集成到 FastAPI 主路由 + - 在 `app/module/__init__.py` 中注册路由 + - 添加 API 文档 + +3. 实现配置管理 + - 从环境变量读取 API Key + - 使用 `SecretStr` 保护敏感信息 + +4. 编写单元测试 + - 测试抽取功能 + - 测试错误处理 + +**验收标准**: +- ✅ 能够通过 API 调用抽取功能 +- ✅ 返回结构化的节点和边 +- ✅ 有完整的 API 文档 +- ✅ 单元测试覆盖率 > 80% + +#### 任务 1.2:实现 Java 服务的关系(Relation)功能(3天) + +**子任务**: +1. 补充 `GraphRelationRepository` + - 实现 `findByGraphId` + - 实现 `findBySourceAndTarget` + - 实现 `findByType` + +2. 实现 `GraphRelationService` + - 创建关系 + - 查询关系 + - 更新关系 + - 删除关系 + +3. 实现 `GraphRelationController` + - `POST /{graphId}/relations`:创建关系 + - `GET /{graphId}/relations`:列表查询 + - `GET /{graphId}/relations/{relationId}`:单个查询 + - `PUT /{graphId}/relations/{relationId}`:更新关系 + - `DELETE /{graphId}/relations/{relationId}`:删除关系 + +4. 编写单元测试和集成测试 + +**验收标准**: +- ✅ 关系的 CRUD 功能完整 +- ✅ 支持按类型、源节点、目标节点查询 +- ✅ 有完整的权限控制 +- ✅ 测试覆盖率 > 80% + +#### 任务 1.3:定义核心实体和关系模型(2天) + +**子任务**: +1. 确定 5-8 类核心实体 + - 分析 DataMate 现有数据模型 + - 选择高价值实体 + - 定义实体属性 + +2. 定义实体之间的关系 + - 分析业务流程 + - 定义关系类型 + - 定义关系属性 + +3. 设计 Schema 版本管理 + - 定义 Schema 版本号 + - 实现 Schema 迁移机制 + - 记录 Schema 变更历史 + +4. 创建文档 + - 实体和关系清单 + - 属性说明 + - 示例数据 + +**验收标准**: +- ✅ 有清晰的实体和关系定义 +- ✅ 有完整的文档 +- ✅ 有示例数据 + +**建议的核心实体**(5-8 类): +1. **Dataset**:数据集 +2. **Field**:字段 +3. **LabelTask**:标注任务 +4. **Workflow**:工作流 +5. **Job**:作业 +6. **User**:用户 +7. **Model**:模型(可选) +8. **Rule**:规则(可选) + +**建议的核心关系**: +1. **HAS_FIELD**:数据集包含字段 +2. **TRIGGERS**:触发关系(Workflow → Job) +3. **USES_DATASET**:使用数据集(Workflow → Dataset) +4. **ASSIGNED_TO**:分配给(LabelTask → User) +5. **PRODUCED_BY**:产生于(Dataset → Job) +6. **DEPENDS_ON**:依赖于(Job → Job) + +#### 任务 1.4:实现基础的图谱构建流程(5天) + +**子任务**: +1. 实现 MySQL → Neo4j 同步 + - 创建 `GraphSyncService` + - 实现全量同步 + - 实现增量同步 + - 实现对账机制 + +2. 实现手动触发构建 + - 创建 `/api/kg/sync` 端点 + - 支持按实体类型同步 + - 支持按时间范围同步 + +3. 实现同步监控 + - 记录同步日志 + - 统计同步数据量 + - 监控同步耗时 + +4. 编写集成测试 + - 测试全量同步 + - 测试增量同步 + - 测试对账机制 + +**验收标准**: +- ✅ 能够从 MySQL 同步元数据到 Neo4j +- ✅ 支持增量更新 +- ✅ 有完整的监控和日志 +- ✅ 集成测试通过 + +#### 任务 1.5:实现基础查询功能(2天) + +**子任务**: +1. 实现邻居查询 + - 支持 N 跳邻居查询 + - 支持按关系类型过滤 + - 支持分页 + +2. 实现路径查询 + - 最短路径 + - 所有路径(限制最大数量) + +3. 实现子图查询 + - 按条件筛选子图 + - 支持导出 + +4. 编写单元测试 + +**验收标准**: +- ✅ 查询功能完整 +- ✅ 性能满足要求(< 1s) +- ✅ 测试覆盖率 > 80% + +### 里程碑 + +**M1.1**(第 1 周结束): +- ✅ Python 抽取器 API 完成 +- ✅ Java 关系功能完成 + +**M1.2**(第 2 周结束): +- ✅ 核心实体和关系模型定义完成 +- ✅ 图谱构建流程完成 + +**M1.3**(第 3 周结束): +- ✅ 基础查询功能完成 +- ✅ 集成测试通过 +- ✅ MVP 演示 + +### 验收标准 + +1. **功能完整性**: + - ✅ 能够从文本抽取实体和关系 + - ✅ 能够存储到 Neo4j + - ✅ 能够查询和遍历图谱 + - ✅ 支持基础的权限控制 + +2. **性能指标**: + - ✅ 抽取响应时间 < 5s + - ✅ 查询响应时间 < 1s + - ✅ 同步吞吐量 > 1000 实体/分钟 + +3. **质量指标**: + - ✅ 单元测试覆盖率 > 80% + - ✅ 集成测试通过 + - ✅ 代码审查通过 + +--- + +## ⏳ 第 2 阶段:GraphRAG 融合(3-4周) + +### 目标 +将知识图谱与现有 RAG 系统深度融合,提升检索和生成质量。 + +### 任务列表 + +#### 任务 2.1:实现混合检索(2周) + +**子任务**: +1. 在 `rag-query-service` 中增加图检索模块 +2. 实现 Milvus + Neo4j 并行检索 +3. 实现结果融合和排序 +4. 实现三元组文本化 + +#### 任务 2.2:实现 GraphRAG(1周) + +**子任务**: +1. 设计 GraphRAG 流程 +2. 实现 Context 构建 +3. 实现 LLM 生成 +4. 优化 Prompt + +#### 任务 2.3:评估和优化(1周) + +**子任务**: +1. 设计评估指标 +2. 收集测试数据 +3. 进行 A/B 测试 +4. 优化检索策略 + +### 验收标准 +- ✅ 混合检索性能优于单一检索 +- ✅ 支持可配置的检索策略 +- ✅ 有完整的评估指标 + +--- + +## ⏳ 第 3 阶段:可视化与优化(4-6周) + +### 目标 +提供友好的图谱可视化和编辑功能,优化性能和运维。 + +### 任务列表 + +#### 任务 3.1:前端图谱浏览器(2周) + +**子任务**: +1. 搭建 React + AntV G6 项目 +2. 实现图谱可视化 +3. 实现交互功能(缩放、拖拽、搜索) +4. 实现查询构建器 + +#### 任务 3.2:Human-in-the-loop 编辑(1周) + +**子任务**: +1. 实现实体编辑 +2. 实现关系编辑 +3. 实现批量操作 +4. 实现审核流程 + +#### 任务 3.3:性能优化(1周) + +**子任务**: +1. 优化索引策略 +2. 实现缓存机制 +3. 实现离线计算 +4. 优化查询语句 + +#### 任务 3.4:监控和运维(1周) + +**子任务**: +1. 集成 Prometheus + Grafana +2. 实现备份和恢复 +3. 编写运维文档 +4. 进行压力测试 + +### 验收标准 +- ✅ 支持大规模图谱可视化(10000+ 节点) +- ✅ 支持实时编辑和反馈 +- ✅ 查询响应时间 < 1s +- ✅ 有完整的监控和告警 + +--- + +## 📊 资源需求 + +### 人力资源 +- **后端开发**:1-2 人 +- **前端开发**:1 人 +- **算法工程师**:1 人(兼职) +- **测试工程师**:1 人(兼职) + +### 基础设施 +- **Neo4j**:4 核 8GB 内存(开发环境) +- **MySQL**:现有资源 +- **Milvus**:现有资源 +- **Redis**:现有资源 + +### 外部依赖 +- **LLM API**:OpenAI 或自部署模型 +- **对象存储**:备份使用 + +--- + +## 🎯 关键里程碑 + +| 里程碑 | 时间 | 交付物 | +|--------|------|--------| +| M0 | 第 1 周 | 基础设施搭建完成 ✅ | +| M1 | 第 4 周 | MVP 完成,支持基础图谱构建和查询 | +| M2 | 第 8 周 | GraphRAG 融合完成,检索质量提升 | +| M3 | 第 12 周 | 可视化和优化完成,系统上线 | + +--- + +## 🔗 相关文档 + +- [总体方案](./README.md) +- [架构设计](./architecture.md) +- [AI 分析结果](./analysis/) diff --git a/docs/knowledge-graph/schema/entities.md b/docs/knowledge-graph/schema/entities.md new file mode 100644 index 0000000..962ca62 --- /dev/null +++ b/docs/knowledge-graph/schema/entities.md @@ -0,0 +1,329 @@ +# DataMate 知识图谱 - 核心实体定义 + +> Schema 版本:1.0.0 +> 更新日期:2026-02-17 + +## 概述 + +DataMate 知识图谱定义了 **8 类核心实体**,覆盖数据资产管理、任务追踪、组织归属和知识管理四大领域。 + +所有实体在 Neo4j 中统一使用 `Entity` 标签,通过 `type` 属性区分语义类型。每个实体都包含以下公共属性: + +| 公共属性 | 类型 | 必填 | 说明 | +|---------|------|------|------| +| `id` | String (UUID) | 是 | 全局唯一标识符 | +| `name` | String | 是 | 实体名称 | +| `type` | String | 是 | 实体类型(见下文各类型定义) | +| `description` | String | 否 | 实体描述 | +| `graph_id` | String (UUID) | 是 | 所属图谱 ID,用于多租户隔离 | +| `source_id` | String | 否 | 来源记录 ID(MySQL 主键或外部系统 ID) | +| `source_type` | String | 否 | 来源类型:`SYNC`(MySQL 同步)、`EXTRACTION`(LLM 抽取)、`MANUAL`(人工创建) | +| `confidence` | Double | 否 | 置信度 0.0-1.0(同步数据默认 1.0,抽取数据由模型评分) | +| `properties_json` | String (JSON) | 否 | 类型特有 properties 的 JSON 序列化,各类型的 properties 定义见下文 | +| `created_at` | LocalDateTime | 是 | 创建时间 | + +--- + +## 1. Dataset(数据集) + +数据集是 DataMate 的核心资产,代表一组结构化或非结构化数据的集合。 + +**对应代码模型**:`data-management-service` 的 `Dataset.java` + +### properties(properties_json 字段) + +| property | 类型 | 必填 | 约束 | 说明 | +|----------|------|------|------|------| +| `dataset_type` | String | 是 | `IMAGE` / `TEXT` / `QA` / `MULTIMODAL` / `OTHER` | 数据集类型 | +| `status` | String | 是 | `DRAFT` / `ACTIVE` / `ARCHIVED` | 数据集状态 | +| `category` | String | 否 | 最长 50 字符 | 业务分类 | +| `format` | String | 否 | — | 数据格式(如 CSV、JSON、DICOM) | +| `record_count` | Long | 否 | >= 0 | 记录/文件数量 | +| `size_bytes` | Long | 否 | >= 0 | 数据集大小(字节) | +| `version` | Integer | 否 | >= 1 | 版本号 | +| `tags` | List\ | 否 | — | 标签列表 | + +### Cypher 示例 + +```cypher +// 创建 Dataset 实体(类型 properties 序列化到 properties_json) +CREATE (d:Entity { + id: 'a1b2c3d4-...', + name: '用户行为日志-v2', + type: 'Dataset', + description: '2025年Q4用户行为埋点数据', + graph_id: $graphId, + source_id: '12345', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"dataset_type":"TEXT","status":"ACTIVE","category":"用户行为","format":"JSON","record_count":1500000,"size_bytes":2147483648,"version":2,"tags":["behavior","production"]}', + created_at: datetime() +}) +``` + +--- + +## 2. Field(字段) + +字段代表数据集中的列或属性元数据,是数据血缘分析和影响评估的基础单元。 + +**对应代码模型**:从 `DatasetFile` 的 schema 元数据中提取 + +### properties(properties_json 字段) + +| property | 类型 | 必填 | 约束 | 说明 | +|----------|------|------|------|------| +| `data_type` | String | 是 | — | 数据类型(如 STRING、INT、FLOAT、DATETIME、JSON) | +| `nullable` | Boolean | 否 | — | 是否允许空值 | +| `is_primary_key` | Boolean | 否 | — | 是否为主键 | +| `default_value` | String | 否 | — | 默认值 | +| `sample_values` | List\ | 否 | 最多 5 个 | 示例值 | +| `statistics` | String | 否 | JSON 格式 | 字段统计信息(null 率、唯一值数等) | + +### Cypher 示例 + +```cypher +CREATE (f:Entity { + id: 'f1e2d3c4-...', + name: 'user_id', + type: 'Field', + description: '用户唯一标识符', + graph_id: $graphId, + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"data_type":"STRING","nullable":false,"is_primary_key":true,"sample_values":["U001","U002","U003"]}', + created_at: datetime() +}) +``` + +--- + +## 3. LabelTask(标注任务) + +标注任务代表一次数据标注活动,包括人工标注和自动标注。 + +**对应代码模型**:`data-annotation-service` 的 `LabelingProject`、`AutoAnnotationTask`;`task-coordination-service` 的 `TaskMeta` + +### properties(properties_json 字段) + +| property | 类型 | 必填 | 约束 | 说明 | +|----------|------|------|------|------| +| `task_mode` | String | 是 | `MANUAL` / `AUTO` / `HYBRID` | 标注模式 | +| `data_type` | String | 否 | `image` / `text` / `audio` / `video` / `pdf` 等 | 标注数据类型 | +| `labeling_type` | String | 否 | — | 标注类型(如 NER、目标检测、情感分析) | +| `status` | String | 是 | `PENDING` / `IN_PROGRESS` / `COMPLETED` / `FAILED` / `STOPPED` | 任务状态 | +| `progress` | Double | 否 | 0.0-100.0 | 完成进度百分比 | +| `template_name` | String | 否 | — | 使用的标注模板名称 | + +### Cypher 示例 + +```cypher +CREATE (t:Entity { + id: 'e5f6a7b8-...', + name: '医学图像病灶标注-批次3', + type: 'LabelTask', + description: 'CT影像中肺结节目标检测标注', + graph_id: $graphId, + source_id: '67890', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"task_mode":"HYBRID","data_type":"image","labeling_type":"object_detection","status":"IN_PROGRESS","progress":45.5,"template_name":"医学目标检测"}', + created_at: datetime() +}) +``` + +--- + +## 4. Workflow(工作流) + +工作流代表一组数据处理步骤的编排定义,涵盖数据清洗、数据合成、数据评估等处理管道。 + +**对应代码模型**:`data-cleaning-service` 的 `CleaningTemplate`;`data-collection-service` 的 `CollectionTemplate`;算子编排 `Operator` + +### properties(properties_json 字段) + +| property | 类型 | 必填 | 约束 | 说明 | +|----------|------|------|------|------| +| `workflow_type` | String | 是 | `CLEANING` / `SYNTHESIS` / `EVALUATION` / `COLLECTION` / `CUSTOM` | 工作流类型 | +| `status` | String | 否 | `DRAFT` / `ACTIVE` / `DEPRECATED` | 工作流状态 | +| `version` | String | 否 | — | 版本号 | +| `operator_count` | Integer | 否 | >= 0 | 包含的算子数量 | +| `schedule` | String | 否 | Cron 表达式 | 调度表达式(用于定时工作流) | + +### Cypher 示例 + +```cypher +CREATE (w:Entity { + id: 'c9d0e1f2-...', + name: '文本去重清洗管道', + type: 'Workflow', + description: '基于SimHash的文本去重 + 格式标准化 + 质量过滤', + graph_id: $graphId, + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"workflow_type":"CLEANING","status":"ACTIVE","version":"2.1","operator_count":3}', + created_at: datetime() +}) +``` + +--- + +## 5. Job(作业) + +作业代表一次具体的任务执行实例,是工作流的运行时实体,记录输入输出和执行状态。 + +**对应代码模型**:`CleaningTask`、`DataSynthInstance`、`EvaluationTask`、`CollectionTask`、`TaskExecution` + +### properties(properties_json 字段) + +| property | 类型 | 必填 | 约束 | 说明 | +|----------|------|------|------|------| +| `job_type` | String | 是 | `CLEANING` / `SYNTHESIS` / `EVALUATION` / `COLLECTION` / `ANNOTATION` | 作业类型 | +| `status` | String | 是 | `PENDING` / `RUNNING` / `COMPLETED` / `FAILED` / `STOPPED` / `CANCELLED` | 执行状态 | +| `started_at` | String | 否 | ISO 8601 | 开始时间 | +| `completed_at` | String | 否 | ISO 8601 | 完成时间 | +| `duration_seconds` | Long | 否 | >= 0 | 执行耗时(秒) | +| `input_count` | Long | 否 | >= 0 | 输入记录/文件数 | +| `output_count` | Long | 否 | >= 0 | 输出记录/文件数 | +| `error_message` | String | 否 | — | 错误信息(失败时) | + +### Cypher 示例 + +```cypher +CREATE (j:Entity { + id: 'd3e4f5a6-...', + name: '清洗作业-20260215-001', + type: 'Job', + description: '用户行为日志去重清洗', + graph_id: $graphId, + source_id: '54321', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"job_type":"CLEANING","status":"COMPLETED","started_at":"2026-02-15T10:00:00","completed_at":"2026-02-15T10:35:00","duration_seconds":2100,"input_count":1500000,"output_count":1380000}', + created_at: datetime() +}) +``` + +--- + +## 6. User(用户) + +用户代表 DataMate 平台的操作人员,用于追踪数据资产的责任人和任务的执行者。 + +**对应代码模型**:`User.java`(`user` 表) + +### properties(properties_json 字段) + +| property | 类型 | 必填 | 约束 | 说明 | +|----------|------|------|------|------| +| `username` | String | 是 | 唯一 | 登录用户名 | +| `email` | String | 否 | — | 邮箱地址 | +| `role` | String | 否 | `ADMIN` / `USER` | 角色 | +| `enabled` | Boolean | 否 | — | 是否启用 | + +### Cypher 示例 + +```cypher +CREATE (u:Entity { + id: 'b7c8d9e0-...', + name: '张三', + type: 'User', + graph_id: $graphId, + source_id: '1001', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"username":"zhangsan","email":"zhangsan@example.com","role":"USER","enabled":true}', + created_at: datetime() +}) +``` + +--- + +## 7. Org(组织) + +组织代表企业内部的团队或部门,用于数据资产的归属管理和权限隔离。 + +**对应代码模型**:从 `User.organization` 字段聚合派生 + +### properties(properties_json 字段) + +| property | 类型 | 必填 | 约束 | 说明 | +|----------|------|------|------|------| +| `org_code` | String | 否 | 唯一 | 组织编码 | +| `parent_org_id` | String | 否 | UUID | 上级组织 ID | +| `level` | Integer | 否 | >= 1 | 组织层级 | +| `member_count` | Integer | 否 | >= 0 | 成员数量 | + +### Cypher 示例 + +```cypher +CREATE (o:Entity { + id: 'a0b1c2d3-...', + name: '数据工程部', + type: 'Org', + description: '负责数据采集、清洗和标注', + graph_id: $graphId, + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"org_code":"DE","level":2,"member_count":15}', + created_at: datetime() +}) +``` + +--- + +## 8. KnowledgeSet(知识集) + +知识集代表经过整理和验证的知识资产集合,是 RAG 检索和知识问答的基础。 + +**对应代码模型**:`KnowledgeSet.java`(`knowledge_set` 表) + +### properties(properties_json 字段) + +| property | 类型 | 必填 | 约束 | 说明 | +|----------|------|------|------|------| +| `status` | String | 是 | `DRAFT` / `PUBLISHED` / `ARCHIVED` / `DEPRECATED` | 知识集状态 | +| `domain` | String | 否 | — | 知识领域 | +| `business_line` | String | 否 | — | 业务线 | +| `sensitivity` | String | 否 | `PUBLIC` / `INTERNAL` / `CONFIDENTIAL` / `SECRET` | 敏感级别 | +| `item_count` | Integer | 否 | >= 0 | 包含的知识条目数 | +| `valid_from` | String | 否 | ISO 8601 | 有效期开始 | +| `valid_to` | String | 否 | ISO 8601 | 有效期结束 | + +### Cypher 示例 + +```cypher +CREATE (k:Entity { + id: 'f4e5d6c7-...', + name: '医学影像标注规范知识库', + type: 'KnowledgeSet', + description: 'CT/MRI影像标注标准和常见病灶特征知识', + graph_id: $graphId, + source_id: '777', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"status":"PUBLISHED","domain":"医学影像","business_line":"AI辅助诊断","sensitivity":"INTERNAL","item_count":320,"valid_from":"2026-01-01T00:00:00","valid_to":"2027-01-01T00:00:00"}', + created_at: datetime() +}) +``` + +--- + +## 实体类型汇总 + +| 实体类型 | Neo4j type 值 | 核心用途 | 来源 | +|---------|--------------|---------|------| +| Dataset | `Dataset` | 数据资产管理、血缘追踪 | MySQL 同步 | +| Field | `Field` | 字段级血缘、影响分析 | MySQL 同步 / Schema 解析 | +| LabelTask | `LabelTask` | 标注任务追踪、人员管理 | MySQL 同步 | +| Workflow | `Workflow` | 流程编排、复用管理 | MySQL 同步 | +| Job | `Job` | 执行追踪、输入输出血缘 | MySQL 同步 | +| User | `User` | 责任人追踪、权限管理 | MySQL 同步 | +| Org | `Org` | 组织归属、资产隔离 | MySQL 同步 / 派生 | +| KnowledgeSet | `KnowledgeSet` | 知识资产管理、RAG 检索 | MySQL 同步 | + +## 扩展说明 + +- **自定义实体类型**:除上述 8 类核心实体外,用户可通过 LLM 抽取或手动创建自定义实体类型。自定义实体使用相同的 `Entity` 标签和公共属性结构,`type` 字段可为任意字符串。 +- **属性存储**:类型特有 properties 存储在 `properties_json` 字段中(JSON 序列化),不直接作为 Neo4j 节点属性。这保证了 schema 的灵活性,同时通过 `type` 字段实现类型区分。 +- **索引策略**:`id`、`graph_id`、`type`、`name` 字段建立 Neo4j 索引,`properties_json` 中的 properties 不建立索引。如果某个 property 需要高频查询,应提升为节点顶层属性并建立索引。 diff --git a/docs/knowledge-graph/schema/er-diagram.md b/docs/knowledge-graph/schema/er-diagram.md new file mode 100644 index 0000000..12d0646 --- /dev/null +++ b/docs/knowledge-graph/schema/er-diagram.md @@ -0,0 +1,298 @@ +# DataMate 知识图谱 - 实体关系图 + +> Schema 版本:1.0.0 +> 更新日期:2026-02-17 + +## 核心实体关系总览 + +```mermaid +graph LR + %% 实体定义 + Dataset["Dataset
数据集"] + Field["Field
字段"] + LabelTask["LabelTask
标注任务"] + Workflow["Workflow
工作流"] + Job["Job
作业"] + User["User
用户"] + Org["Org
组织"] + KnowledgeSet["KnowledgeSet
知识集"] + + %% 关系连接 + Dataset -->|HAS_FIELD| Field + Dataset -->|DERIVED_FROM| Dataset + Dataset -->|BELONGS_TO| Org + + Job -->|USES_DATASET| Dataset + Job -->|PRODUCES| Dataset + Job -->|DEPENDS_ON| Job + + Workflow -->|TRIGGERS| Job + Workflow -->|USES_DATASET| Dataset + + LabelTask -->|USES_DATASET| Dataset + LabelTask -->|ASSIGNED_TO| User + + User -->|BELONGS_TO| Org + + Field -->|IMPACTS| Field + + KnowledgeSet -->|SOURCED_FROM| Dataset + + %% 样式 + classDef dataAsset fill:#4A90D9,stroke:#2C5F8A,color:#fff,stroke-width:2px + classDef task fill:#7B68EE,stroke:#5A4CB5,color:#fff,stroke-width:2px + classDef actor fill:#50C878,stroke:#3A9B5B,color:#fff,stroke-width:2px + classDef knowledge fill:#FFB347,stroke:#CC8F39,color:#fff,stroke-width:2px + + class Dataset,Field dataAsset + class LabelTask,Workflow,Job task + class User,Org actor + class KnowledgeSet knowledge +``` + +## 分领域视图 + +### 数据血缘视图 + +展示数据集之间的派生关系和字段级血缘。 + +```mermaid +graph TB + subgraph 源数据层 + DS_RAW["Dataset
原始数据集"] + F1["Field: user_id"] + F2["Field: event_type"] + F3["Field: timestamp"] + end + + subgraph 处理层 + JOB_CLEAN["Job
清洗作业"] + JOB_SYNTH["Job
合成作业"] + end + + subgraph 产出数据层 + DS_CLEAN["Dataset
清洗后数据集"] + DS_SYNTH["Dataset
合成数据集"] + F1_CLEAN["Field: user_id"] + F4["Field: user_segment"] + end + + DS_RAW -->|HAS_FIELD| F1 + DS_RAW -->|HAS_FIELD| F2 + DS_RAW -->|HAS_FIELD| F3 + + JOB_CLEAN -->|USES_DATASET| DS_RAW + JOB_CLEAN -->|PRODUCES| DS_CLEAN + JOB_SYNTH -->|USES_DATASET| DS_CLEAN + JOB_SYNTH -->|PRODUCES| DS_SYNTH + + DS_CLEAN -->|DERIVED_FROM| DS_RAW + DS_SYNTH -->|DERIVED_FROM| DS_CLEAN + + DS_CLEAN -->|HAS_FIELD| F1_CLEAN + DS_SYNTH -->|HAS_FIELD| F4 + + F1 -->|IMPACTS| F1_CLEAN + F1_CLEAN -->|IMPACTS| F4 + + classDef source fill:#E8F4FD,stroke:#4A90D9,color:#333 + classDef process fill:#F3E8FF,stroke:#7B68EE,color:#333 + classDef output fill:#E8FFF0,stroke:#50C878,color:#333 + + class DS_RAW,F1,F2,F3 source + class JOB_CLEAN,JOB_SYNTH process + class DS_CLEAN,DS_SYNTH,F1_CLEAN,F4 output +``` + +### 任务编排视图 + +展示工作流、作业和任务之间的编排关系。 + +```mermaid +graph LR + subgraph 工作流定义 + WF_CLEAN["Workflow
清洗管道"] + WF_EVAL["Workflow
评估管道"] + end + + subgraph 作业执行 + JOB1["Job
清洗作业 #1"] + JOB2["Job
清洗作业 #2"] + JOB3["Job
评估作业"] + end + + subgraph 标注任务 + LT1["LabelTask
人工标注"] + LT2["LabelTask
自动标注"] + end + + subgraph 人员 + U1["User
张三"] + U2["User
李四"] + end + + WF_CLEAN -->|TRIGGERS| JOB1 + WF_CLEAN -->|TRIGGERS| JOB2 + WF_EVAL -->|TRIGGERS| JOB3 + + JOB2 -->|DEPENDS_ON| JOB1 + JOB3 -->|DEPENDS_ON| JOB2 + + LT1 -->|ASSIGNED_TO| U1 + LT2 -->|ASSIGNED_TO| U2 + + classDef wf fill:#7B68EE,stroke:#5A4CB5,color:#fff + classDef job fill:#9B8FFF,stroke:#7B68EE,color:#fff + classDef task fill:#B8A9FF,stroke:#9B8FFF,color:#fff + classDef user fill:#50C878,stroke:#3A9B5B,color:#fff + + class WF_CLEAN,WF_EVAL wf + class JOB1,JOB2,JOB3 job + class LT1,LT2 task + class U1,U2 user +``` + +### 组织归属视图 + +展示用户、数据集与组织的归属关系。 + +```mermaid +graph TB + subgraph 组织 + ORG1["Org
数据工程部"] + ORG2["Org
AI研发部"] + end + + subgraph 人员 + U1["User: 张三"] + U2["User: 李四"] + U3["User: 王五"] + end + + subgraph 数据资产 + DS1["Dataset: 用户行为日志"] + DS2["Dataset: 医学影像集"] + DS3["Dataset: 训练数据集"] + end + + U1 -->|BELONGS_TO| ORG1 + U2 -->|BELONGS_TO| ORG1 + U3 -->|BELONGS_TO| ORG2 + + DS1 -->|BELONGS_TO| ORG1 + DS2 -->|BELONGS_TO| ORG2 + DS3 -->|BELONGS_TO| ORG2 + + classDef org fill:#FFB347,stroke:#CC8F39,color:#fff + classDef user fill:#50C878,stroke:#3A9B5B,color:#fff + classDef data fill:#4A90D9,stroke:#2C5F8A,color:#fff + + class ORG1,ORG2 org + class U1,U2,U3 user + class DS1,DS2,DS3 data +``` + +### 知识溯源视图 + +展示知识集与数据集的溯源关系。 + +```mermaid +graph LR + subgraph 数据源 + DS1["Dataset
用户行为日志"] + DS2["Dataset
产品文档"] + end + + subgraph 知识资产 + KS1["KnowledgeSet
用户行为知识库"] + end + + subgraph 标注 + LT["LabelTask
知识标注"] + end + + KS1 -->|SOURCED_FROM| DS1 + KS1 -->|SOURCED_FROM| DS2 + LT -->|USES_DATASET| DS1 + + classDef data fill:#4A90D9,stroke:#2C5F8A,color:#fff + classDef knowledge fill:#FFB347,stroke:#CC8F39,color:#fff + classDef task fill:#7B68EE,stroke:#5A4CB5,color:#fff + + class DS1,DS2 data + class KS1 knowledge + class LT task +``` + +## 综合示例:完整数据流 + +展示从原始数据到知识资产的完整处理链路。 + +```mermaid +graph TB + %% 组织和人员 + ORG["Org: 数据工程部"] + USER["User: 张三"] + + %% 数据资产 + DS_RAW["Dataset: 原始日志"] + DS_CLEAN["Dataset: 清洗数据"] + F_UID_RAW["Field: user_id (原始)"] + F_UID_CLEAN["Field: user_id (清洗)"] + + %% 处理流程 + WF["Workflow: 清洗管道"] + JOB["Job: 清洗作业"] + LT["LabelTask: 情感标注"] + + %% 知识 + KS["KnowledgeSet: 行为知识库"] + + %% 组织归属 + USER -->|BELONGS_TO| ORG + DS_RAW -->|BELONGS_TO| ORG + + %% 数据结构 + DS_RAW -->|HAS_FIELD| F_UID_RAW + DS_CLEAN -->|HAS_FIELD| F_UID_CLEAN + + %% 处理链路 + WF -->|TRIGGERS| JOB + JOB -->|USES_DATASET| DS_RAW + JOB -->|PRODUCES| DS_CLEAN + DS_CLEAN -->|DERIVED_FROM| DS_RAW + + %% 字段血缘 + F_UID_RAW -->|IMPACTS| F_UID_CLEAN + + %% 任务分配 + LT -->|USES_DATASET| DS_CLEAN + LT -->|ASSIGNED_TO| USER + + %% 知识溯源 + KS -->|SOURCED_FROM| DS_CLEAN + + %% 样式 + classDef org fill:#FFB347,stroke:#CC8F39,color:#fff,stroke-width:2px + classDef user fill:#50C878,stroke:#3A9B5B,color:#fff,stroke-width:2px + classDef data fill:#4A90D9,stroke:#2C5F8A,color:#fff,stroke-width:2px + classDef field fill:#87CEEB,stroke:#4A90D9,color:#333,stroke-width:1px + classDef process fill:#7B68EE,stroke:#5A4CB5,color:#fff,stroke-width:2px + classDef knowledge fill:#FF6B6B,stroke:#CC5555,color:#fff,stroke-width:2px + + class ORG org + class USER user + class DS_RAW,DS_CLEAN data + class F_UID_RAW,F_UID_CLEAN field + class WF,JOB,LT process + class KS knowledge +``` + +## 图例 + +| 颜色 | 分类 | 包含实体 | +|------|------|---------| +| 蓝色 | 数据资产 | Dataset, Field | +| 紫色 | 任务/流程 | Workflow, Job, LabelTask | +| 绿色 | 人员 | User, Org | +| 橙色/红色 | 知识 | KnowledgeSet | diff --git a/docs/knowledge-graph/schema/relationships.md b/docs/knowledge-graph/schema/relationships.md new file mode 100644 index 0000000..6c2ac46 --- /dev/null +++ b/docs/knowledge-graph/schema/relationships.md @@ -0,0 +1,585 @@ +# DataMate 知识图谱 - 核心关系定义 + +> Schema 版本:1.0.0 +> 更新日期:2026-02-17 + +## 概述 + +DataMate 知识图谱定义了 **10 类核心关系**,覆盖数据血缘、任务编排、组织归属和知识溯源四大场景。 + +所有关系在 Neo4j 中统一使用 `RELATED_TO` 关系类型,通过 `relation_type` 属性区分语义类型。每个关系都包含以下公共属性: + +| 公共属性 | 类型 | 必填 | 说明 | +|---------|------|------|------| +| `id` | String (UUID) | 是 | 关系唯一标识符 | +| `relation_type` | String | 是 | 语义关系类型(见下文各类型定义) | +| `graph_id` | String (UUID) | 是 | 所属图谱 ID | +| `weight` | Double | 否 | 关系权重 0.0-1.0(默认 1.0) | +| `confidence` | Double | 否 | 置信度 0.0-1.0(同步数据默认 1.0,抽取数据由模型评分) | +| `source_id` | String | 否 | 来源记录 ID | +| `properties_json` | String | 否 | 扩展属性 JSON | +| `created_at` | LocalDateTime | 是 | 创建时间 | + +### 关系方向约定 + +所有关系均为有向关系。方向表示语义上的"主动方 → 被动方"关系: +- `(A)-[:RELATED_TO {relation_type: 'HAS_FIELD'}]->(B)` 表示 A 拥有 B +- 查询时应注意方向,反向查询需要使用 `<-[]-` 语法 + +--- + +## 1. HAS_FIELD(包含字段) + +**方向**:`Dataset → Field` + +表示数据集包含某个字段/列。这是数据血缘分析的基础关系,支撑字段级影响评估。 + +### 关系属性 + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `ordinal` | Integer | 否 | 字段在数据集中的位置(从 0 开始) | +| `required` | Boolean | 否 | 是否为必填字段 | + +### 约束 + +- 源实体类型必须为 `Dataset` +- 目标实体类型必须为 `Field` +- 同一 Dataset → Field 对不应重复 + +### Cypher 示例 + +```cypher +// 创建 HAS_FIELD 关系 +MATCH (d:Entity {id: $datasetId, graph_id: $graphId}) +MATCH (f:Entity {id: $fieldId, graph_id: $graphId}) +CREATE (d)-[r:RELATED_TO { + id: randomUUID(), + relation_type: 'HAS_FIELD', + graph_id: $graphId, + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"ordinal": 0, "required": true}', + created_at: datetime() +}]->(f) + +// 查询数据集的所有字段 +MATCH (d:Entity {id: $datasetId, graph_id: $graphId}) + -[r:RELATED_TO {relation_type: 'HAS_FIELD', graph_id: $graphId}]-> + (f:Entity {graph_id: $graphId}) +RETURN f ORDER BY r.properties_json +``` + +### 业务场景 + +- 查看数据集包含哪些字段 +- 字段搜索:找到包含 `user_id` 字段的所有数据集 +- Schema 对比:比较两个数据集的字段差异 + +--- + +## 2. DERIVED_FROM(派生自) + +**方向**:`Dataset → Dataset` + +表示数据集之间的血缘关系:目标数据集是源数据集经过某种处理后派生出来的。涵盖数据清洗、数据合成、版本迭代等场景。 + +### 关系属性 + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `derivation_type` | String | 是 | 派生类型:`CLEANING`(清洗)/ `SYNTHESIS`(合成)/ `SPLIT`(拆分)/ `MERGE`(合并)/ `VERSION`(版本迭代) | +| `job_id` | String | 否 | 产生该派生关系的作业 ID | +| `transformation` | String | 否 | 转换描述(如"去重 + 格式标准化") | + +### 约束 + +- 源实体和目标实体类型均为 `Dataset` +- 不允许自引用(源 ≠ 目标) +- 建议检查避免循环依赖 + +### Cypher 示例 + +```cypher +// 创建清洗派生关系 +MATCH (output:Entity {id: $outputDatasetId, graph_id: $graphId}) +MATCH (input:Entity {id: $inputDatasetId, graph_id: $graphId}) +CREATE (output)-[r:RELATED_TO { + id: randomUUID(), + relation_type: 'DERIVED_FROM', + graph_id: $graphId, + weight: 1.0, + confidence: 1.0, + properties_json: '{"derivation_type":"CLEANING","job_id":"d3e4f5a6-...","transformation":"SimHash去重 + 空值过滤"}', + created_at: datetime() +}]->(input) + +// 追踪数据血缘(最多 5 跳) +MATCH path = (d:Entity {id: $datasetId, graph_id: $graphId}) + -[:RELATED_TO *1..5 {relation_type: 'DERIVED_FROM'}]-> + (ancestor:Entity {graph_id: $graphId}) +RETURN path +``` + +### 业务场景 + +- **数据血缘追踪**:追溯数据集的来源链路 +- **影响分析**:当源数据集变更时,哪些下游数据集受影响 +- **版本管理**:查看数据集的版本演进历史 + +--- + +## 3. USES_DATASET(使用数据集) + +**方向**:`Job | LabelTask | Workflow → Dataset` + +表示作业、标注任务或工作流使用某个数据集作为输入。 + +### 关系属性 + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `usage_role` | String | 否 | 使用角色:`INPUT`(输入)/ `REFERENCE`(参考)/ `VALIDATION`(验证) | + +### 约束 + +- 源实体类型为 `Job`、`LabelTask` 或 `Workflow` +- 目标实体类型为 `Dataset` + +### Cypher 示例 + +```cypher +// 创建使用关系 +MATCH (j:Entity {id: $jobId, graph_id: $graphId}) +MATCH (d:Entity {id: $datasetId, graph_id: $graphId}) +CREATE (j)-[r:RELATED_TO { + id: randomUUID(), + relation_type: 'USES_DATASET', + graph_id: $graphId, + weight: 1.0, + confidence: 1.0, + properties_json: '{"usage_role":"INPUT"}', + created_at: datetime() +}]->(d) + +// 查询数据集被哪些作业使用 +MATCH (j:Entity {graph_id: $graphId}) + -[r:RELATED_TO {relation_type: 'USES_DATASET', graph_id: $graphId}]-> + (d:Entity {id: $datasetId, graph_id: $graphId}) +RETURN j +``` + +### 业务场景 + +- 查看数据集的消费者:谁在使用这个数据集 +- 评估数据集的重要程度:被多少任务依赖 +- 任务输入追溯:任务使用了哪些数据集 + +--- + +## 4. PRODUCES(产出) + +**方向**:`Job → Dataset` + +表示作业执行后产出了一个新的数据集。与 `USES_DATASET` 相对,构成完整的输入输出链路。 + +### 关系属性 + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `output_type` | String | 否 | 产出类型:`PRIMARY`(主输出)/ `SECONDARY`(副产物,如日志、统计报告) | + +### 约束 + +- 源实体类型为 `Job` +- 目标实体类型为 `Dataset` +- 一个 Job 可以产出多个 Dataset(如主输出 + 统计报告) + +### Cypher 示例 + +```cypher +// 创建产出关系 +MATCH (j:Entity {id: $jobId, graph_id: $graphId}) +MATCH (d:Entity {id: $outputDatasetId, graph_id: $graphId}) +CREATE (j)-[r:RELATED_TO { + id: randomUUID(), + relation_type: 'PRODUCES', + graph_id: $graphId, + weight: 1.0, + confidence: 1.0, + properties_json: '{"output_type":"PRIMARY"}', + created_at: datetime() +}]->(d) + +// 查看作业的完整输入输出 +MATCH (input:Entity {graph_id: $graphId}) + <-[:RELATED_TO {relation_type: 'USES_DATASET'}]- + (j:Entity {id: $jobId, graph_id: $graphId}) + -[:RELATED_TO {relation_type: 'PRODUCES'}]-> + (output:Entity {graph_id: $graphId}) +RETURN input, j, output +``` + +### 业务场景 + +- **端到端血缘**:结合 `USES_DATASET` 查看 Input → Job → Output 完整链路 +- **产出追踪**:查看作业产出了哪些数据集 +- **成本归因**:将产出数据集的成本归因到执行作业 + +--- + +## 5. ASSIGNED_TO(分配给) + +**方向**:`LabelTask | Job → User` + +表示任务被分配给某个用户执行。 + +### 关系属性 + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `assigned_at` | String | 否 | 分配时间(ISO 8601) | +| `role` | String | 否 | 分配角色:`EXECUTOR`(执行者)/ `REVIEWER`(审核者)/ `OWNER`(负责人) | + +### 约束 + +- 源实体类型为 `LabelTask` 或 `Job` +- 目标实体类型为 `User` +- 同一任务可分配给多个用户(不同角色) + +### Cypher 示例 + +```cypher +// 创建分配关系 +MATCH (t:Entity {id: $taskId, graph_id: $graphId}) +MATCH (u:Entity {id: $userId, graph_id: $graphId}) +CREATE (t)-[r:RELATED_TO { + id: randomUUID(), + relation_type: 'ASSIGNED_TO', + graph_id: $graphId, + weight: 1.0, + confidence: 1.0, + properties_json: '{"assigned_at":"2026-02-15T10:00:00","role":"EXECUTOR"}', + created_at: datetime() +}]->(u) + +// 查询用户的所有待办任务 +MATCH (t:Entity {graph_id: $graphId}) + -[r:RELATED_TO {relation_type: 'ASSIGNED_TO', graph_id: $graphId}]-> + (u:Entity {id: $userId, graph_id: $graphId}) +RETURN t +``` + +### 业务场景 + +- **工作量分析**:查看用户被分配了多少任务 +- **任务追踪**:查看任务的执行者和审核者 +- **人员负载均衡**:分析团队内任务分配情况 + +--- + +## 6. BELONGS_TO(归属于) + +**方向**:`User → Org` 或 `Dataset → Org` + +表示用户属于某个组织,或数据集归属于某个组织。 + +### 关系属性 + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `membership_type` | String | 否 | 归属类型:`PRIMARY`(主归属)/ `SECONDARY`(兼任/共享) | +| `since` | String | 否 | 归属起始时间(ISO 8601) | + +### 约束 + +- 源实体类型为 `User` 或 `Dataset` +- 目标实体类型为 `Org` +- User → Org 通常为 1:1(主归属),但允许兼任 + +### Cypher 示例 + +```cypher +// 用户归属组织 +MATCH (u:Entity {id: $userId, graph_id: $graphId}) +MATCH (o:Entity {id: $orgId, graph_id: $graphId}) +CREATE (u)-[r:RELATED_TO { + id: randomUUID(), + relation_type: 'BELONGS_TO', + graph_id: $graphId, + weight: 1.0, + confidence: 1.0, + properties_json: '{"membership_type":"PRIMARY","since":"2025-03-01T00:00:00"}', + created_at: datetime() +}]->(o) + +// 查询组织下的所有数据资产 +MATCH (d:Entity {type: 'Dataset', graph_id: $graphId}) + -[:RELATED_TO {relation_type: 'BELONGS_TO', graph_id: $graphId}]-> + (o:Entity {id: $orgId, graph_id: $graphId}) +RETURN d +``` + +### 业务场景 + +- **组织资产看板**:查看组织拥有的所有数据集 +- **权限继承**:基于组织关系推导数据访问权限 +- **跨组织协作**:发现共享数据集的组织关系 + +--- + +## 7. TRIGGERS(触发) + +**方向**:`Workflow → Job` + +表示工作流触发了一次作业执行。 + +### 关系属性 + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `trigger_type` | String | 否 | 触发方式:`MANUAL`(手动)/ `SCHEDULED`(定时)/ `EVENT`(事件驱动) | +| `triggered_at` | String | 否 | 触发时间(ISO 8601) | + +### 约束 + +- 源实体类型为 `Workflow` +- 目标实体类型为 `Job` +- 一个 Workflow 可触发多个 Job(每次执行产生一个) + +### Cypher 示例 + +```cypher +// 创建触发关系 +MATCH (w:Entity {id: $workflowId, graph_id: $graphId}) +MATCH (j:Entity {id: $jobId, graph_id: $graphId}) +CREATE (w)-[r:RELATED_TO { + id: randomUUID(), + relation_type: 'TRIGGERS', + graph_id: $graphId, + weight: 1.0, + confidence: 1.0, + properties_json: '{"trigger_type":"SCHEDULED","triggered_at":"2026-02-15T10:00:00"}', + created_at: datetime() +}]->(j) + +// 查询工作流的执行历史 +MATCH (w:Entity {id: $workflowId, graph_id: $graphId}) + -[r:RELATED_TO {relation_type: 'TRIGGERS', graph_id: $graphId}]-> + (j:Entity {graph_id: $graphId}) +RETURN j ORDER BY r.created_at DESC +``` + +### 业务场景 + +- **执行历史**:查看工作流的所有执行记录 +- **故障排查**:定位工作流最近一次失败的作业 +- **运行统计**:统计工作流的执行频率和成功率 + +--- + +## 8. DEPENDS_ON(依赖) + +**方向**:`Job → Job` + +表示作业之间的执行依赖关系:源作业的执行依赖于目标作业的完成。 + +### 关系属性 + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `dependency_type` | String | 否 | 依赖类型:`STRICT`(强依赖,必须成功)/ `SOFT`(弱依赖,失败可继续) | + +### 约束 + +- 源实体和目标实体类型均为 `Job` +- 不允许自引用 +- 不允许循环依赖(应用层校验) + +### Cypher 示例 + +```cypher +// 创建依赖关系 +MATCH (j1:Entity {id: $jobId, graph_id: $graphId}) +MATCH (j2:Entity {id: $dependsOnJobId, graph_id: $graphId}) +CREATE (j1)-[r:RELATED_TO { + id: randomUUID(), + relation_type: 'DEPENDS_ON', + graph_id: $graphId, + weight: 1.0, + confidence: 1.0, + properties_json: '{"dependency_type":"STRICT"}', + created_at: datetime() +}]->(j2) + +// 查询作业的完整依赖链 +MATCH path = (j:Entity {id: $jobId, graph_id: $graphId}) + -[:RELATED_TO *1..10 {relation_type: 'DEPENDS_ON'}]-> + (dep:Entity {graph_id: $graphId}) +RETURN path +``` + +### 业务场景 + +- **DAG 执行调度**:确定作业执行顺序 +- **失败传播分析**:当某个作业失败,哪些下游作业受影响 +- **关键路径分析**:找到最长依赖链,识别瓶颈 + +--- + +## 9. IMPACTS(影响) + +**方向**:`Field → Field` + +表示字段之间的影响关系:源字段的变更会影响目标字段。这是跨数据集的字段级血缘关系。 + +### 关系属性 + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `impact_type` | String | 否 | 影响类型:`DIRECT`(直接映射)/ `TRANSFORM`(转换派生)/ `AGGREGATE`(聚合计算) | +| `transformation_rule` | String | 否 | 转换规则描述(如"UPPER(source.name)") | +| `job_id` | String | 否 | 建立该影响关系的作业 ID | + +### 约束 + +- 源实体和目标实体类型均为 `Field` +- 通常跨越不同 Dataset(但同 Dataset 内的字段派生也允许) +- 不允许自引用 + +### Cypher 示例 + +```cypher +// 创建字段影响关系 +MATCH (f1:Entity {id: $sourceFieldId, graph_id: $graphId}) +MATCH (f2:Entity {id: $targetFieldId, graph_id: $graphId}) +CREATE (f1)-[r:RELATED_TO { + id: randomUUID(), + relation_type: 'IMPACTS', + graph_id: $graphId, + weight: 0.8, + confidence: 0.9, + properties_json: '{"impact_type":"TRANSFORM","transformation_rule":"TRIM(LOWER(source))","job_id":"d3e4f5a6-..."}', + created_at: datetime() +}]->(f2) + +// 查询字段的影响范围(下游) +MATCH (f:Entity {id: $fieldId, graph_id: $graphId}) + -[:RELATED_TO *1..5 {relation_type: 'IMPACTS'}]-> + (downstream:Entity {graph_id: $graphId}) +RETURN downstream +``` + +### 业务场景 + +- **字段级血缘**:追踪字段从源到目标的完整链路 +- **影响评估**:修改某个字段前,评估下游影响范围 +- **数据质量追溯**:发现下游字段质量问题时,回溯源头 + +--- + +## 10. SOURCED_FROM(来源于) + +**方向**:`KnowledgeSet → Dataset` + +表示知识集的知识内容来源于某个数据集,是知识溯源的基础关系。 + +### 关系属性 + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `extraction_method` | String | 否 | 抽取方式:`LLM`(LLM 抽取)/ `RULE`(规则抽取)/ `MANUAL`(人工整理) | +| `extracted_at` | String | 否 | 抽取时间(ISO 8601) | +| `item_count` | Integer | 否 | 从该数据集抽取的知识条目数 | + +### 约束 + +- 源实体类型为 `KnowledgeSet` +- 目标实体类型为 `Dataset` +- 一个 KnowledgeSet 可来源于多个 Dataset + +### Cypher 示例 + +```cypher +// 创建来源关系 +MATCH (k:Entity {id: $knowledgeSetId, graph_id: $graphId}) +MATCH (d:Entity {id: $datasetId, graph_id: $graphId}) +CREATE (k)-[r:RELATED_TO { + id: randomUUID(), + relation_type: 'SOURCED_FROM', + graph_id: $graphId, + weight: 1.0, + confidence: 0.85, + properties_json: '{"extraction_method":"LLM","extracted_at":"2026-02-10T14:30:00","item_count":120}', + created_at: datetime() +}]->(d) + +// 查询知识集的所有数据来源 +MATCH (k:Entity {id: $knowledgeSetId, graph_id: $graphId}) + -[r:RELATED_TO {relation_type: 'SOURCED_FROM', graph_id: $graphId}]-> + (d:Entity {graph_id: $graphId}) +RETURN d, r.properties_json AS extraction_info +``` + +### 业务场景 + +- **知识溯源**:查看知识集基于哪些数据构建 +- **数据变更通知**:当源数据集更新时,提醒知识集需要刷新 +- **知识覆盖分析**:查看哪些数据集尚未被纳入知识管理 + +--- + +## 关系类型汇总 + +| 关系类型 | 方向 | relation_type 值 | 核心用途 | +|---------|------|-----------------|---------| +| HAS_FIELD | Dataset → Field | `HAS_FIELD` | 数据集字段结构 | +| DERIVED_FROM | Dataset → Dataset | `DERIVED_FROM` | 数据集级血缘 | +| USES_DATASET | Job/LabelTask/Workflow → Dataset | `USES_DATASET` | 输入依赖 | +| PRODUCES | Job → Dataset | `PRODUCES` | 输出产出 | +| ASSIGNED_TO | LabelTask/Job → User | `ASSIGNED_TO` | 任务分配 | +| BELONGS_TO | User/Dataset → Org | `BELONGS_TO` | 组织归属 | +| TRIGGERS | Workflow → Job | `TRIGGERS` | 流程触发 | +| DEPENDS_ON | Job → Job | `DEPENDS_ON` | 作业依赖 | +| IMPACTS | Field → Field | `IMPACTS` | 字段级血缘 | +| SOURCED_FROM | KnowledgeSet → Dataset | `SOURCED_FROM` | 知识溯源 | + +## 典型查询模式 + +### 1. 端到端数据血缘 + +```cypher +// 从最终数据集追溯到原始数据集,经过的所有处理步骤 +MATCH path = (final:Entity {id: $datasetId, graph_id: $graphId}) + -[:RELATED_TO *1..10]-> + (origin:Entity {graph_id: $graphId}) +WHERE ALL(r IN relationships(path) WHERE r.relation_type IN ['DERIVED_FROM', 'USES_DATASET', 'PRODUCES']) +RETURN path +``` + +### 2. 数据集影响分析 + +```cypher +// 查找修改某数据集后,所有受影响的下游实体 +MATCH (d:Entity {id: $datasetId, graph_id: $graphId}) + <-[:RELATED_TO {relation_type: 'USES_DATASET'}]- + (consumer:Entity {graph_id: $graphId}) +RETURN consumer.type AS entity_type, consumer.name AS entity_name, consumer.id AS entity_id +``` + +### 3. 用户工作看板 + +```cypher +// 查询用户相关的所有实体和关系 +MATCH (u:Entity {id: $userId, type: 'User', graph_id: $graphId}) +OPTIONAL MATCH (task:Entity)-[:RELATED_TO {relation_type: 'ASSIGNED_TO'}]->(u) +OPTIONAL MATCH (u)-[:RELATED_TO {relation_type: 'BELONGS_TO'}]->(org:Entity) +RETURN u, collect(DISTINCT task) AS tasks, collect(DISTINCT org) AS orgs +``` + +## 扩展说明 + +- **自定义关系类型**:除上述 10 类核心关系外,用户可通过 LLM 抽取或手动创建自定义关系类型。自定义关系使用相同的 `RELATED_TO` Neo4j 关系类型和公共属性结构,`relation_type` 字段可为任意字符串。 +- **双向关系**:所有关系均为单向。如果需要表达双向关系(如"A 和 B 互相影响"),应创建两条方向相反的关系。 +- **关系去重**:应用层应在创建关系前检查是否已存在相同的(source, target, relation_type)组合,避免重复。 diff --git a/docs/knowledge-graph/schema/schema.cypher b/docs/knowledge-graph/schema/schema.cypher new file mode 100644 index 0000000..d4e25cc --- /dev/null +++ b/docs/knowledge-graph/schema/schema.cypher @@ -0,0 +1,469 @@ +// ============================================================================= +// DataMate 知识图谱 - Neo4j Schema 初始化脚本 +// Schema 版本:1.0.0 +// 更新日期:2026-02-17 +// +// 使用方式: +// 1. 通过 Cypher Shell 执行: +// cat schema.cypher | cypher-shell -u neo4j -p +// 2. 或在 Neo4j Browser 中逐段执行 +// +// 注意: +// - 所有索引和约束使用 IF NOT EXISTS,可重复执行 +// - 约束自动创建对应索引,无需重复创建 +// - 关系属性索引需要 Neo4j Enterprise Edition,社区版使用属性内联匹配 +// ============================================================================= + +// ============================================================================= +// 第 1 部分:节点约束 +// ============================================================================= + +// Entity 节点 ID 唯一性约束(自动创建索引) +CREATE CONSTRAINT entity_id_unique IF NOT EXISTS +FOR (n:Entity) REQUIRE n.id IS UNIQUE; + +// ============================================================================= +// 第 2 部分:节点索引 +// ============================================================================= + +// graph_id 索引 —— 多租户隔离的核心索引,所有查询都会带上 graph_id +CREATE INDEX entity_graph_id IF NOT EXISTS +FOR (n:Entity) ON (n.graph_id); + +// type 索引 —— 按实体类型过滤 +CREATE INDEX entity_type IF NOT EXISTS +FOR (n:Entity) ON (n.type); + +// name 索引 —— 按名称搜索 +CREATE INDEX entity_name IF NOT EXISTS +FOR (n:Entity) ON (n.name); + +// source_id 索引 —— MySQL → Neo4j 同步时按源 ID 查找 +CREATE INDEX entity_source_id IF NOT EXISTS +FOR (n:Entity) ON (n.source_id); + +// 复合索引:(graph_id, type) —— 查询某图谱内指定类型的实体 +CREATE INDEX entity_graph_id_type IF NOT EXISTS +FOR (n:Entity) ON (n.graph_id, n.type); + +// 复合索引:(graph_id, id) —— 精确查找实体(最常用查询路径) +CREATE INDEX entity_graph_id_id IF NOT EXISTS +FOR (n:Entity) ON (n.graph_id, n.id); + +// 复合索引:(graph_id, source_id) —— 同步时按源 ID 查找 +CREATE INDEX entity_graph_id_source_id IF NOT EXISTS +FOR (n:Entity) ON (n.graph_id, n.source_id); + +// created_at 索引 —— 按创建时间排序 +CREATE INDEX entity_created_at IF NOT EXISTS +FOR (n:Entity) ON (n.created_at); + +// ============================================================================= +// 第 3 部分:全文索引(用于模糊搜索) +// ============================================================================= + +// Entity name + description 全文索引 +CREATE FULLTEXT INDEX entity_fulltext IF NOT EXISTS +FOR (n:Entity) ON EACH [n.name, n.description]; + +// ============================================================================= +// 第 3.1 部分:SyncHistory 约束和索引(同步元数据节点) +// ============================================================================= + +// (graph_id, sync_id) 唯一约束 —— 防止 syncId 碰撞产生重复记录 +CREATE CONSTRAINT sync_history_graph_sync_unique IF NOT EXISTS +FOR (h:SyncHistory) REQUIRE (h.graph_id, h.sync_id) IS UNIQUE; + +// (graph_id, started_at) 索引 —— 加速按时间范围查询同步历史 +CREATE INDEX sync_history_graph_started IF NOT EXISTS +FOR (h:SyncHistory) ON (h.graph_id, h.started_at); + +// (graph_id, status, started_at) 索引 —— 加速按状态+时间的过滤查询 +CREATE INDEX sync_history_graph_status_started IF NOT EXISTS +FOR (h:SyncHistory) ON (h.graph_id, h.status, h.started_at); + +// ============================================================================= +// 第 4 部分:关系属性说明 +// ============================================================================= + +// Neo4j 社区版不支持关系属性索引。 +// 所有关系查询通过节点索引定位后,在关系上使用属性内联匹配: +// -[r:RELATED_TO {graph_id: $graphId, relation_type: $type}]-> +// +// 如果使用 Neo4j Enterprise Edition,可取消以下注释创建关系索引: +// +// CREATE INDEX rel_graph_id IF NOT EXISTS +// FOR ()-[r:RELATED_TO]-() ON (r.graph_id); +// +// CREATE INDEX rel_relation_type IF NOT EXISTS +// FOR ()-[r:RELATED_TO]-() ON (r.relation_type); +// +// CREATE INDEX rel_id IF NOT EXISTS +// FOR ()-[r:RELATED_TO]-() ON (r.id); + +// ============================================================================= +// 第 5 部分:示例数据(可选,用于验证 Schema) +// ============================================================================= + +// 以下示例数据使用固定的 graph_id,用于开发和测试环境。 +// 生产环境中 graph_id 由应用层生成和管理。 + +// --- 创建示例组织 --- +CREATE (org:Entity { + id: '00000000-0000-0000-0000-000000000001', + name: '数据工程部', + type: 'Org', + description: '负责数据采集、清洗和标注', + graph_id: '11111111-1111-1111-1111-111111111111', + source_type: 'MANUAL', + confidence: 1.0, + properties_json: '{"org_code":"DE","level":1,"member_count":15}', + created_at: datetime() +}); + +// --- 创建示例用户 --- +CREATE (user:Entity { + id: '00000000-0000-0000-0000-000000000002', + name: '张三', + type: 'User', + graph_id: '11111111-1111-1111-1111-111111111111', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"username":"zhangsan","email":"zhangsan@example.com","role":"USER","enabled":true}', + created_at: datetime() +}); + +// --- 创建示例数据集(源) --- +CREATE (ds1:Entity { + id: '00000000-0000-0000-0000-000000000010', + name: '用户行为日志-原始', + type: 'Dataset', + description: '原始用户行为埋点数据', + graph_id: '11111111-1111-1111-1111-111111111111', + source_id: '100', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"dataset_type":"TEXT","status":"ACTIVE","category":"用户行为","format":"JSON","record_count":2000000,"size_bytes":3221225472}', + created_at: datetime() +}); + +// --- 创建示例数据集(清洗后) --- +CREATE (ds2:Entity { + id: '00000000-0000-0000-0000-000000000011', + name: '用户行为日志-清洗后', + type: 'Dataset', + description: '经过去重和格式标准化的用户行为数据', + graph_id: '11111111-1111-1111-1111-111111111111', + source_id: '101', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"dataset_type":"TEXT","status":"ACTIVE","category":"用户行为","format":"JSON","record_count":1500000,"size_bytes":2147483648,"version":1}', + created_at: datetime() +}); + +// --- 创建示例字段 --- +CREATE (f1:Entity { + id: '00000000-0000-0000-0000-000000000020', + name: 'user_id', + type: 'Field', + description: '用户唯一标识符', + graph_id: '11111111-1111-1111-1111-111111111111', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"data_type":"STRING","nullable":false,"is_primary_key":true}', + created_at: datetime() +}); + +CREATE (f2:Entity { + id: '00000000-0000-0000-0000-000000000021', + name: 'event_type', + type: 'Field', + description: '事件类型', + graph_id: '11111111-1111-1111-1111-111111111111', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"data_type":"STRING","nullable":false,"sample_values":["click","view","purchase"]}', + created_at: datetime() +}); + +CREATE (f3:Entity { + id: '00000000-0000-0000-0000-000000000022', + name: 'user_id', + type: 'Field', + description: '用户唯一标识符(清洗后)', + graph_id: '11111111-1111-1111-1111-111111111111', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"data_type":"STRING","nullable":false,"is_primary_key":true}', + created_at: datetime() +}); + +// --- 创建示例工作流 --- +CREATE (wf:Entity { + id: '00000000-0000-0000-0000-000000000030', + name: '文本去重清洗管道', + type: 'Workflow', + description: 'SimHash去重 + 格式标准化 + 空值过滤', + graph_id: '11111111-1111-1111-1111-111111111111', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"workflow_type":"CLEANING","status":"ACTIVE","version":"1.0","operator_count":3}', + created_at: datetime() +}); + +// --- 创建示例作业 --- +CREATE (job:Entity { + id: '00000000-0000-0000-0000-000000000040', + name: '清洗作业-20260215-001', + type: 'Job', + description: '用户行为日志去重清洗', + graph_id: '11111111-1111-1111-1111-111111111111', + source_id: '500', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"job_type":"CLEANING","status":"COMPLETED","started_at":"2026-02-15T10:00:00","completed_at":"2026-02-15T10:35:00","duration_seconds":2100,"input_count":2000000,"output_count":1500000}', + created_at: datetime() +}); + +// --- 创建示例标注任务 --- +CREATE (lt:Entity { + id: '00000000-0000-0000-0000-000000000050', + name: '情感分析标注-批次1', + type: 'LabelTask', + description: '用户评论情感标注(正面/负面/中性)', + graph_id: '11111111-1111-1111-1111-111111111111', + source_id: '600', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"task_mode":"MANUAL","data_type":"text","labeling_type":"sentiment_analysis","status":"IN_PROGRESS","progress":30.0,"template_name":"情感分析"}', + created_at: datetime() +}); + +// --- 创建示例知识集 --- +CREATE (ks:Entity { + id: '00000000-0000-0000-0000-000000000060', + name: '用户行为分析知识库', + type: 'KnowledgeSet', + description: '从用户行为数据中提取的业务规则和洞察', + graph_id: '11111111-1111-1111-1111-111111111111', + source_type: 'SYNC', + confidence: 1.0, + properties_json: '{"status":"PUBLISHED","domain":"用户行为","business_line":"数据分析","sensitivity":"INTERNAL","item_count":85}', + created_at: datetime() +}); + +// ============================================================================= +// 第 6 部分:示例关系 +// ============================================================================= + +// HAS_FIELD:源数据集 → 字段 +MATCH (ds1:Entity {id: '00000000-0000-0000-0000-000000000010'}) +MATCH (f1:Entity {id: '00000000-0000-0000-0000-000000000020'}) +CREATE (ds1)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000001', + relation_type: 'HAS_FIELD', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"ordinal":0,"required":true}', + created_at: datetime() +}]->(f1); + +MATCH (ds1:Entity {id: '00000000-0000-0000-0000-000000000010'}) +MATCH (f2:Entity {id: '00000000-0000-0000-0000-000000000021'}) +CREATE (ds1)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000002', + relation_type: 'HAS_FIELD', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"ordinal":1,"required":true}', + created_at: datetime() +}]->(f2); + +// HAS_FIELD:清洗后数据集 → 字段 +MATCH (ds2:Entity {id: '00000000-0000-0000-0000-000000000011'}) +MATCH (f3:Entity {id: '00000000-0000-0000-0000-000000000022'}) +CREATE (ds2)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000003', + relation_type: 'HAS_FIELD', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"ordinal":0,"required":true}', + created_at: datetime() +}]->(f3); + +// DERIVED_FROM:清洗后数据集 → 源数据集 +MATCH (ds2:Entity {id: '00000000-0000-0000-0000-000000000011'}) +MATCH (ds1:Entity {id: '00000000-0000-0000-0000-000000000010'}) +CREATE (ds2)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000004', + relation_type: 'DERIVED_FROM', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"derivation_type":"CLEANING","job_id":"00000000-0000-0000-0000-000000000040","transformation":"SimHash去重 + 空值过滤"}', + created_at: datetime() +}]->(ds1); + +// TRIGGERS:工作流 → 作业 +MATCH (wf:Entity {id: '00000000-0000-0000-0000-000000000030'}) +MATCH (job:Entity {id: '00000000-0000-0000-0000-000000000040'}) +CREATE (wf)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000005', + relation_type: 'TRIGGERS', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"trigger_type":"MANUAL","triggered_at":"2026-02-15T10:00:00"}', + created_at: datetime() +}]->(job); + +// USES_DATASET:作业 → 源数据集 +MATCH (job:Entity {id: '00000000-0000-0000-0000-000000000040'}) +MATCH (ds1:Entity {id: '00000000-0000-0000-0000-000000000010'}) +CREATE (job)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000006', + relation_type: 'USES_DATASET', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"usage_role":"INPUT"}', + created_at: datetime() +}]->(ds1); + +// PRODUCES:作业 → 清洗后数据集 +MATCH (job:Entity {id: '00000000-0000-0000-0000-000000000040'}) +MATCH (ds2:Entity {id: '00000000-0000-0000-0000-000000000011'}) +CREATE (job)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000007', + relation_type: 'PRODUCES', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"output_type":"PRIMARY"}', + created_at: datetime() +}]->(ds2); + +// ASSIGNED_TO:标注任务 → 用户 +MATCH (lt:Entity {id: '00000000-0000-0000-0000-000000000050'}) +MATCH (user:Entity {id: '00000000-0000-0000-0000-000000000002'}) +CREATE (lt)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000008', + relation_type: 'ASSIGNED_TO', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"assigned_at":"2026-02-14T09:00:00","role":"EXECUTOR"}', + created_at: datetime() +}]->(user); + +// USES_DATASET:标注任务 → 清洗后数据集 +MATCH (lt:Entity {id: '00000000-0000-0000-0000-000000000050'}) +MATCH (ds2:Entity {id: '00000000-0000-0000-0000-000000000011'}) +CREATE (lt)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000009', + relation_type: 'USES_DATASET', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"usage_role":"INPUT"}', + created_at: datetime() +}]->(ds2); + +// BELONGS_TO:用户 → 组织 +MATCH (user:Entity {id: '00000000-0000-0000-0000-000000000002'}) +MATCH (org:Entity {id: '00000000-0000-0000-0000-000000000001'}) +CREATE (user)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000010', + relation_type: 'BELONGS_TO', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"membership_type":"PRIMARY","since":"2025-03-01T00:00:00"}', + created_at: datetime() +}]->(org); + +// BELONGS_TO:源数据集 → 组织 +MATCH (ds1:Entity {id: '00000000-0000-0000-0000-000000000010'}) +MATCH (org:Entity {id: '00000000-0000-0000-0000-000000000001'}) +CREATE (ds1)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000011', + relation_type: 'BELONGS_TO', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 1.0, + source_id: '', + properties_json: '{"membership_type":"PRIMARY"}', + created_at: datetime() +}]->(org); + +// IMPACTS:源字段 → 清洗后字段 +MATCH (f1:Entity {id: '00000000-0000-0000-0000-000000000020'}) +MATCH (f3:Entity {id: '00000000-0000-0000-0000-000000000022'}) +CREATE (f1)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000012', + relation_type: 'IMPACTS', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 0.95, + source_id: '', + properties_json: '{"impact_type":"DIRECT","job_id":"00000000-0000-0000-0000-000000000040"}', + created_at: datetime() +}]->(f3); + +// SOURCED_FROM:知识集 → 清洗后数据集 +MATCH (ks:Entity {id: '00000000-0000-0000-0000-000000000060'}) +MATCH (ds2:Entity {id: '00000000-0000-0000-0000-000000000011'}) +CREATE (ks)-[:RELATED_TO { + id: '00000000-0000-0000-0000-100000000013', + relation_type: 'SOURCED_FROM', + graph_id: '11111111-1111-1111-1111-111111111111', + weight: 1.0, + confidence: 0.85, + source_id: '', + properties_json: '{"extraction_method":"LLM","extracted_at":"2026-02-16T14:30:00","item_count":85}', + created_at: datetime() +}]->(ds2); + +// ============================================================================= +// 第 7 部分:验证查询 +// ============================================================================= + +// 验证节点数量 +// MATCH (n:Entity {graph_id: '11111111-1111-1111-1111-111111111111'}) +// RETURN n.type AS type, count(*) AS count +// ORDER BY count DESC; + +// 验证关系数量 +// MATCH (:Entity {graph_id: '11111111-1111-1111-1111-111111111111'}) +// -[r:RELATED_TO {graph_id: '11111111-1111-1111-1111-111111111111'}]-> +// (:Entity {graph_id: '11111111-1111-1111-1111-111111111111'}) +// RETURN r.relation_type AS type, count(*) AS count +// ORDER BY count DESC; + +// 验证端到端血缘 +// MATCH path = (ds2:Entity {name: '用户行为日志-清洗后'}) +// -[:RELATED_TO *1..5]-> +// (origin:Entity) +// WHERE ALL(r IN relationships(path) WHERE r.graph_id = '11111111-1111-1111-1111-111111111111') +// RETURN path; + +// ============================================================================= +// 第 8 部分:清理示例数据(可选) +// ============================================================================= + +// 如需清理示例数据,执行以下语句: +// MATCH (n:Entity {graph_id: '11111111-1111-1111-1111-111111111111'}) +// DETACH DELETE n; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0091f86..49442db 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "edatamate", "version": "0.0.0", "dependencies": { + "@antv/g6": "^5.0.0", "@reduxjs/toolkit": "^2.11.0", "@xyflow/react": "^12.8.3", "antd": "^5.27.0", @@ -157,6 +158,241 @@ "react": ">=16.9.0" } }, + "node_modules/@antv/algorithm": { + "version": "0.1.26", + "resolved": "https://registry.npmmirror.com/@antv/algorithm/-/algorithm-0.1.26.tgz", + "integrity": "sha512-DVhcFSQ8YQnMNW34Mk8BSsfc61iC1sAnmcfYoXTAshYHuU50p/6b7x3QYaGctDNKWGvi1ub7mPcSY0bK+aN0qg==", + "license": "MIT", + "dependencies": { + "@antv/util": "^2.0.13", + "tslib": "^2.0.0" + } + }, + "node_modules/@antv/algorithm/node_modules/@antv/util": { + "version": "2.0.17", + "resolved": "https://registry.npmmirror.com/@antv/util/-/util-2.0.17.tgz", + "integrity": "sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q==", + "license": "ISC", + "dependencies": { + "csstype": "^3.0.8", + "tslib": "^2.0.3" + } + }, + "node_modules/@antv/component": { + "version": "2.1.11", + "resolved": "https://registry.npmmirror.com/@antv/component/-/component-2.1.11.tgz", + "integrity": "sha512-dTdz8VAd3rpjOaGEZTluz82mtzrP4XCtNlNQyrxY7VNRNcjtvpTLDn57bUL2lRu1T+iklKvgbE2llMriWkq9vQ==", + "license": "MIT", + "dependencies": { + "@antv/g": "^6.1.11", + "@antv/scale": "^0.4.16", + "@antv/util": "^3.3.10", + "svg-path-parser": "^1.1.0" + } + }, + "node_modules/@antv/event-emitter": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/@antv/event-emitter/-/event-emitter-0.1.3.tgz", + "integrity": "sha512-4ddpsiHN9Pd4UIlWuKVK1C4IiZIdbwQvy9i7DUSI3xNJ89FPUFt8lxDYj8GzzfdllV0NkJTRxnG+FvLk0llidg==", + "license": "MIT" + }, + "node_modules/@antv/g": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/@antv/g/-/g-6.3.1.tgz", + "integrity": "sha512-WYEKqy86LHB2PzTmrZXrIsIe+3Epeds2f68zceQ+BJtRoGki7Sy4IhlC8LrUMztgfT1t3d/0L745NWZwITroKA==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.7.0", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "html2canvas": "^1.4.1" + } + }, + "node_modules/@antv/g-canvas": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@antv/g-canvas/-/g-canvas-2.2.0.tgz", + "integrity": "sha512-h7zVBBo2aO64DuGKvq9sG+yTU3sCUb9DALCVm7nz8qGPs8hhLuFOkKPEzUDNfNYZGJUGzY8UDtJ3QRGRFcvEQg==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.7.0", + "@antv/g-math": "3.1.0", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-lite": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/@antv/g-lite/-/g-lite-2.7.0.tgz", + "integrity": "sha512-uSzgHYa5bwR5L2Au7/5tsOhFmXKZKLPBH90+Q9bP9teVs5VT4kOAi0isPSpDI8uhdDC2/VrfTWu5K9HhWI6FWw==", + "license": "MIT", + "dependencies": { + "@antv/g-math": "3.1.0", + "@antv/util": "^3.3.5", + "@antv/vendor": "^1.0.3", + "@babel/runtime": "^7.25.6", + "eventemitter3": "^5.0.1", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-lite/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/@antv/g-math": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@antv/g-math/-/g-math-3.1.0.tgz", + "integrity": "sha512-DtN1Gj/yI0UiK18nSBsZX8RK0LszGwqfb+cBYWgE+ddyTm8dZnW4tPUhV7QXePsS6/A5hHC+JFpAAK7OEGo5ZQ==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-dragndrop": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/@antv/g-plugin-dragndrop/-/g-plugin-dragndrop-2.1.1.tgz", + "integrity": "sha512-+aesDUJVQDs6UJ2bOBbDlaGAPCfHmU0MbrMTlQlfpwNplWueqtgVAZ3L57oZ2ZGHRWUHiRwZGPjXMBM3O2LELw==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.7.0", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g6": { + "version": "5.0.51", + "resolved": "https://registry.npmmirror.com/@antv/g6/-/g6-5.0.51.tgz", + "integrity": "sha512-/88LJDZ7FHKtpyJibXOnJWZ8gFRp32mLb8KzEFrMuiIC/dsZgTf/oYVw6L4tLKooPXfXqUtrJb2tWFMGR04EMg==", + "license": "MIT", + "dependencies": { + "@antv/algorithm": "^0.1.26", + "@antv/component": "^2.1.7", + "@antv/event-emitter": "^0.1.3", + "@antv/g": "^6.1.28", + "@antv/g-canvas": "^2.0.48", + "@antv/g-plugin-dragndrop": "^2.0.38", + "@antv/graphlib": "^2.0.4", + "@antv/hierarchy": "^0.7.1", + "@antv/layout": "1.2.14-beta.9", + "@antv/util": "^3.3.11", + "bubblesets-js": "^2.3.4" + } + }, + "node_modules/@antv/graphlib": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/@antv/graphlib/-/graphlib-2.0.4.tgz", + "integrity": "sha512-zc/5oQlsdk42Z0ib1mGklwzhJ5vczLFiPa1v7DgJkTbgJ2YxRh9xdarf86zI49sKVJmgbweRpJs7Nu5bIiwv4w==", + "license": "MIT", + "dependencies": { + "@antv/event-emitter": "^0.1.3" + } + }, + "node_modules/@antv/hierarchy": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/@antv/hierarchy/-/hierarchy-0.7.1.tgz", + "integrity": "sha512-7r22r+HxfcRZp79ZjGmsn97zgC1Iajrv0Mm9DIgx3lPfk+Kme2MG/+EKdZj1iEBsN0rJRzjWVPGL5YrBdVHchw==", + "license": "MIT" + }, + "node_modules/@antv/layout": { + "version": "1.2.14-beta.9", + "resolved": "https://registry.npmmirror.com/@antv/layout/-/layout-1.2.14-beta.9.tgz", + "integrity": "sha512-wPlwBFMtq2lWZFc89/7Lzb8fjHnyKVZZ9zBb2h+zZIP0YWmVmHRE8+dqCiPKOyOGUXEdDtn813f1g107dCHZlg==", + "license": "MIT", + "dependencies": { + "@antv/event-emitter": "^0.1.3", + "@antv/graphlib": "^2.0.0", + "@antv/util": "^3.3.2", + "@naoak/workerize-transferable": "^0.1.0", + "comlink": "^4.4.1", + "d3-force": "^3.0.0", + "d3-force-3d": "^3.0.5", + "d3-octree": "^1.0.2", + "d3-quadtree": "^3.0.1", + "dagre": "^0.8.5", + "ml-matrix": "^6.10.4", + "tslib": "^2.5.0" + } + }, + "node_modules/@antv/scale": { + "version": "0.4.16", + "resolved": "https://registry.npmmirror.com/@antv/scale/-/scale-0.4.16.tgz", + "integrity": "sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.7", + "color-string": "^1.5.5", + "fecha": "^4.2.1" + } + }, + "node_modules/@antv/util": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/@antv/util/-/util-3.3.11.tgz", + "integrity": "sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "gl-matrix": "^3.3.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@antv/vendor": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@antv/vendor/-/vendor-1.0.11.tgz", + "integrity": "sha512-LmhPEQ+aapk3barntaiIxJ5VHno/Tyab2JnfdcPzp5xONh/8VSfed4bo/9xKo5HcUAEydko38vYLfj6lJliLiw==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.2.1", + "@types/d3-color": "^3.1.3", + "@types/d3-dispatch": "^3.0.6", + "@types/d3-dsv": "^3.0.7", + "@types/d3-ease": "^3.0.2", + "@types/d3-fetch": "^3.0.7", + "@types/d3-force": "^3.0.10", + "@types/d3-format": "^3.0.4", + "@types/d3-geo": "^3.1.0", + "@types/d3-hierarchy": "^3.1.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-path": "^3.1.0", + "@types/d3-quadtree": "^3.0.6", + "@types/d3-random": "^3.0.3", + "@types/d3-scale": "^4.0.9", + "@types/d3-scale-chromatic": "^3.1.0", + "@types/d3-shape": "^3.1.7", + "@types/d3-time": "^3.0.4", + "@types/d3-timer": "^3.0.2", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-dispatch": "^3.0.1", + "d3-dsv": "^3.0.1", + "d3-ease": "^3.0.1", + "d3-fetch": "^3.0.1", + "d3-force": "^3.0.0", + "d3-force-3d": "^3.0.5", + "d3-format": "^3.1.0", + "d3-geo": "^3.1.1", + "d3-geo-projection": "^4.0.0", + "d3-hierarchy": "^3.1.2", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.1.0", + "d3-quadtree": "^3.0.1", + "d3-random": "^3.0.1", + "d3-regression": "^1.3.10", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "d3-shape": "^3.2.0", + "d3-time": "^3.1.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1139,7 +1375,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1161,30 +1396,47 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.30", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@naoak/workerize-transferable": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/@naoak/workerize-transferable/-/workerize-transferable-0.1.0.tgz", + "integrity": "sha512-fDLfuP71IPNP5+zSfxFb52OHgtjZvauRJWbVnpzQ7G7BjcbLjTny0OW1d3ZO806XKpLWNKmeeW3MhE0sy8iwYQ==", + "license": "MIT", + "peerDependencies": { + "workerize-loader": "*" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2031,6 +2283,12 @@ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "license": "MIT" }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, "node_modules/@types/d3-drag": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", @@ -2040,12 +2298,54 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "license": "MIT" }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmmirror.com/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", @@ -2061,6 +2361,18 @@ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", "license": "MIT" }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, "node_modules/@types/d3-scale": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", @@ -2070,6 +2382,12 @@ "@types/d3-time": "*" } }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, "node_modules/@types/d3-selection": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", @@ -2116,25 +2434,50 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmmirror.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "24.2.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz", "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.10.0" @@ -2466,6 +2809,181 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/@xyflow/react": { "version": "12.8.3", "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.3.tgz", @@ -2516,7 +3034,6 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2525,6 +3042,19 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -2552,6 +3082,48 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT", + "peer": true + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2681,16 +3253,34 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.9.11", "resolved": "https://mirrors.huaweicloud.com/repository/npm/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", - "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2760,7 +3350,6 @@ "version": "4.28.1", "resolved": "https://mirrors.huaweicloud.com/repository/npm/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2790,6 +3379,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bubblesets-js": { + "version": "2.3.4", + "resolved": "https://registry.npmmirror.com/bubblesets-js/-/bubblesets-js-2.3.4.tgz", + "integrity": "sha512-DyMjHmpkS2+xcFNtyN00apJYL3ESdp9fTrkDr5+9Qg/GPqFmcWgGsK1akZnttE1XFxJ/VMy4DNNGMGYtmFp1Sg==", + "license": "MIT" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT", + "peer": true + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2845,7 +3447,6 @@ "version": "1.0.30001762", "resolved": "https://mirrors.huaweicloud.com/repository/npm/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2927,6 +3528,16 @@ "node": ">=18" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/classcat": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", @@ -2965,9 +3576,24 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/comlink": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/comlink/-/comlink-4.4.2.tgz", + "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", + "license": "Apache-2.0" + }, "node_modules/commander": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", @@ -3064,6 +3690,15 @@ "node": ">= 8" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -3082,6 +3717,12 @@ "node": ">=12" } }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -3113,6 +3754,40 @@ "node": ">=12" } }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", @@ -3122,6 +3797,48 @@ "node": ">=12" } }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", @@ -3131,6 +3848,57 @@ "node": ">=12" } }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo-projection": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", + "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", + "license": "ISC", + "dependencies": { + "commander": "7", + "d3-array": "1 - 3", + "d3-geo": "1.12.0 - 3" + }, + "bin": { + "geo2svg": "bin/geo2svg.js", + "geograticule": "bin/geograticule.js", + "geoproject": "bin/geoproject.js", + "geoquantize": "bin/geoquantize.js", + "geostitch": "bin/geostitch.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo-projection/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-interpolate": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", @@ -3143,6 +3911,12 @@ "node": ">=12" } }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -3152,6 +3926,30 @@ "node": ">=12" } }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-regression": { + "version": "1.3.10", + "resolved": "https://registry.npmmirror.com/d3-regression/-/d3-regression-1.3.10.tgz", + "integrity": "sha512-PF8GWEL70cHHWpx2jUQXc68r1pyPHIA+St16muk/XRokETzlegj5LriNKg7o4LR0TySug4nHYPJNNRz/W+/Niw==", + "license": "BSD-3-Clause" + }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -3168,6 +3966,19 @@ "node": ">=12" } }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-selection": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", @@ -3257,6 +4068,16 @@ "node": ">=12" } }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmmirror.com/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "license": "MIT", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -3350,9 +4171,18 @@ "version": "1.5.267", "resolved": "https://mirrors.huaweicloud.com/repository/npm/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", - "dev": true, "license": "ISC" }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -3364,14 +4194,13 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "dev": true, + "version": "5.19.0", + "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -3397,6 +4226,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "license": "MIT", + "peer": true + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -3456,7 +4292,6 @@ "version": "3.2.0", "resolved": "https://mirrors.huaweicloud.com/repository/npm/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3631,7 +4466,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -3644,7 +4478,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -3676,6 +4509,16 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -3787,7 +4630,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-equals": { @@ -3843,6 +4685,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -3853,6 +4712,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4044,6 +4909,12 @@ "node": ">= 0.4" } }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmmirror.com/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4057,6 +4928,13 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause", + "peer": true + }, "node_modules/globals": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", @@ -4087,7 +4965,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -4097,11 +4974,19 @@ "dev": true, "license": "MIT" }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmmirror.com/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4133,6 +5018,19 @@ "node": ">= 0.4" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -4164,7 +5062,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -4253,6 +5150,18 @@ "node": ">= 0.10" } }, + "node_modules/is-any-array": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-any-array/-/is-any-array-2.0.1.tgz", + "integrity": "sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==", + "license": "MIT" + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4320,6 +5229,37 @@ "dev": true, "license": "ISC" }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/jiti": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", @@ -4369,6 +5309,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT", + "peer": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4396,7 +5343,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -4703,6 +5649,35 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4806,6 +5781,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT", + "peer": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4922,6 +5904,45 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/ml-array-max": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/ml-array-max/-/ml-array-max-1.2.4.tgz", + "integrity": "sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0" + } + }, + "node_modules/ml-array-min": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/ml-array-min/-/ml-array-min-1.2.3.tgz", + "integrity": "sha512-VcZ5f3VZ1iihtrGvgfh/q0XlMobG6GQ8FsNyQXD3T+IlstDv85g8kfV0xUG1QPRO/t21aukaJowDzMTc7j5V6Q==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0" + } + }, + "node_modules/ml-array-rescale": { + "version": "1.3.7", + "resolved": "https://registry.npmmirror.com/ml-array-rescale/-/ml-array-rescale-1.3.7.tgz", + "integrity": "sha512-48NGChTouvEo9KBctDfHC3udWnQKNKEWN0ziELvY3KG25GR5cA8K8wNVzracsqSW1QEkAXjTNx+ycgAv06/1mQ==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0", + "ml-array-max": "^1.2.4", + "ml-array-min": "^1.2.3" + } + }, + "node_modules/ml-matrix": { + "version": "6.12.1", + "resolved": "https://registry.npmmirror.com/ml-matrix/-/ml-matrix-6.12.1.tgz", + "integrity": "sha512-TJ+8eOFdp+INvzR4zAuwBQJznDUfktMtOB6g/hUcGh3rcyjxbz4Te57Pgri8Q9bhSQ7Zys4IYOGhFdnlgeB6Lw==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.1", + "ml-array-rescale": "^1.3.7" + } + }, "node_modules/mockjs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mockjs/-/mockjs-1.1.0.tgz", @@ -4977,11 +5998,17 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT", + "peer": true + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://mirrors.huaweicloud.com/repository/npm/node-releases/-/node-releases-2.0.27.tgz", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, "license": "MIT" }, "node_modules/nodemon": { @@ -5222,7 +6249,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -5372,6 +6398,16 @@ "node": ">= 0.8" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -6198,6 +7234,16 @@ "redux": "^5.0.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", @@ -6322,11 +7368,16 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -6347,7 +7398,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/scheduler": { @@ -6359,6 +7409,63 @@ "loose-envify": "^1.1.0" } }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT", + "peer": true + }, "node_modules/scroll-into-view-if-needed": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", @@ -6401,6 +7508,16 @@ "node": ">= 18" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/serve-static": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", @@ -6589,6 +7706,15 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -6615,6 +7741,16 @@ "node": ">=10" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6625,6 +7761,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -6673,6 +7820,12 @@ "node": ">=8" } }, + "node_modules/svg-path-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/svg-path-parser/-/svg-path-parser-1.1.0.tgz", + "integrity": "sha512-jGCUqcQyXpfe38R7RFfhrMyfXcBmpMNJI/B+4CE9/Unkh98UporAc461GTthv+TVDuZXsBx7/WiwJb1Oh4tt4A==", + "license": "MIT" + }, "node_modules/tailwindcss": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", @@ -6681,13 +7834,16 @@ "license": "MIT" }, "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "dev": true, + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/tar": { @@ -6718,6 +7874,76 @@ "node": ">=18" } }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmmirror.com/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.16", + "resolved": "https://registry.npmmirror.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT", + "peer": true + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/throttle-debounce": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", @@ -6830,6 +8056,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6930,7 +8162,6 @@ "version": "7.10.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", - "dev": true, "license": "MIT" }, "node_modules/universalify": { @@ -6957,7 +8188,6 @@ "version": "1.2.3", "resolved": "https://mirrors.huaweicloud.com/repository/npm/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, "funding": [ { "type": "opencollective", @@ -7003,6 +8233,15 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -7138,6 +8377,126 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "license": "MIT", + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.105.2", + "resolved": "https://registry.npmmirror.com/webpack/-/webpack-5.105.2.tgz", + "integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7164,6 +8523,19 @@ "node": ">=0.10.0" } }, + "node_modules/workerize-loader": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/workerize-loader/-/workerize-loader-2.0.2.tgz", + "integrity": "sha512-HoZ6XY4sHWxA2w0WpzgBwUiR3dv1oo7bS+oCwIpb6n54MclQ/7KXdXsVIChTCygyuHtVuGBO1+i3HzTt699UJQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loader-utils": "^2.0.0" + }, + "peerDependencies": { + "webpack": "*" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5d44f3b..6ba0870 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,7 +20,8 @@ "react-dom": "^18.1.1", "react-redux": "^9.2.0", "react-router": "^7.8.0", - "recharts": "2.15.0" + "recharts": "2.15.0", + "@antv/g6": "^5.0.0" }, "devDependencies": { "@eslint/js": "^9.33.0", diff --git a/frontend/src/auth/permissions.ts b/frontend/src/auth/permissions.ts index 3340f88..98675eb 100644 --- a/frontend/src/auth/permissions.ts +++ b/frontend/src/auth/permissions.ts @@ -22,6 +22,8 @@ export const PermissionCodes = { taskCoordinationAssign: "module:task-coordination:assign", contentGenerationUse: "module:content-generation:use", agentUse: "module:agent:use", + knowledgeGraphRead: "module:knowledge-graph:read", + knowledgeGraphWrite: "module:knowledge-graph:write", userManage: "system:user:manage", roleManage: "system:role:manage", permissionManage: "system:permission:manage", @@ -39,6 +41,7 @@ const routePermissionRules: Array<{ prefix: string; permission: string }> = [ { prefix: "/data/orchestration", permission: PermissionCodes.orchestrationRead }, { prefix: "/data/task-coordination", permission: PermissionCodes.taskCoordinationRead }, { prefix: "/data/content-generation", permission: PermissionCodes.contentGenerationUse }, + { prefix: "/data/knowledge-graph", permission: PermissionCodes.knowledgeGraphRead }, { prefix: "/chat", permission: PermissionCodes.agentUse }, ]; diff --git a/frontend/src/pages/KnowledgeGraph/Home/KnowledgeGraphPage.tsx b/frontend/src/pages/KnowledgeGraph/Home/KnowledgeGraphPage.tsx new file mode 100644 index 0000000..edf8056 --- /dev/null +++ b/frontend/src/pages/KnowledgeGraph/Home/KnowledgeGraphPage.tsx @@ -0,0 +1,274 @@ +import { useState, useCallback, useEffect } from "react"; +import { Card, Input, Select, Button, Tag, Space, Empty, Tabs, message } from "antd"; +import { Network, RotateCcw } from "lucide-react"; +import { useSearchParams } from "react-router"; +import GraphCanvas from "../components/GraphCanvas"; +import SearchPanel from "../components/SearchPanel"; +import QueryBuilder from "../components/QueryBuilder"; +import NodeDetail from "../components/NodeDetail"; +import RelationDetail from "../components/RelationDetail"; +import useGraphData from "../hooks/useGraphData"; +import useGraphLayout, { LAYOUT_OPTIONS } from "../hooks/useGraphLayout"; +import { + ENTITY_TYPE_COLORS, + DEFAULT_ENTITY_COLOR, + ENTITY_TYPE_LABELS, +} from "../knowledge-graph.const"; + +const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + +export default function KnowledgeGraphPage() { + const [params, setParams] = useSearchParams(); + const [graphId, setGraphId] = useState(() => params.get("graphId") ?? ""); + const [graphIdInput, setGraphIdInput] = useState(() => params.get("graphId") ?? ""); + + const { + graphData, + loading, + searchResults, + searchLoading, + highlightedNodeIds, + loadInitialData, + expandNode, + searchEntities, + mergePathData, + clearGraph, + clearSearch, + } = useGraphData(); + + const { layoutType, setLayoutType } = useGraphLayout(); + + // Detail panel state + const [selectedNodeId, setSelectedNodeId] = useState(null); + const [selectedEdgeId, setSelectedEdgeId] = useState(null); + const [nodeDetailOpen, setNodeDetailOpen] = useState(false); + const [relationDetailOpen, setRelationDetailOpen] = useState(false); + + // Load graph when graphId changes + useEffect(() => { + if (graphId && UUID_REGEX.test(graphId)) { + clearGraph(); + loadInitialData(graphId); + } + }, [graphId, loadInitialData, clearGraph]); + + const handleLoadGraph = useCallback(() => { + if (!UUID_REGEX.test(graphIdInput)) { + message.warning("请输入有效的图谱 ID(UUID 格式)"); + return; + } + setGraphId(graphIdInput); + setParams({ graphId: graphIdInput }); + }, [graphIdInput, setParams]); + + const handleNodeClick = useCallback((nodeId: string) => { + setSelectedNodeId(nodeId); + setSelectedEdgeId(null); + setNodeDetailOpen(true); + setRelationDetailOpen(false); + }, []); + + const handleEdgeClick = useCallback((edgeId: string) => { + setSelectedEdgeId(edgeId); + setSelectedNodeId(null); + setRelationDetailOpen(true); + setNodeDetailOpen(false); + }, []); + + const handleNodeDoubleClick = useCallback( + (nodeId: string) => { + if (!graphId) return; + expandNode(graphId, nodeId); + }, + [graphId, expandNode] + ); + + const handleCanvasClick = useCallback(() => { + setSelectedNodeId(null); + setSelectedEdgeId(null); + setNodeDetailOpen(false); + setRelationDetailOpen(false); + }, []); + + const handleExpandNode = useCallback( + (entityId: string) => { + if (!graphId) return; + expandNode(graphId, entityId); + }, + [graphId, expandNode] + ); + + const handleEntityNavigate = useCallback( + (entityId: string) => { + setSelectedNodeId(entityId); + setNodeDetailOpen(true); + setRelationDetailOpen(false); + }, + [] + ); + + const handleSearchResultClick = useCallback( + (entityId: string) => { + handleNodeClick(entityId); + // If the entity is not in the current graph, expand it + if (!graphData.nodes.find((n) => n.id === entityId) && graphId) { + expandNode(graphId, entityId); + } + }, + [handleNodeClick, graphData.nodes, graphId, expandNode] + ); + + const handleRelationClick = useCallback((relationId: string) => { + setSelectedEdgeId(relationId); + setRelationDetailOpen(true); + setNodeDetailOpen(false); + }, []); + + const hasGraph = graphId && UUID_REGEX.test(graphId); + const nodeCount = graphData.nodes.length; + const edgeCount = graphData.edges.length; + + // Collect unique entity types in current graph for legend + const entityTypes = [...new Set(graphData.nodes.map((n) => n.data.type))].sort(); + + return ( +
+ {/* Header */} +
+

+ + 知识图谱浏览器 +

+
+ + {/* Graph ID Input + Controls */} +
+ + setGraphIdInput(e.target.value)} + onPressEnter={handleLoadGraph} + allowClear + /> + + + + + + setSourceId(e.target.value)} + allowClear + /> + + setTargetId(e.target.value)} + allowClear + /> + +
+ 最大深度 + setMaxDepth(v ?? 5)} + size="small" + className="flex-1" + /> +
+ + {queryType === "all-paths" && ( +
+ 最大路径数 + setMaxPaths(v ?? 3)} + size="small" + className="flex-1" + /> +
+ )} + +
+ + +
+ + + {pathResults.length > 0 ? ( + ( + +
+
+ 路径 {index + 1}({path.pathLength} 跳) +
+
+ {path.nodes.map((node, ni) => ( + + {ni > 0 && ( + + {path.edges[ni - 1] + ? RELATION_TYPE_LABELS[path.edges[ni - 1].relationType] ?? + path.edges[ni - 1].relationType + : "→"} + + )} + + {ENTITY_TYPE_LABELS[node.type] ?? node.type} + + {node.name} + + ))} +
+
+
+ )} + /> + ) : !loading && sourceId && targetId ? ( + + ) : null} +
+
+ ); +} diff --git a/frontend/src/pages/KnowledgeGraph/components/RelationDetail.tsx b/frontend/src/pages/KnowledgeGraph/components/RelationDetail.tsx new file mode 100644 index 0000000..f8c5e78 --- /dev/null +++ b/frontend/src/pages/KnowledgeGraph/components/RelationDetail.tsx @@ -0,0 +1,122 @@ +import { useEffect, useState } from "react"; +import { Drawer, Descriptions, Tag, Spin, Empty, message } from "antd"; +import type { RelationVO } from "../knowledge-graph.model"; +import { + ENTITY_TYPE_LABELS, + ENTITY_TYPE_COLORS, + DEFAULT_ENTITY_COLOR, + RELATION_TYPE_LABELS, +} from "../knowledge-graph.const"; +import * as api from "../knowledge-graph.api"; + +interface RelationDetailProps { + graphId: string; + relationId: string | null; + open: boolean; + onClose: () => void; + onEntityNavigate: (entityId: string) => void; +} + +export default function RelationDetail({ + graphId, + relationId, + open, + onClose, + onEntityNavigate, +}: RelationDetailProps) { + const [relation, setRelation] = useState(null); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (!relationId || !graphId) { + setRelation(null); + return; + } + if (!open) return; + + setLoading(true); + api + .getRelation(graphId, relationId) + .then((data) => setRelation(data)) + .catch(() => message.error("加载关系详情失败")) + .finally(() => setLoading(false)); + }, [graphId, relationId, open]); + + return ( + + + {relation ? ( +
+ + + + {RELATION_TYPE_LABELS[relation.relationType] ?? relation.relationType} + + + +
+ + {ENTITY_TYPE_LABELS[relation.sourceEntityType] ?? relation.sourceEntityType} + + onEntityNavigate(relation.sourceEntityId)} + > + {relation.sourceEntityName} + +
+
+ +
+ + {ENTITY_TYPE_LABELS[relation.targetEntityType] ?? relation.targetEntityType} + + onEntityNavigate(relation.targetEntityId)} + > + {relation.targetEntityName} + +
+
+ {relation.weight != null && ( + {relation.weight} + )} + {relation.confidence != null && ( + + {(relation.confidence * 100).toFixed(0)}% + + )} + {relation.createdAt && ( + {relation.createdAt} + )} +
+ + {relation.properties && Object.keys(relation.properties).length > 0 && ( + <> +

扩展属性

+ + {Object.entries(relation.properties).map(([key, value]) => ( + + {String(value)} + + ))} + + + )} +
+ ) : !loading ? ( + + ) : null} +
+
+ ); +} diff --git a/frontend/src/pages/KnowledgeGraph/components/SearchPanel.tsx b/frontend/src/pages/KnowledgeGraph/components/SearchPanel.tsx new file mode 100644 index 0000000..c4d6489 --- /dev/null +++ b/frontend/src/pages/KnowledgeGraph/components/SearchPanel.tsx @@ -0,0 +1,102 @@ +import { useState, useCallback } from "react"; +import { Input, List, Tag, Select, Empty } from "antd"; +import { Search } from "lucide-react"; +import type { SearchHitVO } from "../knowledge-graph.model"; +import { + ENTITY_TYPES, + ENTITY_TYPE_LABELS, + ENTITY_TYPE_COLORS, + DEFAULT_ENTITY_COLOR, +} from "../knowledge-graph.const"; + +interface SearchPanelProps { + graphId: string; + results: SearchHitVO[]; + loading: boolean; + onSearch: (graphId: string, query: string) => void; + onResultClick: (entityId: string) => void; + onClear: () => void; +} + +export default function SearchPanel({ + graphId, + results, + loading, + onSearch, + onResultClick, + onClear, +}: SearchPanelProps) { + const [query, setQuery] = useState(""); + const [typeFilter, setTypeFilter] = useState(undefined); + + const handleSearch = useCallback( + (value: string) => { + setQuery(value); + if (!value.trim()) { + onClear(); + return; + } + onSearch(graphId, value); + }, + [graphId, onSearch, onClear] + ); + + const filteredResults = typeFilter + ? results.filter((r) => r.type === typeFilter) + : results; + + return ( +
+ setQuery(e.target.value)} + onSearch={handleSearch} + allowClear + onClear={() => { + setQuery(""); + onClear(); + }} + prefix={} + loading={loading} + /> + +