From 9d9101a3e50a0166be243bc373e3c716140ed15d Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Tue, 22 Jul 2025 17:29:38 +0800 Subject: [PATCH] =?UTF-8?q?refactor(router):=20=E6=9B=B4=E6=96=B0=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 2 +- server/routers/__init__.py | 18 +- server/routers/auth_router.py | 18 +- server/routers/chat_router.py | 18 +- server/routers/graph_router.py | 227 +++++++--- .../{data_router.py => knowledge_router.py} | 352 +++++++-------- .../{base_router.py => system_router.py} | 180 ++++---- server/utils/auth_middleware.py | 5 +- src/agents/__init__.py | 4 +- src/agents/chatbot/graph.py | 2 +- src/agents/react/__init__.py | 4 - src/agents/react/configuration.py | 12 - src/agents/react/graph.py | 16 +- src/agents/registry.py | 118 +++++ src/core/kb_manager.py | 34 +- web/src/apis/admin_api.js | 413 ------------------ web/src/apis/base.js | 17 - web/src/apis/graph_api.js | 358 ++++++++------- web/src/apis/index.js | 36 +- web/src/apis/knowledge_api.js | 222 ++++++++++ web/src/apis/public_api.js | 74 ---- web/src/apis/system_api.js | 183 ++++++++ web/src/components/AgentChatComponent.vue | 9 +- web/src/components/ChatComponent.vue | 4 +- web/src/components/DebugComponent.vue | 6 +- web/src/components/KnowledgeGraphViewer.vue | 155 ++++--- web/src/main.js | 4 +- web/src/stores/config.js | 8 +- web/src/stores/database.js | 4 +- web/src/stores/info.js | 6 +- web/src/views/AgentView.vue | 14 +- web/src/views/DataBaseInfoView.vue | 30 +- web/src/views/DataBaseView.vue | 8 +- web/src/views/GraphView.vue | 16 +- web/src/views/LoginView.vue | 4 +- web/src/views/SettingView.vue | 4 +- 36 files changed, 1351 insertions(+), 1234 deletions(-) rename server/routers/{data_router.py => knowledge_router.py} (66%) rename server/routers/{base_router.py => system_router.py} (78%) delete mode 100644 src/agents/react/configuration.py delete mode 100644 web/src/apis/admin_api.js create mode 100644 web/src/apis/knowledge_api.js delete mode 100644 web/src/apis/public_api.js create mode 100644 web/src/apis/system_api.js diff --git a/docker-compose.yml b/docker-compose.yml index e6c6e7bf..4217eb2a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,7 @@ services: command: uv run uvicorn server.main:app --host 0.0.0.0 --port 5050 --reload restart: unless-stopped healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:5050/api/health || exit 1"] + test: ["CMD-SHELL", "curl -f http://localhost:5050/api/system/health || exit 1"] interval: 30s timeout: 15s retries: 8 diff --git a/server/routers/__init__.py b/server/routers/__init__.py index b8f2c610..48dd6cd6 100644 --- a/server/routers/__init__.py +++ b/server/routers/__init__.py @@ -1,13 +1,15 @@ from fastapi import APIRouter -from server.routers.chat_router import chat -from server.routers.data_router import data -from server.routers.base_router import base +from server.routers.system_router import system from server.routers.auth_router import auth +from server.routers.chat_router import chat +from server.routers.knowledge_router import knowledge from server.routers.graph_router import graph router = APIRouter() -router.include_router(base) -router.include_router(chat) -router.include_router(data) -router.include_router(auth) -router.include_router(graph) + +# 注册路由结构 +router.include_router(system) # /api/system/* +router.include_router(auth) # /api/auth/* +router.include_router(chat) # /api/chat/* +router.include_router(knowledge) # /api/knowledge/* +router.include_router(graph) # /api/graph/* diff --git a/server/routers/auth_router.py b/server/routers/auth_router.py index 179fb741..62e6fe2a 100644 --- a/server/routers/auth_router.py +++ b/server/routers/auth_router.py @@ -10,7 +10,7 @@ from server.utils.auth_utils import AuthUtils from server.utils.auth_middleware import get_db, get_current_user, get_admin_user, get_superadmin_user, oauth2_scheme # 创建路由器 -auth = APIRouter(prefix="/auth", tags=["auth"]) +auth = APIRouter(prefix="/auth", tags=["authentication"]) # 请求和响应模型 class Token(BaseModel): @@ -41,6 +41,10 @@ class InitializeAdmin(BaseModel): username: str password: str +# ============================================================================= +# === 工具函数 === +# ============================================================================= + # 记录操作日志 def log_operation(db: Session, user_id: int, operation: str, details: str = None, request: Request = None): ip_address = None @@ -57,6 +61,10 @@ def log_operation(db: Session, user_id: int, operation: str, details: str = None db.commit() # 路由:登录获取令牌 +# ============================================================================= +# === 认证分组 === +# ============================================================================= + @auth.post("/token", response_model=Token) async def login_for_access_token( form_data: OAuth2PasswordRequestForm = Depends(), @@ -141,11 +149,19 @@ async def initialize_admin( } # 路由:获取当前用户信息 +# ============================================================================= +# === 用户信息分组 === +# ============================================================================= + @auth.get("/me", response_model=UserResponse) async def read_users_me(current_user: User = Depends(get_current_user)): return current_user.to_dict() # 路由:创建新用户(管理员权限) +# ============================================================================= +# === 用户管理分组 === +# ============================================================================= + @auth.post("/users", response_model=UserResponse) async def create_user( user_data: UserCreate, diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py index e109685c..72ab083a 100644 --- a/server/routers/chat_router.py +++ b/server/routers/chat_router.py @@ -21,7 +21,11 @@ from server.utils.auth_middleware import get_required_user, get_db from server.models.user_model import User from server.models.thread_model import Thread -chat = APIRouter(prefix="/chat") +chat = APIRouter(prefix="/chat", tags=["chat"]) + +# ============================================================================= +# > === 智能体管理分组 === +# ============================================================================= @chat.get("/default_agent") async def get_default_agent(current_user: User = Depends(get_required_user)): @@ -62,6 +66,10 @@ async def set_default_agent(agent_id: str = Body(..., embed=True), current_user logger.error(f"设置默认智能体出错: {e}") raise HTTPException(status_code=500, detail=f"设置默认智能体出错: {str(e)}") +# ============================================================================= +# > === 对话分组 === +# ============================================================================= + @chat.get("/") async def chat_get(current_user: User = Depends(get_required_user)): """聊天服务健康检查(需要登录)""" @@ -155,6 +163,10 @@ async def chat_agent(agent_name: str, return StreamingResponse(stream_messages(), media_type='application/json') +# ============================================================================= +# > === 模型管理分组 === +# ============================================================================= + @chat.get("/models") async def get_chat_models(model_provider: str, current_user: User = Depends(get_admin_user)): """获取指定模型提供商的模型列表(需要登录)""" @@ -257,6 +269,10 @@ class ThreadResponse(BaseModel): update_at: str +# ============================================================================= +# > === 会话管理分组 === +# ============================================================================= + @chat.post("/thread", response_model=ThreadResponse) async def create_thread( thread: ThreadCreate, diff --git a/server/routers/graph_router.py b/server/routers/graph_router.py index 0a8b29a9..b8b82abc 100644 --- a/server/routers/graph_router.py +++ b/server/routers/graph_router.py @@ -1,16 +1,20 @@ import traceback -from fastapi import APIRouter, Query, HTTPException, Depends +from fastapi import APIRouter, Query, HTTPException, Depends, Body from server.utils.auth_middleware import get_admin_user from server.models.user_model import User -from src import knowledge_base +from src import knowledge_base, graph_base from src.utils.logging_config import logger -graph = APIRouter() +graph = APIRouter(prefix="/graph", tags=["graph"]) -@graph.get("/graph/subgraph") -async def get_subgraph( +# ============================================================================= +# === 子图查询分组 === +# ============================================================================= + +@graph.get("/lightrag/subgraph") +async def get_lightrag_subgraph( db_id: str = Query(..., description="数据库ID"), node_label: str = Query(..., description="节点标签或实体名称"), max_depth: int = Query(2, description="最大深度", ge=1, le=5), @@ -94,8 +98,35 @@ async def get_subgraph( raise HTTPException(status_code=500, detail=f"获取子图数据失败: {str(e)}") -@graph.get("/graph/labels") -async def get_graph_labels( +@graph.get("/lightrag/databases") +async def get_lightrag_databases( + current_user: User = Depends(get_admin_user) +): + """ + 获取所有可用的 LightRAG 数据库 + + Returns: + 可用的 LightRAG 数据库列表 + """ + try: + lightrag_databases = knowledge_base.get_lightrag_databases() + return { + "success": True, + "data": { + "databases": lightrag_databases + } + } + + except Exception as e: + logger.error(f"获取 LightRAG 数据库列表失败: {e}") + raise HTTPException(status_code=500, detail=f"获取 LightRAG 数据库列表失败: {str(e)}") + +# ============================================================================= +# === 节点管理分组 === +# ============================================================================= + +@graph.get("/lightrag/labels") +async def get_lightrag_labels( db_id: str = Query(..., description="数据库ID"), current_user: User = Depends(get_admin_user) ): @@ -142,92 +173,71 @@ async def get_graph_labels( raise HTTPException(status_code=500, detail=f"获取图谱标签失败: {str(e)}") -@graph.get("/graph/databases") -async def get_available_databases( +@graph.get("/neo4j/nodes") +async def get_neo4j_nodes( + kgdb_name: str = Query(..., description="知识图谱数据库名称"), + num: int = Query(100, description="节点数量", ge=1, le=1000), current_user: User = Depends(get_admin_user) ): """ - 获取所有可用的 LightRAG 数据库 - - Returns: - 可用的 LightRAG 数据库列表 + 获取图谱节点样本数据 """ try: - lightrag_databases = knowledge_base.get_lightrag_databases() + logger.debug(f"Get graph nodes in {kgdb_name} with {num} nodes") + + if not graph_base.is_running(): + raise HTTPException(status_code=400, detail="图数据库未启动") + + result = graph_base.get_sample_nodes(kgdb_name, num) + formatted_result = graph_base.format_general_results(result) + return { "success": True, - "data": { - "databases": lightrag_databases - } - } - - except Exception as e: - logger.error(f"获取 LightRAG 数据库列表失败: {e}") - raise HTTPException(status_code=500, detail=f"获取 LightRAG 数据库列表失败: {str(e)}") - - -# 保留原有的直接数据库查询方法作为备用(如果需要的话) -@graph.get("/graph/nodes") -async def get_graph_nodes_legacy( - db_id: str = Query(..., description="数据库ID"), - limit: int = Query(500, description="最大节点数量", ge=1, le=2000), - offset: int = Query(0, description="偏移量", ge=0), - entity_type: str | None = Query(None, description="实体类型筛选"), - search: str | None = Query(None, description="搜索关键词"), - current_user: User = Depends(get_admin_user) -): - """ - 直接查询数据库获取节点数据(备用方法) - 建议使用 /graph/subgraph 接口 - """ - try: - # 这里可以添加直接数据库查询的逻辑 - # 但建议用户使用 get_subgraph 接口 - return { - "success": False, - "message": "建议使用 /graph/subgraph 接口获取图谱数据", - "data": { - "nodes": [], - "total": 0 - } + "result": formatted_result, + "message": "success" } except Exception as e: logger.error(f"获取图节点数据失败: {e}") raise HTTPException(status_code=500, detail=f"获取图节点数据失败: {str(e)}") - -@graph.get("/graph/edges") -async def get_graph_edges_legacy( - db_id: str = Query(..., description="数据库ID"), - limit: int = Query(500, description="最大边数量", ge=1, le=2000), - offset: int = Query(0, description="偏移量", ge=0), - min_weight: float | None = Query(None, description="最小权重筛选"), +@graph.get("/neo4j/node") +async def get_neo4j_node( + entity_name: str = Query(..., description="实体名称"), current_user: User = Depends(get_admin_user) ): """ - 直接查询数据库获取边数据(备用方法) - 建议使用 /graph/subgraph 接口 + 根据实体名称查询图节点 """ try: - # 这里可以添加直接数据库查询的逻辑 - # 但建议用户使用 get_subgraph 接口 + if not graph_base.is_running(): + raise HTTPException(status_code=400, detail="图数据库未启动") + + result = graph_base.query_node(entity_name=entity_name) + formatted_result = graph_base.format_query_result_to_graph(result) + return { - "success": False, - "message": "建议使用 /graph/subgraph 接口获取图谱数据", - "data": { - "edges": [], - "total": 0 - } + "success": True, + "result": formatted_result, + "message": "success" } except Exception as e: - logger.error(f"获取图边数据失败: {e}") - raise HTTPException(status_code=500, detail=f"获取图边数据失败: {str(e)}") + logger.error(f"查询图节点失败: {e}") + raise HTTPException(status_code=500, detail=f"查询图节点失败: {str(e)}") +# ============================================================================= +# === 边管理分组 === +# ============================================================================= -@graph.get("/graph/stats") -async def get_graph_stats( +# 可以在这里添加边相关的管理功能 + +# ============================================================================= +# === 图谱分析分组 === +# ============================================================================= + +@graph.get("/lightrag/stats") +async def get_lightrag_stats( db_id: str = Query(..., description="数据库ID"), current_user: User = Depends(get_admin_user) ): @@ -284,3 +294,82 @@ async def get_graph_stats( logger.error(f"获取图谱统计信息失败: {e}") logger.error(f"Traceback: {traceback.format_exc()}") raise HTTPException(status_code=500, detail=f"获取图谱统计信息失败: {str(e)}") + +@graph.get("/neo4j/info") +async def get_neo4j_info(current_user: User = Depends(get_admin_user)): + """获取Neo4j图数据库信息""" + try: + graph_info = graph_base.get_graph_info() + if graph_info is None: + raise HTTPException(status_code=400, detail="图数据库获取出错") + return { + "success": True, + "data": graph_info + } + except Exception as e: + logger.error(f"获取图数据库信息失败: {e}") + raise HTTPException(status_code=500, detail=f"获取图数据库信息失败: {str(e)}") + +@graph.post("/neo4j/index-entities") +async def index_neo4j_entities( + data: dict = Body(default={}), + current_user: User = Depends(get_admin_user) +): + """为Neo4j图谱节点添加嵌入向量索引""" + try: + if not graph_base.is_running(): + raise HTTPException(status_code=400, detail="图数据库未启动") + + # 获取参数或使用默认值 + kgdb_name = data.get('kgdb_name', 'neo4j') + + # 调用GraphDatabase的add_embedding_to_nodes方法 + count = graph_base.add_embedding_to_nodes(kgdb_name=kgdb_name) + + return { + "success": True, + "status": "success", + "message": f"已成功为{count}个节点添加嵌入向量", + "indexed_count": count + } + except Exception as e: + logger.error(f"索引节点失败: {e}") + raise HTTPException(status_code=500, detail=f"索引节点失败: {str(e)}") + +@graph.post("/neo4j/add-entities") +async def add_neo4j_entities( + file_path: str = Body(...), + kgdb_name: str | None = Body(None), + current_user: User = Depends(get_admin_user) +): + """通过JSONL文件添加图谱实体到Neo4j""" + try: + if not file_path.endswith('.jsonl'): + return { + "success": False, + "message": "文件格式错误,请上传jsonl文件", + "status": "failed" + } + + await graph_base.jsonl_file_add_entity(file_path, kgdb_name) + return { + "success": True, + "message": "实体添加成功", + "status": "success" + } + except Exception as e: + logger.error(f"添加实体失败: {e}, {traceback.format_exc()}") + return { + "success": False, + "message": f"添加实体失败: {e}", + "status": "failed" + } + +# ============================================================================= +# === 兼容性接口 (保持向后兼容) === +# ============================================================================= + +@graph.get("/graph") +async def get_graph_info_compat(current_user: User = Depends(get_admin_user)): + """兼容性接口:获取图数据库信息""" + return await get_graph_info(current_user) diff --git a/server/routers/data_router.py b/server/routers/knowledge_router.py similarity index 66% rename from server/routers/data_router.py rename to server/routers/knowledge_router.py index 5d9cb251..2470780e 100644 --- a/server/routers/data_router.py +++ b/server/routers/knowledge_router.py @@ -4,44 +4,182 @@ import traceback from fastapi import APIRouter, File, UploadFile, HTTPException, Depends, Body, Form, Query from src.utils import logger, hashstr -from src import executor, config, knowledge_base, graph_base +from src import executor, config, knowledge_base from server.utils.auth_middleware import get_admin_user from server.models.user_model import User -data = APIRouter(prefix="/data") +knowledge = APIRouter(prefix="/knowledge", tags=["knowledge"]) +# ============================================================================= +# === 数据库管理分组 === +# ============================================================================= -@data.get("/") +@knowledge.get("/databases") async def get_databases(current_user: User = Depends(get_admin_user)): + """获取所有知识库""" try: database = knowledge_base.get_databases() + return database except Exception as e: logger.error(f"获取数据库列表失败 {e}, {traceback.format_exc()}") return {"message": f"获取数据库列表失败 {e}", "databases": []} + +@knowledge.post("/databases") +async def create_database( + database_name: str = Body(...), + description: str = Body(...), + embed_model_name: str = Body(...), + kb_type: str = Body("lightrag"), + current_user: User = Depends(get_admin_user) +): + """创建知识库""" + logger.debug(f"Create database {database_name} with kb_type {kb_type}") + try: + embed_info = config.embed_model_names[embed_model_name] + database_info = knowledge_base.create_database( + database_name, + description, + kb_type=kb_type, + embed_info=embed_info + ) + return database_info + except Exception as e: + logger.error(f"创建数据库失败 {e}, {traceback.format_exc()}") + return {"message": f"创建数据库失败 {e}", "status": "failed"} + +@knowledge.get("/databases/{db_id}") +async def get_database_info(db_id: str, current_user: User = Depends(get_admin_user)): + """获取知识库详细信息""" + database = knowledge_base.get_database_info(db_id) + if database is None: + raise HTTPException(status_code=404, detail="Database not found") return database -@data.get("/kb-types") -async def get_knowledge_base_types(current_user: User = Depends(get_admin_user)): - """获取支持的知识库类型""" +@knowledge.put("/databases/{db_id}") +async def update_database_info( + db_id: str, + name: str = Body(...), + description: str = Body(...), + current_user: User = Depends(get_admin_user) +): + """更新知识库信息""" + logger.debug(f"Update database {db_id} info: {name}, {description}") try: - kb_types = knowledge_base.get_supported_kb_types() - return {"kb_types": kb_types, "message": "success"} + database = knowledge_base.update_database(db_id, name, description) + return {"message": "更新成功", "database": database} except Exception as e: - logger.error(f"获取知识库类型失败 {e}, {traceback.format_exc()}") - return {"message": f"获取知识库类型失败 {e}", "kb_types": {}} + logger.error(f"更新数据库失败 {e}, {traceback.format_exc()}") + raise HTTPException(status_code=400, detail=f"更新数据库失败: {e}") -@data.get("/stats") -async def get_knowledge_base_statistics(current_user: User = Depends(get_admin_user)): - """获取知识库统计信息""" +@knowledge.delete("/databases/{db_id}") +async def delete_database(db_id: str, current_user: User = Depends(get_admin_user)): + """删除知识库""" + logger.debug(f"Delete database {db_id}") try: - stats = knowledge_base.get_statistics() - return {"stats": stats, "message": "success"} + knowledge_base.delete_database(db_id) + return {"message": "删除成功"} except Exception as e: - logger.error(f"获取知识库统计失败 {e}, {traceback.format_exc()}") - return {"message": f"获取知识库统计失败 {e}", "stats": {}} + logger.error(f"删除数据库失败 {e}, {traceback.format_exc()}") + raise HTTPException(status_code=400, detail=f"删除数据库失败: {e}") -@data.get("/query-params/{db_id}") -async def get_knowledge_base_query_params(db_id: str, current_user: User = Depends(get_admin_user)): +# ============================================================================= +# === 文档管理分组 === +# ============================================================================= + +@knowledge.post("/databases/{db_id}/documents") +async def add_documents( + db_id: str, + items: list[str] = Body(...), + params: dict = Body(...), + current_user: User = Depends(get_admin_user) +): + """添加文档到知识库""" + logger.debug(f"Add documents for db_id {db_id}: {items} {params=}") + + content_type = params.get('content_type', 'file') + + try: + processed_items = await knowledge_base.add_content(db_id, items, params=params) + item_type = "URLs" if content_type == 'url' else "files" + processed_failed_count = len([_p for _p in processed_items if _p['status'] == 'failed']) + processed_info = f"Processed {len(processed_items)} {item_type}, {processed_failed_count} {item_type} failed" + return {"message": processed_info, "items": processed_items, "status": "success"} + except Exception as e: + logger.error(f"Failed to process {content_type}s: {e}, {traceback.format_exc()}") + return {"message": f"Failed to process {content_type}s: {e}", "status": "failed"} + +@knowledge.get("/databases/{db_id}/documents/{doc_id}") +async def get_document_info( + db_id: str, + doc_id: str, + current_user: User = Depends(get_admin_user) +): + """获取文档详细信息""" + logger.debug(f"GET document {doc_id} info in {db_id}") + + try: + info = await knowledge_base.get_file_info(db_id, doc_id) + return info + except Exception as e: + logger.error(f"Failed to get file info, {e}, {db_id=}, {doc_id=}, {traceback.format_exc()}") + return {"message": "Failed to get file info", "status": "failed"} + +@knowledge.delete("/databases/{db_id}/documents/{doc_id}") +async def delete_document( + db_id: str, + doc_id: str, + current_user: User = Depends(get_admin_user) +): + """删除文档""" + logger.debug(f"DELETE document {doc_id} info in {db_id}") + try: + await knowledge_base.delete_file(db_id, doc_id) + return {"message": "删除成功"} + except Exception as e: + logger.error(f"删除文档失败 {e}, {traceback.format_exc()}") + raise HTTPException(status_code=400, detail=f"删除文档失败: {e}") + +# ============================================================================= +# === 查询分组 === +# ============================================================================= + +@knowledge.post("/databases/{db_id}/query") +async def query_knowledge_base( + db_id: str, + query: str = Body(...), + meta: dict = Body(...), + current_user: User = Depends(get_admin_user) +): + """查询知识库""" + logger.debug(f"Query knowledge base {db_id}: {query}") + try: + result = await knowledge_base.aquery(query, db_id=db_id, **meta) + return {"result": result, "status": "success"} + except Exception as e: + logger.error(f"知识库查询失败 {e}, {traceback.format_exc()}") + return {"message": f"知识库查询失败: {e}", "status": "failed"} + +@knowledge.post("/databases/{db_id}/query-test") +async def query_test( + db_id: str, + query: str = Body(...), + meta: dict = Body(...), + current_user: User = Depends(get_admin_user) +): + """测试查询知识库""" + logger.debug(f"Query test in {db_id}: {query}") + try: + result = await knowledge_base.aquery(query, db_id=db_id, **meta) + return result + except Exception as e: + logger.error(f"测试查询失败 {e}, {traceback.format_exc()}") + return {"message": f"测试查询失败: {e}", "status": "failed"} + +@knowledge.get("/databases/{db_id}/query-params") +async def get_knowledge_base_query_params( + db_id: str, + current_user: User = Depends(get_admin_user) +): """获取知识库类型特定的查询参数""" try: # 获取数据库信息 @@ -193,113 +331,17 @@ async def get_knowledge_base_query_params(db_id: str, current_user: User = Depen logger.error(f"获取知识库查询参数失败 {e}, {traceback.format_exc()}") return {"message": f"获取知识库查询参数失败 {e}", "params": {}} -@data.post("/") -async def create_database( - database_name: str = Body(...), - description: str = Body(...), - embed_model_name: str = Body(...), - kb_type: str = Body("lightrag"), # 新增:知识库类型参数,默认为lightrag - current_user: User = Depends(get_admin_user) -): - logger.debug(f"Create database {database_name} with kb_type {kb_type}") - try: - embed_info = config.embed_model_names[embed_model_name] - database_info = knowledge_base.create_database( - database_name, - description, - kb_type=kb_type, # 传递知识库类型 - embed_info=embed_info - ) - except Exception as e: - logger.error(f"创建数据库失败 {e}, {traceback.format_exc()}") - return {"message": f"创建数据库失败 {e}", "status": "failed"} - return database_info +# ============================================================================= +# === 文件管理分组 === +# ============================================================================= -@data.delete("/") -async def delete_database(db_id, current_user: User = Depends(get_admin_user)): - logger.debug(f"Delete database {db_id}") - knowledge_base.delete_database(db_id) - return {"message": "删除成功"} - -@data.post("/query-test") -async def query_test(query: str = Body(...), meta: dict = Body(...), current_user: User = Depends(get_admin_user)): - logger.debug(f"Query test in {meta}: {query}") - result = await knowledge_base.aquery(query, **meta) - return result - -@data.post("/add-files") -async def add_files(db_id: str = Body(...), items: list[str] = Body(...), params: dict = Body(...), current_user: User = Depends(get_admin_user)): - logger.debug(f"Add files/urls for db_id {db_id}: {items} {params=}") - - # 从 params 中获取 content_type,默认为 'file' - content_type = params.get('content_type', 'file') - - try: - # 使用统一的 add_content 方法 - processed_items = await knowledge_base.add_content(db_id, items, params=params) - - item_type = "URLs" if content_type == 'url' else "files" - processed_failed_count = len([_p for _p in processed_items if _p['status'] == 'failed']) - processed_info = f"Processed {len(processed_items)} {item_type}, {processed_failed_count} {item_type} failed" - return {"message": processed_info, "items": processed_items, "status": "success"} - except Exception as e: - logger.error(f"Failed to process {content_type}s: {e}, {traceback.format_exc()}") - return {"message": f"Failed to process {content_type}s: {e}", "status": "failed"} - -@data.post("/file-to-chunk") -async def file_to_chunk(db_id: str = Body(...), files: list[str] = Body(...), params: dict = Body(...), current_user: User = Depends(get_admin_user)): - logger.debug(f"File to chunk for db_id {db_id}: {files} {params=} (deprecated, use /add-files)") - # 兼容性路由,转发到新的统一接口 - params['content_type'] = 'file' - return await add_files(db_id, files, params, current_user) - -@data.post("/url-to-chunk") -async def url_to_chunk(db_id: str = Body(...), urls: list[str] = Body(...), params: dict = Body(...), current_user: User = Depends(get_admin_user)): - logger.debug(f"Url to chunk for db_id {db_id}: {urls} {params=} (deprecated, use /add-files)") - # 兼容性路由,转发到新的统一接口 - params['content_type'] = 'url' - return await add_files(db_id, urls, params, current_user) - -@data.post("/add-by-file") -async def create_document_by_file(db_id: str = Body(...), files: list[str] = Body(...), current_user: User = Depends(get_admin_user)): - raise ValueError("This method is deprecated. Use /add-files instead.") - -@data.post("/add-by-chunks") -async def add_by_chunks(db_id: str = Body(...), file_chunks: dict = Body(...), current_user: User = Depends(get_admin_user)): - raise ValueError("This method is deprecated. Use /add-files instead.") - -@data.get("/info") -async def get_database_info(db_id: str, current_user: User = Depends(get_admin_user)): - # logger.debug(f"Get database {db_id} info") - database = knowledge_base.get_database_info(db_id) - if database is None: - raise HTTPException(status_code=404, detail="Database not found") - return database - -@data.delete("/document") -async def delete_document(db_id: str = Body(...), file_id: str = Body(...), current_user: User = Depends(get_admin_user)): - logger.debug(f"DELETE document {file_id} info in {db_id}") - await knowledge_base.delete_file(db_id, file_id) - return {"message": "删除成功"} - -@data.get("/document") -async def get_document_info(db_id: str, file_id: str, current_user: User = Depends(get_admin_user)): - logger.debug(f"GET document {file_id} info in {db_id}") - - try: - info = await knowledge_base.get_file_info(db_id, file_id) - except Exception as e: - logger.error(f"Failed to get file info, {e}, {db_id=}, {file_id=}, {traceback.format_exc()}") - info = {"message": "Failed to get file info", "status": "failed"} - - return info - -@data.post("/upload") +@knowledge.post("/files/upload") async def upload_file( file: UploadFile = File(...), db_id: str | None = Query(None), current_user: User = Depends(get_admin_user) ): + """上传文件""" if not file.filename: raise HTTPException(status_code=400, detail="No selected file") @@ -319,63 +361,27 @@ async def upload_file( return {"message": "File successfully uploaded", "file_path": file_path, "db_id": db_id} -@data.get("/graph") -async def get_graph_info(current_user: User = Depends(get_admin_user)): - graph_info = graph_base.get_graph_info() - if graph_info is None: - raise HTTPException(status_code=400, detail="图数据库获取出错") - return graph_info - -@data.post("/graph/index-nodes") -async def index_nodes(data: dict = Body(default={}), current_user: User = Depends(get_admin_user)): - if not graph_base.is_running(): - raise HTTPException(status_code=400, detail="图数据库未启动") - - # 获取参数或使用默认值 - kgdb_name = data.get('kgdb_name', 'neo4j') - - # 调用GraphDatabase的add_embedding_to_nodes方法 - count = graph_base.add_embedding_to_nodes(kgdb_name=kgdb_name) - - return {"status": "success", "message": f"已成功为{count}个节点添加嵌入向量", "indexed_count": count} - -@data.get("/graph/node") -async def get_graph_node(entity_name: str, current_user: User = Depends(get_admin_user)): - result = graph_base.query_node(entity_name=entity_name) - return {"result": graph_base.format_query_result_to_graph(result), "message": "success"} - -@data.get("/graph/nodes") -async def get_graph_nodes(kgdb_name: str, num: int, current_user: User = Depends(get_admin_user)): - - logger.debug(f"Get graph nodes in {kgdb_name} with {num} nodes") - result = graph_base.get_sample_nodes(kgdb_name, num) - return {"result": graph_base.format_general_results(result), "message": "success"} - -@data.post("/graph/add-by-jsonl") -async def add_graph_entity(file_path: str = Body(...), kgdb_name: str | None = Body(None), current_user: User = Depends(get_admin_user)): - - if not file_path.endswith('.jsonl'): - return {"message": "文件格式错误,请上传jsonl文件", "status": "failed"} +# ============================================================================= +# === 知识库类型分组 === +# ============================================================================= +@knowledge.get("/types") +async def get_knowledge_base_types(current_user: User = Depends(get_admin_user)): + """获取支持的知识库类型""" try: - await graph_base.jsonl_file_add_entity(file_path, kgdb_name) - return {"message": "实体添加成功", "status": "success"} + kb_types = knowledge_base.get_supported_kb_types() + return {"kb_types": kb_types, "message": "success"} except Exception as e: - logger.error(f"添加实体失败: {e}, {traceback.format_exc()}") - return {"message": f"添加实体失败: {e}", "status": "failed"} + logger.error(f"获取知识库类型失败 {e}, {traceback.format_exc()}") + return {"message": f"获取知识库类型失败 {e}", "kb_types": {}} -@data.post("/update") -async def update_database_info( - db_id: str = Body(...), - name: str = Body(...), - description: str = Body(...), - current_user: User = Depends(get_admin_user) -): - logger.debug(f"Update database {db_id} info: {name}, {description}") +@knowledge.get("/stats") +async def get_knowledge_base_statistics(current_user: User = Depends(get_admin_user)): + """获取知识库统计信息""" try: - database = knowledge_base.update_database(db_id, name, description) - return {"message": "更新成功", "database": database} + stats = knowledge_base.get_statistics() + return {"stats": stats, "message": "success"} except Exception as e: - logger.error(f"更新数据库失败 {e}, {traceback.format_exc()}") - raise HTTPException(status_code=400, detail=f"更新数据库失败: {e}") + logger.error(f"获取知识库统计失败 {e}, {traceback.format_exc()}") + return {"message": f"获取知识库统计失败 {e}", "stats": {}} diff --git a/server/routers/base_router.py b/server/routers/system_router.py similarity index 78% rename from server/routers/base_router.py rename to server/routers/system_router.py index ce090c7f..12816133 100644 --- a/server/routers/base_router.py +++ b/server/routers/system_router.py @@ -1,10 +1,10 @@ import os import yaml -import asyncio import requests from pathlib import Path from fastapi import Request, Body, Depends, HTTPException from fastapi import APIRouter +from collections import deque from src import config, knowledge_base, graph_base from server.utils.auth_middleware import get_admin_user, get_superadmin_user @@ -12,7 +12,71 @@ from server.models.user_model import User from src.utils.logging_config import logger -base = APIRouter() +system = APIRouter(prefix="/system", tags=["system"]) + +# ============================================================================= +# === 健康检查分组 === +# ============================================================================= + +@system.get("/health") +async def health_check(): + """系统健康检查接口(公开接口)""" + return {"status": "ok", "message": "服务正常运行"} + +# ============================================================================= +# === 配置管理分组 === +# ============================================================================= + +@system.get("/config") +def get_config(current_user: User = Depends(get_admin_user)): + """获取系统配置""" + return config.dump_config() + +@system.post("/config") +async def update_config_single( + key = Body(...), + value = Body(...), + current_user: User = Depends(get_admin_user) +) -> dict: + """更新单个配置项""" + config[key] = value + config.save() + return config.dump_config() + +@system.post("/config/update") +async def update_config_batch( + items: dict = Body(...), + current_user: User = Depends(get_admin_user) +) -> dict: + """批量更新配置项""" + config.update(items) + config.save() + return config.dump_config() + +@system.post("/restart") +async def restart_system(current_user: User = Depends(get_superadmin_user)): + """重启系统(仅超级管理员)""" + graph_base.start() + return {"message": "系统已重启"} + +@system.get("/logs") +def get_system_logs(current_user: User = Depends(get_admin_user)): + """获取系统日志""" + try: + from src.utils.logging_config import LOG_FILE + + with open(LOG_FILE) as f: + last_lines = deque(f, maxlen=1000) + + log = ''.join(last_lines) + return {"log": log, "message": "success", "log_file": LOG_FILE} + except Exception as e: + logger.error(f"获取系统日志失败: {e}") + raise HTTPException(status_code=500, detail=f"获取系统日志失败: {str(e)}") + +# ============================================================================= +# === 信息管理分组 === +# ============================================================================= def load_info_config(): """加载信息配置文件""" @@ -59,55 +123,7 @@ def get_default_info_config(): } } -@base.get("/") -async def route_index(): - return {"message": "You Got It!"} - -@base.get("/health") -async def health_check(): - """简单的健康检查接口""" - return {"status": "ok", "message": "服务正常运行"} - -@base.get("/config") -def get_config(current_user: User = Depends(get_admin_user)): - return config.dump_config() - -@base.post("/config") -async def update_config( - key = Body(...), - value = Body(...), - current_user: User = Depends(get_admin_user) -) -> dict: - config[key] = value - config.save() - return config.dump_config() - -@base.post("/config/update") -async def update_config_item( - items: dict = Body(...), - current_user: User = Depends(get_admin_user) -) -> dict: - config.update(items) - config.save() - return config.dump_config() - -@base.post("/restart") -async def restart(current_user: User = Depends(get_superadmin_user)): - graph_base.start() - return {"message": "Restarted!"} - -@base.get("/log") -def get_log(current_user: User = Depends(get_admin_user)): - from src.utils.logging_config import LOG_FILE - from collections import deque - - with open(LOG_FILE) as f: - last_lines = deque(f, maxlen=1000) - - log = ''.join(last_lines) - return {"log": log, "message": "success", "log_file": LOG_FILE} - -@base.get("/info") +@system.get("/info") async def get_info_config(): """获取系统信息配置(公开接口,无需认证)""" try: @@ -120,10 +136,9 @@ async def get_info_config(): logger.error(f"获取信息配置失败: {e}") raise HTTPException(status_code=500, detail="获取信息配置失败") -@base.get("/info/reload") -async def reload_info_config(): - """重新加载信息配置(管理员接口)""" - # 注:这里暂时不添加权限验证,后续可以根据需要添加 +@system.post("/info/reload") +async def reload_info_config(current_user: User = Depends(get_admin_user)): + """重新加载信息配置""" try: config = load_info_config() return { @@ -135,7 +150,35 @@ async def reload_info_config(): logger.error(f"重新加载信息配置失败: {e}") raise HTTPException(status_code=500, detail="重新加载信息配置失败") -@base.get("/ocr/health") +# ============================================================================= +# === OCR服务分组 === +# ============================================================================= + +@system.get("/ocr/stats") +async def get_ocr_stats(current_user: User = Depends(get_admin_user)): + """ + 获取OCR服务使用统计信息 + 返回各个OCR服务的处理统计和性能指标 + """ + try: + from src.plugins._ocr import get_ocr_stats + stats = get_ocr_stats() + + return { + "status": "success", + "stats": stats, + "message": "OCR统计信息获取成功" + } + except Exception as e: + logger.error(f"获取OCR统计信息失败: {str(e)}") + return { + "status": "error", + "stats": {}, + "message": f"获取OCR统计信息失败: {str(e)}" + } + + +@system.get("/ocr/health") async def check_ocr_services_health(current_user: User = Depends(get_admin_user)): """ 检查所有OCR服务的健康状态 @@ -218,28 +261,3 @@ async def check_ocr_services_health(current_user: User = Depends(get_admin_user) "services": health_status, "message": "OCR服务健康检查完成" } - -@base.get("/ocr/stats") -async def get_ocr_stats(current_user: User = Depends(get_admin_user)): - """ - 获取OCR服务使用统计信息 - 返回各个OCR服务的处理统计和性能指标 - """ - try: - from src.plugins._ocr import get_ocr_stats - stats = get_ocr_stats() - - return { - "status": "success", - "stats": stats, - "message": "OCR统计信息获取成功" - } - except Exception as e: - logger.error(f"获取OCR统计信息失败: {str(e)}") - return { - "status": "error", - "stats": {}, - "message": f"获取OCR统计信息失败: {str(e)}" - } - - diff --git a/server/utils/auth_middleware.py b/server/utils/auth_middleware.py index d156fa40..346d987f 100644 --- a/server/utils/auth_middleware.py +++ b/server/utils/auth_middleware.py @@ -17,9 +17,8 @@ PUBLIC_PATHS = [ r"^/api/auth/check-first-run$", # 检查是否首次运行 r"^/api/auth/initialize$", # 初始化系统 r"^/api$", # Health Check - r"^/api/login$", # 登录页面 - r"^/api/info$", # 获取系统信息配置 - r"^/api/info/.*$", # 系统信息配置相关接口 + r"^/api/system/health$", # Health Check + r"^/api/system/info$", # 获取系统信息配置 ] # 获取数据库会话 diff --git a/src/agents/__init__.py b/src/agents/__init__.py index 6051dff2..6c5890b5 100644 --- a/src/agents/__init__.py +++ b/src/agents/__init__.py @@ -1,7 +1,8 @@ import asyncio from .chatbot import ChatbotAgent -from .react import ReActAgent +from .react.graph import ReActAgent +from .open_deep_research.graph import OpenDeepResearchAgent class AgentManager: def __init__(self): @@ -34,6 +35,7 @@ class AgentManager: agent_manager = AgentManager() agent_manager.register_agent(ChatbotAgent) agent_manager.register_agent(ReActAgent) +agent_manager.register_agent(OpenDeepResearchAgent) agent_manager.init_all_agents() __all__ = ["agent_manager"] diff --git a/src/agents/chatbot/graph.py b/src/agents/chatbot/graph.py index d0091b6a..657b3c6c 100644 --- a/src/agents/chatbot/graph.py +++ b/src/agents/chatbot/graph.py @@ -18,7 +18,7 @@ from src.agents.chatbot.configuration import ChatbotConfiguration from src.agents.tools_factory import get_all_tools class ChatbotAgent(BaseAgent): - name = "chatbot" + name = "对话机器人(Chatbot)" description = "基础的对话机器人,可以回答问题,默认不使用任何工具,可在配置中启用需要的工具。" requirements = ["TAVILY_API_KEY", "ZHIPUAI_API_KEY"] config_schema = ChatbotConfiguration diff --git a/src/agents/react/__init__.py b/src/agents/react/__init__.py index 1fc79cca..e69de29b 100644 --- a/src/agents/react/__init__.py +++ b/src/agents/react/__init__.py @@ -1,4 +0,0 @@ -from .graph import ReActAgent -from .configuration import ReActConfiguration - -__all__ = ["ReActAgent", "ReActConfiguration"] diff --git a/src/agents/react/configuration.py b/src/agents/react/configuration.py deleted file mode 100644 index a9885692..00000000 --- a/src/agents/react/configuration.py +++ /dev/null @@ -1,12 +0,0 @@ - -from dataclasses import dataclass - -from src.agents.registry import Configuration - - -@dataclass(kw_only=True) -class ReActConfiguration(Configuration): - """配置""" - - pass - diff --git a/src/agents/react/graph.py b/src/agents/react/graph.py index de14cfe7..38b7e897 100644 --- a/src/agents/react/graph.py +++ b/src/agents/react/graph.py @@ -1,23 +1,9 @@ -import asyncio -import uuid - - -from src.utils import logger from src.agents.registry import BaseAgent -from src.agents.react.configuration import ReActConfiguration class ReActAgent(BaseAgent): name = "ReAct" description = "A react agent that can answer questions and help with tasks." - config_schema = ReActConfiguration async def get_graph(self, **kwargs): from .workflows import graph - return graph - - - - - -if __name__ == "__main__": - pass + return graph \ No newline at end of file diff --git a/src/agents/registry.py b/src/agents/registry.py index 2b401eca..59733cdd 100644 --- a/src/agents/registry.py +++ b/src/agents/registry.py @@ -8,6 +8,7 @@ from typing import Annotated, TypedDict, Optional, Any from abc import abstractmethod from dataclasses import dataclass, fields, field +from pydantic import BaseModel, Field from langchain_core.runnables import RunnableConfig from langchain_core.messages import BaseMessage from langgraph.graph.state import CompiledStateGraph @@ -158,6 +159,123 @@ class Configuration(dict): +class BaseModelConfiguration(BaseModel): + + thread_id: str = Field( + default_factory=lambda: str(uuid.uuid4()), + metadata={ + "title": "线程ID", + "description": "用来描述智能体的角色和行为", + "configurable": False, + }, + ) + + user_id: str = Field( + default_factory=lambda: str(uuid.uuid4()), + metadata={ + "title": "用户ID", + "description": "用来描述智能体的角色和行为", + "configurable": False, + }, + ) + + @classmethod + def from_runnable_config( + cls, + config: Optional["RunnableConfig"] = None, + agent_name: Optional[str] = None, + ) -> "BaseModelConfiguration": + """ + 从 RunnableConfig 和 YAML 文件中构建 Configuration 实例 + """ + # 默认配置 + default_instance = cls() + default_values = default_instance.dict() + + # 文件配置 + file_config = cls.from_file(agent_name) if agent_name else {} + + # 运行时配置(最高优先级) + runtime_config = config.get("configurable") if config else {} + + merged_config = { + **default_values, + **file_config, + **runtime_config, + } + + return cls(**merged_config) + + @classmethod + def from_file(cls, agent_name: str) -> dict[str, Any]: + """ + 从 YAML 文件加载配置 + """ + config_file_path = Path(f"src/agents/{agent_name}/config.private.yaml") + if os.path.exists(config_file_path): + try: + with open(config_file_path, encoding="utf-8") as f: + return yaml.safe_load(f) or {} + except Exception as e: + logger.error(f"加载配置文件失败: {e}") + return {} + + @classmethod + def save_to_file(cls, config: dict, agent_name: str) -> bool: + """ + 保存配置到 YAML 文件 + """ + try: + config_file_path = Path(f"src/agents/{agent_name}/config.private.yaml") + os.makedirs(config_file_path.parent, exist_ok=True) + with open(config_file_path, "w", encoding="utf-8") as f: + yaml.dump(config, f, indent=2, allow_unicode=True) + return True + except Exception as e: + logger.error(f"保存配置文件失败: {e}") + return False + + @classmethod + def to_dict(cls) -> dict[str, Any]: + """ + 提取类字段的默认值与字段元数据,主要用于前端动态生成配置项。 + """ + # 创建实例以获得 default_factory 值 + instance = cls() + + confs: dict[str, Any] = {} + configurable_items: dict[str, Any] = {} + + for name, field in cls.model_fields.items(): + value = getattr(instance, name) + + confs[name] = value + + # 安全地处理 Pydantic 字段元数据 + field_metadata = {} + if hasattr(field, 'json_schema_extra') and field.json_schema_extra: + if isinstance(field.json_schema_extra, dict): + field_metadata = field.json_schema_extra['metadata'] + elif isinstance(field.json_schema_extra, (list, tuple)): + # 在 Pydantic v2 中,metadata 可能是列表,合并所有字典项 + for item in field.json_schema_extra: + if isinstance(item, dict): + field_metadata.update(item['metadata']) + + # 检查字段是否应该可配置 - 支持不同的元数据格式 + if field_metadata.get("configurable", True): + configurable_items[name] = { + "type": field_metadata.get("type", field.annotation.__name__), + "name": field_metadata.get("name") or name, + "options": field_metadata.get("options") or [], + "default": field.default if field.default is not None else None, + "description": field_metadata.get("description") or "", + "x_oap_ui_config": field_metadata.get("x_oap_ui_config", {}), + } + + confs["configurable_items"] = configurable_items + return confs + class BaseAgent: """ diff --git a/src/core/kb_manager.py b/src/core/kb_manager.py index 025b7ce9..bd2b461b 100644 --- a/src/core/kb_manager.py +++ b/src/core/kb_manager.py @@ -305,26 +305,26 @@ class KnowledgeBaseManager: # TODO: 实现数据库迁移逻辑 raise NotImplementedError("Database migration not implemented yet") - def get_statistics(self) -> Dict: - """获取统计信息""" - stats = { - "total_databases": len(self.global_databases_meta), - "kb_types": {}, - "total_files": 0 - } + def get_statistics(self) -> Dict: + """获取统计信息""" + stats = { + "total_databases": len(self.global_databases_meta), + "kb_types": {}, + "total_files": 0 + } - # 按知识库类型统计 - for db_meta in self.global_databases_meta.values(): - kb_type = db_meta.get("kb_type", "lightrag") - if kb_type not in stats["kb_types"]: - stats["kb_types"][kb_type] = 0 - stats["kb_types"][kb_type] += 1 + # 按知识库类型统计 + for db_meta in self.global_databases_meta.values(): + kb_type = db_meta.get("kb_type", "lightrag") + if kb_type not in stats["kb_types"]: + stats["kb_types"][kb_type] = 0 + stats["kb_types"][kb_type] += 1 - # 统计文件总数 - for kb_instance in self.kb_instances.values(): - stats["total_files"] += len(kb_instance.files_meta) + # 统计文件总数 + for kb_instance in self.kb_instances.values(): + stats["total_files"] += len(kb_instance.files_meta) - return stats + return stats # ============================================================================= # 兼容性方法 - 为了支持现有的 graph_router.py diff --git a/web/src/apis/admin_api.js b/web/src/apis/admin_api.js deleted file mode 100644 index 4a7220ff..00000000 --- a/web/src/apis/admin_api.js +++ /dev/null @@ -1,413 +0,0 @@ -import { apiGet, apiPost, apiPut, apiDelete } from './base' -import { useUserStore } from '@/stores/user' - -/** - * 管理员API模块 - * 只有管理员和超级管理员可以访问的API - * 权限要求: admin 或 superadmin - * - * 注意: 请确保在使用这些API之前检查用户是否具有管理员权限 - */ - -// 检查当前用户是否有管理员权限 -const checkAdminPermission = () => { - const userStore = useUserStore() - if (!userStore.isAdmin) { - throw new Error('需要管理员权限') - } - return true -} - -// 检查当前用户是否有超级管理员权限 -const checkSuperAdminPermission = () => { - const userStore = useUserStore() - if (!userStore.isSuperAdmin) { - throw new Error('需要超级管理员权限') - } - return true -} - -// 用户管理API -export const userManagementApi = { - /** - * 获取用户列表 - * @returns {Promise} - 用户列表 - */ - getUsers: async () => { - checkAdminPermission() - return apiGet('/api/auth/users', {}, true) - }, - - /** - * 创建新用户 - * @param {Object} userData - 用户数据 - * @returns {Promise} - 创建结果 - */ - createUser: async (userData) => { - checkAdminPermission() - return apiPost('/api/auth/users', userData, {}, true) - }, - - /** - * 更新用户 - * @param {number} userId - 用户ID - * @param {Object} userData - 用户数据 - * @returns {Promise} - 更新结果 - */ - updateUser: async (userId, userData) => { - checkAdminPermission() - return apiPut(`/api/auth/users/${userId}`, userData, {}, true) - }, - - /** - * 删除用户 - * @param {number} userId - 用户ID - * @returns {Promise} - 删除结果 - */ - deleteUser: async (userId) => { - checkAdminPermission() - return apiDelete(`/api/auth/users/${userId}`, {}, true) - }, -} - -// 知识库管理API -export const knowledgeBaseApi = { - /** - * 获取所有知识库 - * @returns {Promise} - 知识库列表 - */ - getDatabases: async () => { - checkAdminPermission() - return apiGet('/api/data/', {}, true) - }, - - /** - * 创建知识库 - * @param {Object} databaseData - 知识库数据 (包含database_name, description, embed_model_name, kb_type等) - * @returns {Promise} - 创建结果 - */ - createDatabase: async (databaseData) => { - checkAdminPermission() - return apiPost('/api/data/', { - database_name: databaseData.database_name, - description: databaseData.description, - embed_model_name: databaseData.embed_model_name, - kb_type: databaseData.kb_type || 'lightrag', // 默认为lightrag类型 - ...databaseData.extra_config // 额外配置(如Vector的chunk_size等) - }, {}, true) - }, - - /** - * 获取知识库详情 - * @param {string} dbId - 知识库ID - * @returns {Promise} - 知识库详情 - */ - getDatabaseInfo: async (dbId) => { - checkAdminPermission() - return apiGet(`/api/data/info?db_id=${dbId}`, {}, true) - }, - - /** - * 删除知识库 - * @param {string} dbId - 知识库ID - * @returns {Promise} - 删除结果 - */ - deleteDatabase: async (dbId) => { - checkAdminPermission() - return apiDelete(`/api/data/?db_id=${dbId}`, {}, true) - }, - - /** - * 上传文件到知识库 - * @param {FormData} formData - 包含文件的FormData - * @param {string} dbId - 知识库ID - * @returns {Promise} - 上传结果 - */ - uploadFile: async (formData, dbId) => { - checkAdminPermission() - const userStore = useUserStore() - const authHeaders = userStore.getAuthHeaders() - - return fetch(`/api/data/upload?db_id=${dbId}`, { - method: 'POST', - headers: { - ...authHeaders - }, - body: formData - }).then(res => { - if (!res.ok) { - throw new Error(`上传失败: ${res.status} ${res.statusText}`) - } - return res.json() - }) - }, - - /** - * 删除文件 - * @param {string} dbId - 知识库ID - * @param {string} fileId - 文件ID - * @returns {Promise} - 删除结果 - */ - deleteFile: async (dbId, fileId) => { - checkAdminPermission() - return apiDelete('/api/data/document', { - body: JSON.stringify({ db_id: dbId, file_id: fileId }) - }, true) - }, - - /** - * 添加文件或URL到知识库 - * @param {Object} data - 包含 db_id, items (文件路径或URL列表), params (包含 content_type 等参数) - * @returns {Promise} - 处理结果 - */ - addFiles: async (data) => { // data: { db_id, items, params } - checkAdminPermission() - return apiPost('/api/data/add-files', data, {}, true) - }, - - /** - * 将分块添加到数据库 - * @param {Object} data - 包含db_id和file_chunks的数据 - * @returns {Promise} - 添加结果 - */ - addByChunks: async (data) => { - checkAdminPermission() - return apiPost('/api/data/add-by-chunks', data, {}, true) - }, - - /** - * 查询测试 - * @param {Object} data - 查询参数 - * @returns {Promise} - 查询结果 - */ - queryTest: async (data) => { - checkAdminPermission() - return apiPost('/api/data/query-test', data, {}, true) - }, - - /** - * 获取文档详情 - * @param {string} dbId - 知识库ID - * @param {string} fileId - 文件ID - * @returns {Promise} - 文档详情 - */ - getDocumentDetail: async (dbId, fileId) => { - checkAdminPermission() - return apiGet(`/api/data/document?db_id=${dbId}&file_id=${fileId}`, {}, true) - }, - - /** - * 更新知识库信息 - * @param {string} dbId - 知识库ID - * @param {Object} data - 包含name和description的数据对象 - * @returns {Promise} - 更新结果 - */ - updateDatabaseInfo: async (dbId, data) => { - checkAdminPermission() - return apiPost('/api/data/update', { - db_id: dbId, - ...data - }, {}, true) - }, - - /** - * 获取支持的知识库类型 - * @returns {Promise} - 支持的知识库类型列表 - */ - getSupportedKbTypes: async () => { - checkAdminPermission() - return apiGet('/api/data/kb-types', {}, true) - }, - - /** - * 获取知识库统计信息 - * @returns {Promise} - 知识库统计信息 - */ - getKbStatistics: async () => { - checkAdminPermission() - return apiGet('/api/data/stats', {}, true) - }, - - /** - * 获取知识库类型特定的查询参数 - * @param {string} dbId - 知识库ID - * @returns {Promise} - 查询参数配置 - */ - getKbQueryParams: async (dbId) => { - checkAdminPermission() - return apiGet(`/api/data/query-params/${dbId}`, {}, true) - }, -} - -// 图数据库管理API -export const graphApi = { - /** - * 获取图数据库状态 - * @returns {Promise} - 图数据库状态 - */ - getGraphInfo: async () => { - checkAdminPermission() - return apiGet('/api/data/graph', {}, true) - }, - - /** - * 获取节点 - * @param {string} dbName - 图数据库名称 - * @param {number} num - 节点数量 - * @returns {Promise} - 节点数据 - */ - getNodes: async (dbName, num) => { - checkAdminPermission() - return apiGet(`/api/data/graph/nodes?kgdb_name=${dbName}&num=${num}`, {}, true) - }, - - /** - * 查询实体 - * @param {string} entityName - 实体名称 - * @returns {Promise} - 查询结果 - */ - queryNode: async (entityName) => { - checkAdminPermission() - return apiGet(`/api/data/graph/node?entity_name=${entityName}`, {}, true) - }, - - /** - * 添加JSONL文件到图数据库 - * @param {string} filePath - 文件路径 - * @returns {Promise} - 添加结果 - */ - addByJsonl: async (filePath) => { - checkAdminPermission() - return apiPost('/api/data/graph/add-by-jsonl', { file_path: filePath }, {}, true) - }, - - /** - * 为未索引节点添加索引 - * @param {string} dbName - 图数据库名称 - * @returns {Promise} - 索引结果 - */ - indexNodes: async (dbName) => { - checkAdminPermission() - return apiPost('/api/data/graph/index-nodes', { kgdb_name: dbName }, {}, true) - }, -} - -// 系统配置API -export const systemConfigApi = { - /** - * 设置默认智能体 - * @param {string} agentId - 智能体ID - * @returns {Promise} - 设置结果 - */ - setDefaultAgent: async (agentId) => { - checkAdminPermission() - return apiPost('/api/chat/set_default_agent', { agent_id: agentId }, {}, true) - }, - - /** - * 获取系统配置 - * @returns {Promise} - 系统配置 - */ - getSystemConfig: async () => { - checkAdminPermission() - return apiGet('/api/config', {}, true) - }, - - /** - * 获取智能体配置 - * @param {string} agentId - 智能体ID - * @returns {Promise} - 智能体配置 - */ - getAgentConfig: async (agentId) => { - checkAdminPermission() - return apiGet(`/api/chat/agent/${agentId}/config`, {}, true) - }, - - /** - * 保存智能体配置 - * @param {string} agentId - 智能体ID - * @param {Object} config - 配置内容 - * @returns {Promise} - 保存结果 - */ - saveAgentConfig: async (agentId, config) => { - checkAdminPermission() - return apiPost(`/api/chat/agent/${agentId}/config`, config, {}, true) - }, - - /** - * 更新某个配置 - * @param {Object} items - 配置项 - * @returns {Promise} - 更新结果 - */ - updateConfigItems: async (items) => { - checkAdminPermission() - console.log("updateConfigItems", items) - return apiPost('/api/config/update', items, {}, true) - }, - - /** - * 重启服务 - * @returns {Promise} - 重启结果 - */ - restartServer: async () => { - checkSuperAdminPermission() - return apiPost('/api/restart', {}, {}, true) - } -} - -// 日志API -export const logApi = { - /** - * 获取系统日志 - * @param {Object} params - 日志查询参数 - * @returns {Promise} - 日志数据 - */ - getLogs: async (params = {}) => { - checkAdminPermission() - return apiGet('/api/log', { params }, true) - }, -} - -// 通用admin -export const adminApi = { - /** - * 获取所有智能体 - * @param {Object} params - 查询参数 - * @returns {Promise} - 查询结果 - */ - adminGet: async (params, url) => { - checkAdminPermission() - return apiGet(url, { params }, true) - }, - - /** - * 更新某个配置 - * @param {Object} items - 配置项 - * @returns {Promise} - 更新结果 - */ - adminPost: async (data, url) => { - checkAdminPermission() - return apiPost(url, data, {}, true) - }, -} - -// OCR服务管理API -export const ocrApi = { - /** - * 检查OCR服务健康状态 - * @returns {Promise} - OCR服务健康状态信息 - */ - checkHealth: async () => { - checkAdminPermission() - return apiGet('/api/ocr/health', {}, true) - }, - - /** - * 获取OCR服务使用统计 - * @returns {Promise} - OCR服务统计信息 - */ - getStats: async () => { - checkAdminPermission() - return apiGet('/api/ocr/stats', {}, true) - } -} diff --git a/web/src/apis/base.js b/web/src/apis/base.js index 54ef4b56..dde8ea66 100644 --- a/web/src/apis/base.js +++ b/web/src/apis/base.js @@ -155,20 +155,3 @@ export function apiDelete(url, options = {}, requiresAuth = false) { return apiRequest(url, { method: 'DELETE', ...options }, requiresAuth) } -/** - * 健康检查API - */ -export const healthApi = { - /** - * 检查服务端健康状态 - * @returns {Promise} - 健康检查结果 - */ - async checkHealth() { - try { - const response = await apiGet('/api/health') - return { status: 'ok', data: response } - } catch (error) { - return { status: 'error', error: error.message } - } - } -} \ No newline at end of file diff --git a/web/src/apis/graph_api.js b/web/src/apis/graph_api.js index 0e490ca3..aed00c9a 100644 --- a/web/src/apis/graph_api.js +++ b/web/src/apis/graph_api.js @@ -1,203 +1,158 @@ -import { apiGet } from './base' +import { apiGet, apiPost } from './base' /** - * 图数据API调用 - 基于LightRAG的新接口 + * 图数据库API模块 + * 包含LightRAG图知识库和Neo4j图数据库两种接口 + * 采用命名空间分组模式,清晰区分接口类型 */ -/** - * 获取所有可用的数据库 - * @returns {Promise} - 数据库列表 - */ -export const getAvailableDatabases = async () => { - return await apiGet('/api/graph/databases', {}, true) -} +// ============================================================================= +// === LightRAG图知识库接口分组 === +// ============================================================================= -/** - * 获取图标签列表 - * @param {string} dbId - 数据库ID - * @returns {Promise} - 标签列表 - */ -export const getGraphLabels = async (dbId) => { - if (!dbId) { - throw new Error('db_id is required') - } +export const lightragApi = { + /** + * 获取LightRAG知识图谱子图数据 + * @param {Object} params - 查询参数 + * @param {string} params.db_id - LightRAG数据库ID + * @param {string} params.node_label - 节点标签("*"获取全图) + * @param {number} params.max_depth - 最大深度 + * @param {number} params.max_nodes - 最大节点数 + * @returns {Promise} - 子图数据 + */ + getSubgraph: async (params) => { + const { db_id, node_label = "*", max_depth = 2, max_nodes = 100 } = params - const queryParams = new URLSearchParams({ - db_id: dbId - }) + if (!db_id) { + throw new Error('db_id is required') + } - return await apiGet(`/api/graph/labels?${queryParams.toString()}`, {}, true) -} - -/** - * 获取子图数据 - 主要接口 - * @param {Object} params - 查询参数 - * @param {string} params.db_id - 数据库ID - * @param {string} params.node_label - 节点标签 (使用 "*" 获取全图) - * @param {number} params.max_depth - 最大深度 - * @param {number} params.max_nodes - 最大节点数 - * @returns {Promise} - 子图数据 - */ -export const getSubgraph = async (params) => { - const { db_id, node_label = "*", max_depth = 2, max_nodes = 100 } = params - - if (!db_id) { - throw new Error('db_id is required') - } - - const queryParams = new URLSearchParams({ - db_id: db_id, - node_label: node_label, - max_depth: max_depth.toString(), - max_nodes: max_nodes.toString() - }) - - return await apiGet(`/api/graph/subgraph?${queryParams.toString()}`, {}, true) -} - -/** - * 获取图统计信息 - * @param {string} dbId - 数据库ID - * @returns {Promise} - 统计数据 - */ -export const getGraphStats = async (dbId) => { - if (!dbId) { - throw new Error('db_id is required') - } - - const queryParams = new URLSearchParams({ - db_id: dbId - }) - - return await apiGet(`/api/graph/stats?${queryParams.toString()}`, {}, true) -} - -/** - * 获取完整图数据 - 使用新的子图接口 - * @param {Object} params - 查询参数 - * @param {string} params.db_id - 数据库ID - * @param {string} params.node_label - 节点标签筛选 - * @param {number} params.max_nodes - 最大节点数 - * @param {number} params.max_depth - 最大深度 - * @returns {Promise} - 完整图数据 - */ -export const getFullGraph = async (params = {}) => { - const { db_id, node_label = "*", max_nodes = 200, max_depth = 2 } = params - - if (!db_id) { - throw new Error('db_id is required for graph operations') - } - - try { - // 使用子图接口获取数据 - const response = await getSubgraph({ - db_id, - node_label, - max_nodes, - max_depth + const queryParams = new URLSearchParams({ + db_id: db_id, + node_label: node_label, + max_depth: max_depth.toString(), + max_nodes: max_nodes.toString() }) - if (!response.success) { - throw new Error('获取图数据失败') + return await apiGet(`/api/graph/lightrag/subgraph?${queryParams.toString()}`, {}, true) + }, + + /** + * 获取所有可用的LightRAG数据库 + * @returns {Promise} - LightRAG数据库列表 + */ + getDatabases: async () => { + return await apiGet('/api/graph/lightrag/databases', {}, true) + }, + + /** + * 获取LightRAG图谱标签列表 + * @param {string} db_id - LightRAG数据库ID + * @returns {Promise} - 标签列表 + */ + getLabels: async (db_id) => { + if (!db_id) { + throw new Error('db_id is required') } - return { - success: true, - data: { - nodes: response.data.nodes, - edges: response.data.edges, - is_truncated: response.data.is_truncated, - stats: { - total_nodes: response.data.total_nodes, - total_edges: response.data.total_edges, - displayed_nodes: response.data.nodes.length, - displayed_edges: response.data.edges.length - } - } + const queryParams = new URLSearchParams({ + db_id: db_id + }) + + return await apiGet(`/api/graph/lightrag/labels?${queryParams.toString()}`, {}, true) + }, + + /** + * 获取LightRAG图谱统计信息 + * @param {string} db_id - LightRAG数据库ID + * @returns {Promise} - 统计信息 + */ + getStats: async (db_id) => { + if (!db_id) { + throw new Error('db_id is required') } - } catch (error) { - console.error('获取完整图数据失败:', error) - throw error + + const queryParams = new URLSearchParams({ + db_id: db_id + }) + + return await apiGet(`/api/graph/lightrag/stats?${queryParams.toString()}`, {}, true) } } -/** - * 根据特定标签获取子图 - * @param {Object} params - 查询参数 - * @param {string} params.db_id - 数据库ID - * @param {string} params.entity_type - 实体类型 - * @param {number} params.max_nodes - 最大节点数 - * @param {number} params.max_depth - 最大深度 - * @returns {Promise} - 子图数据 - */ -export const getGraphByEntityType = async (params = {}) => { - const { db_id, entity_type, max_nodes = 100, max_depth = 2 } = params +// ============================================================================= +// === Neo4j图数据库接口分组 === +// ============================================================================= - if (!db_id) { - throw new Error('db_id is required') +export const neo4jApi = { + /** + * 获取Neo4j图数据库样例节点 + * @param {string} kgdb_name - Neo4j数据库名称(默认为'neo4j') + * @param {number} num - 节点数量 + * @returns {Promise} - 样例节点数据 + */ + getSampleNodes: async (kgdb_name = 'neo4j', num = 100) => { + const queryParams = new URLSearchParams({ + kgdb_name: kgdb_name, + num: num.toString() + }) + + return await apiGet(`/api/graph/neo4j/nodes?${queryParams.toString()}`, {}, true) + }, + + /** + * 根据实体名称查询Neo4j图节点 + * @param {string} entity_name - 实体名称 + * @returns {Promise} - 节点数据 + */ + queryNode: async (entity_name) => { + if (!entity_name) { + throw new Error('entity_name is required') + } + + const queryParams = new URLSearchParams({ + entity_name: entity_name + }) + + return await apiGet(`/api/graph/neo4j/node?${queryParams.toString()}`, {}, true) + }, + + /** + * 通过JSONL文件添加图谱实体到Neo4j + * @param {string} file_path - JSONL文件路径 + * @param {string} kgdb_name - Neo4j数据库名称(默认为'neo4j') + * @returns {Promise} - 添加结果 + */ + addEntities: async (file_path, kgdb_name = 'neo4j') => { + return await apiPost('/api/graph/neo4j/add-entities', { + file_path: file_path, + kgdb_name: kgdb_name + }, {}, true) + }, + + /** + * 为Neo4j图谱节点添加嵌入向量索引 + * @param {string} kgdb_name - Neo4j数据库名称(默认为'neo4j') + * @returns {Promise} - 索引结果 + */ + indexEntities: async (kgdb_name = 'neo4j') => { + return await apiPost('/api/graph/neo4j/index-entities', { + kgdb_name: kgdb_name + }, {}, true) + }, + + /** + * 获取Neo4j图数据库信息 + * @returns {Promise} - 图数据库信息 + */ + getInfo: async () => { + return await apiGet('/api/graph/neo4j/info', {}, true) } - - return await getSubgraph({ - db_id, - node_label: entity_type || "*", - max_nodes, - max_depth - }) } -/** - * 展开指定节点的邻居 - * @param {Object} params - 查询参数 - * @param {string} params.db_id - 数据库ID - * @param {string} params.node_label - 节点标签 - * @param {number} params.max_depth - 最大深度 - * @param {number} params.max_nodes - 最大节点数 - * @returns {Promise} - 邻居节点数据 - */ -export const expandNodeNeighbors = async (params) => { - const { db_id, node_label, max_depth = 1, max_nodes = 50 } = params - - if (!db_id || !node_label) { - throw new Error('db_id and node_label are required') - } - - return await getSubgraph({ - db_id, - node_label, - max_depth, - max_nodes - }) -} - -// ==================== 兼容性方法 ==================== - -/** - * 获取图节点数据 (已弃用,建议使用 getSubgraph) - * @deprecated 请使用 getSubgraph 替代 - */ -export const getGraphNodes = async (params = {}) => { - console.warn('getGraphNodes is deprecated, please use getSubgraph instead') - const { db_id } = params - if (!db_id) { - throw new Error('db_id is required. Please provide db_id parameter.') - } - return await getSubgraph({ ...params, node_label: "*" }) -} - -/** - * 获取图边数据 (已弃用,建议使用 getSubgraph) - * @deprecated 请使用 getSubgraph 替代 - */ -export const getGraphEdges = async (params = {}) => { - console.warn('getGraphEdges is deprecated, please use getSubgraph instead') - const { db_id } = params - if (!db_id) { - throw new Error('db_id is required. Please provide db_id parameter.') - } - return await getSubgraph({ ...params, node_label: "*" }) -} - -// ==================== 工具函数 ==================== +// ============================================================================= +// === 工具函数分组 === +// ============================================================================= /** * 根据实体类型获取颜色 @@ -227,7 +182,7 @@ export const getEntityTypeColor = (entityType) => { * 根据权重计算边的粗细 * @param {number} weight - 权重值 * @param {number} minWeight - 最小权重 - * @param {number} maxWeight - 最大权重 + * @param {number} maxWeight - 最大权重 * @returns {number} - 边的粗细 */ export const calculateEdgeWidth = (weight, minWeight = 1, maxWeight = 10) => { @@ -235,4 +190,43 @@ export const calculateEdgeWidth = (weight, minWeight = 1, maxWeight = 10) => { const maxWidth = 5 const normalizedWeight = (weight - minWeight) / (maxWeight - minWeight) return minWidth + normalizedWeight * (maxWidth - minWidth) +} + +// ============================================================================= +// === 兼容性导出(可选,用于平滑迁移)=== +// ============================================================================= + +// 保持向后兼容的导出,后续可以移除 +export const getGraphNodes = async (params = {}) => { + console.warn('getGraphNodes is deprecated, use neo4jApi.getSampleNodes instead') + return neo4jApi.getSampleNodes(params.kgdb_name || 'neo4j', params.num || 100) +} + +export const getGraphNode = async (params = {}) => { + console.warn('getGraphNode is deprecated, use neo4jApi.queryNode instead') + return neo4jApi.queryNode(params.entity_name) +} + +export const addByJsonl = async (file_path, kgdb_name = 'neo4j') => { + console.warn('addByJsonl is deprecated, use neo4jApi.addEntities instead') + return neo4jApi.addEntities(file_path, kgdb_name) +} + +export const indexNodes = async (kgdb_name = 'neo4j') => { + console.warn('indexNodes is deprecated, use neo4jApi.indexEntities instead') + return neo4jApi.indexEntities(kgdb_name) +} + +export const getGraphStats = async () => { + console.warn('getGraphStats is deprecated, use neo4jApi.getInfo instead') + return neo4jApi.getInfo() +} + +// 保持旧的分组导出,便于批量替换 +export const graphApi = { + getSubgraph: lightragApi.getSubgraph, + getDatabases: lightragApi.getDatabases, + getLabels: lightragApi.getLabels, + getStats: lightragApi.getStats, + ...neo4jApi // 临时兼容 } \ No newline at end of file diff --git a/web/src/apis/index.js b/web/src/apis/index.js index 6acfb3fc..1214ec0f 100644 --- a/web/src/apis/index.js +++ b/web/src/apis/index.js @@ -3,31 +3,31 @@ * 导出所有API模块,方便统一引入 */ -// 导出公共API模块 -export * from './public_api' - -// 导出需要用户认证的API模块 -export * from './auth_api' - -// 导出需要管理员权限的API模块 -export * from './admin_api' +// 导出API模块 +export * from './system_api' // 系统管理API +export * from './knowledge_api' // 知识库管理API +export * from './auth_api' // 认证API +export * from './graph_api' // 图谱API // 导出基础工具函数 export { apiRequest, apiGet, apiPost, apiPut, apiDelete } from './base' /** - * 权限说明: + * API模块说明: * - * 1. public_api.js: 不需要认证就可以访问的API - * - 登录、初始化管理员、获取公共配置等 + * 1. system_api.js: 系统管理API + * - 健康检查、配置管理、信息管理、OCR服务 + * - 权限要求: 部分公开,部分需要管理员权限 * - * 2. auth_api.js: 需要用户认证才能访问的API - * - 权限要求: 任何已登录用户(普通用户、管理员、超级管理员) - * - 聊天功能、个人设置等 + * 2. knowledge_api.js: 知识库管理API + * - 数据库管理、文档管理、查询接口、文件管理 + * - 权限要求: 管理员权限 * - * 3. admin_api.js: 需要管理员权限才能访问的API - * - 权限要求: admin 或 superadmin - * - 用户管理、知识库管理、系统配置等 + * 3. auth_api.js: 认证API + * - 用户认证、用户管理 * - * 注意:本模块已处理权限验证和请求头,使用时无需再手动添加认证头 + * 4. graph_api.js: 图谱API + * - 知识图谱相关功能 + * + * 注意:API模块已处理权限验证和请求头,使用时无需再手动添加认证头 */ \ No newline at end of file diff --git a/web/src/apis/knowledge_api.js b/web/src/apis/knowledge_api.js new file mode 100644 index 00000000..52cf1cc3 --- /dev/null +++ b/web/src/apis/knowledge_api.js @@ -0,0 +1,222 @@ +import { apiGet, apiPost, apiPut, apiDelete } from './base' +import { useUserStore } from '@/stores/user' + +/** + * 知识库管理API模块 + * 包含数据库管理、文档管理、查询接口等功能 + */ + +// 检查当前用户是否有管理员权限 +const checkAdminPermission = () => { + const userStore = useUserStore() + if (!userStore.isAdmin) { + throw new Error('需要管理员权限') + } + return true +} + +// ============================================================================= +// === 数据库管理分组 === +// ============================================================================= + +export const databaseApi = { + /** + * 获取所有知识库 + * @returns {Promise} - 知识库列表 + */ + getDatabases: async () => { + checkAdminPermission() + return apiGet('/api/knowledge/databases', {}, true) + }, + + /** + * 创建知识库 + * @param {Object} databaseData - 知识库数据 + * @returns {Promise} - 创建结果 + */ + createDatabase: async (databaseData) => { + checkAdminPermission() + return apiPost('/api/knowledge/databases', { + database_name: databaseData.database_name, + description: databaseData.description, + embed_model_name: databaseData.embed_model_name, + kb_type: databaseData.kb_type || 'lightrag', + ...databaseData.extra_config + }, {}, true) + }, + + /** + * 获取知识库详细信息 + * @param {string} dbId - 知识库ID + * @returns {Promise} - 知识库信息 + */ + getDatabaseInfo: async (dbId) => { + checkAdminPermission() + return apiGet(`/api/knowledge/databases/${dbId}`, {}, true) + }, + + /** + * 更新知识库信息 + * @param {string} dbId - 知识库ID + * @param {Object} updateData - 更新数据 + * @returns {Promise} - 更新结果 + */ + updateDatabase: async (dbId, updateData) => { + checkAdminPermission() + return apiPut(`/api/knowledge/databases/${dbId}`, updateData, {}, true) + }, + + /** + * 删除知识库 + * @param {string} dbId - 知识库ID + * @returns {Promise} - 删除结果 + */ + deleteDatabase: async (dbId) => { + checkAdminPermission() + return apiDelete(`/api/knowledge/databases/${dbId}`, {}, true) + } +} + +// ============================================================================= +// === 文档管理分组 === +// ============================================================================= + +export const documentApi = { + /** + * 添加文档到知识库 + * @param {string} dbId - 知识库ID + * @param {Array} items - 文档列表 + * @param {Object} params - 处理参数 + * @returns {Promise} - 添加结果 + */ + addDocuments: async (dbId, items, params = {}) => { + checkAdminPermission() + return apiPost(`/api/knowledge/databases/${dbId}/documents`, { + items, + params + }, {}, true) + }, + + /** + * 获取文档信息 + * @param {string} dbId - 知识库ID + * @param {string} docId - 文档ID + * @returns {Promise} - 文档信息 + */ + getDocumentInfo: async (dbId, docId) => { + checkAdminPermission() + return apiGet(`/api/knowledge/databases/${dbId}/documents/${docId}`, {}, true) + }, + + /** + * 删除文档 + * @param {string} dbId - 知识库ID + * @param {string} docId - 文档ID + * @returns {Promise} - 删除结果 + */ + deleteDocument: async (dbId, docId) => { + checkAdminPermission() + return apiDelete(`/api/knowledge/databases/${dbId}/documents/${docId}`, {}, true) + } +} + +// ============================================================================= +// === 查询分组 === +// ============================================================================= + +export const queryApi = { + /** + * 查询知识库 + * @param {string} dbId - 知识库ID + * @param {string} query - 查询文本 + * @param {Object} meta - 查询参数 + * @returns {Promise} - 查询结果 + */ + queryKnowledgeBase: async (dbId, query, meta = {}) => { + checkAdminPermission() + return apiPost(`/api/knowledge/databases/${dbId}/query`, { + query, + meta + }, {}, true) + }, + + /** + * 测试查询知识库 + * @param {string} dbId - 知识库ID + * @param {string} query - 查询文本 + * @param {Object} meta - 查询参数 + * @returns {Promise} - 测试结果 + */ + queryTest: async (dbId, query, meta = {}) => { + checkAdminPermission() + return apiPost(`/api/knowledge/databases/${dbId}/query-test`, { + query, + meta + }, {}, true) + }, + + /** + * 获取知识库查询参数 + * @param {string} dbId - 知识库ID + * @returns {Promise} - 查询参数 + */ + getKnowledgeBaseQueryParams: async (dbId) => { + checkAdminPermission() + return apiGet(`/api/knowledge/databases/${dbId}/query-params`, {}, true) + } +} + +// ============================================================================= +// === 文件管理分组 === +// ============================================================================= + +export const fileApi = { + /** + * 上传文件 + * @param {File} file - 文件对象 + * @param {string} dbId - 知识库ID(可选) + * @returns {Promise} - 上传结果 + */ + uploadFile: async (file, dbId = null) => { + checkAdminPermission() + + const formData = new FormData() + formData.append('file', file) + + const url = dbId + ? `/api/knowledge/files/upload?db_id=${dbId}` + : '/api/knowledge/files/upload' + + return apiPost(url, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }, true) + } +} + +// ============================================================================= +// === 知识库类型分组 === +// ============================================================================= + +export const typeApi = { + /** + * 获取支持的知识库类型 + * @returns {Promise} - 知识库类型列表 + */ + getKnowledgeBaseTypes: async () => { + checkAdminPermission() + return apiGet('/api/knowledge/types', {}, true) + }, + + /** + * 获取知识库统计信息 + * @returns {Promise} - 统计信息 + */ + getStatistics: async () => { + checkAdminPermission() + return apiGet('/api/knowledge/stats', {}, true) + } +} + + \ No newline at end of file diff --git a/web/src/apis/public_api.js b/web/src/apis/public_api.js deleted file mode 100644 index 134de838..00000000 --- a/web/src/apis/public_api.js +++ /dev/null @@ -1,74 +0,0 @@ -import { apiGet, apiPost } from './base' - -/** - * 公共API模块 - * 包含所有不需要认证的公共接口 - */ - -// 登录相关API -export const authApi = { - /** - * 用户登录 - * @param {Object} credentials - 登录凭证 - * @returns {Promise} - 登录结果 - */ - login: (credentials) => { - const formData = new FormData() - formData.append('username', credentials.username) - formData.append('password', credentials.password) - - return apiRequest('/api/auth/token', { - method: 'POST', - body: formData - }, false) - }, - - /** - * 检查是否是首次运行 - * @returns {Promise} - 是否首次运行 - */ - checkFirstRun: () => apiGet('/api/auth/check-first-run'), - - /** - * 初始化管理员账户 - * @param {Object} adminData - 管理员账户数据 - * @returns {Promise} - 初始化结果 - */ - initializeAdmin: (adminData) => apiPost('/api/auth/initialize', adminData), -} - -// 配置相关API -export const configApi = { - /** - * 获取系统配置 - * @returns {Promise} - 系统配置 - */ - getConfig: () => apiGet('/api/config'), -} - -// 系统信息配置API -export const infoApi = { - /** - * 获取系统信息配置 - * @returns {Promise} - 系统信息配置 - */ - getInfoConfig: () => apiGet('/api/info'), - - /** - * 重新加载信息配置 - * @returns {Promise} - 重新加载结果 - */ - reloadInfoConfig: () => apiGet('/api/info/reload') -} - -// 健康检查API -export const healthApi = { - /** - * 系统健康检查 - * @returns {Promise} - 健康检查结果 - */ - check: () => apiGet('/api/health'), -} - -// 从base.js导入apiRequest以支持FormData -import { apiRequest } from './base' \ No newline at end of file diff --git a/web/src/apis/system_api.js b/web/src/apis/system_api.js new file mode 100644 index 00000000..b4361c59 --- /dev/null +++ b/web/src/apis/system_api.js @@ -0,0 +1,183 @@ +import { apiGet, apiPost } from './base' +import { useUserStore } from '@/stores/user' + +/** + * 系统管理API模块 + * 包含系统配置、健康检查、信息管理等功能 + */ + +// 检查当前用户是否有管理员权限 +const checkAdminPermission = () => { + const userStore = useUserStore() + if (!userStore.isAdmin) { + throw new Error('需要管理员权限') + } + return true +} + +// 检查当前用户是否有超级管理员权限 +const checkSuperAdminPermission = () => { + const userStore = useUserStore() + if (!userStore.isSuperAdmin) { + throw new Error('需要超级管理员权限') + } + return true +} + +// ============================================================================= +// === 健康检查分组 === +// ============================================================================= + +export const healthApi = { + /** + * 系统健康检查(公开接口) + * @returns {Promise} - 健康检查结果 + */ + checkHealth: () => apiGet('/api/system/health'), + + /** + * OCR服务健康检查 + * @returns {Promise} - OCR服务健康状态 + */ + checkOcrHealth: async () => { + checkAdminPermission() + return apiGet('/api/system/health/ocr', {}, true) + } +} + +// ============================================================================= +// === 配置管理分组 === +// ============================================================================= + +export const configApi = { + /** + * 获取系统配置 + * @returns {Promise} - 系统配置 + */ + getConfig: async () => { + checkAdminPermission() + return apiGet('/api/system/config', {}, true) + }, + + /** + * 更新单个配置项 + * @param {string} key - 配置键 + * @param {any} value - 配置值 + * @returns {Promise} - 更新结果 + */ + updateConfig: async (key, value) => { + checkAdminPermission() + return apiPost('/api/system/config', { key, value }, {}, true) + }, + + /** + * 批量更新配置项 + * @param {Object} items - 配置项对象 + * @returns {Promise} - 更新结果 + */ + updateConfigBatch: async (items) => { + checkAdminPermission() + return apiPost('/api/system/config/update', items, {}, true) + }, + + /** + * 重启系统(仅超级管理员) + * @returns {Promise} - 重启结果 + */ + restartSystem: async () => { + checkSuperAdminPermission() + return apiPost('/api/system/restart', {}, {}, true) + }, + + /** + * 获取系统日志 + * @returns {Promise} - 系统日志 + */ + getLogs: async () => { + checkAdminPermission() + return apiGet('/api/system/logs', {}, true) + } +} + +// ============================================================================= +// === 信息管理分组 === +// ============================================================================= + +export const infoApi = { + /** + * 获取系统信息配置(公开接口) + * @returns {Promise} - 系统信息配置 + */ + getInfoConfig: () => apiGet('/api/system/info'), + + /** + * 重新加载信息配置 + * @returns {Promise} - 重新加载结果 + */ + reloadInfoConfig: async () => { + checkAdminPermission() + return apiPost('/api/system/info/reload', {}, {}, true) + } +} + +// ============================================================================= +// === OCR服务分组 === +// ============================================================================= + +export const ocrApi = { + /** + * 获取OCR服务统计信息 + * @returns {Promise} - OCR统计信息 + */ + getStats: async () => { + checkAdminPermission() + return apiGet('/api/system/ocr/stats', {}, true) + }, + + /** + * 获取OCR服务健康状态 + * @returns {Promise} - OCR健康状态 + */ + getHealth: async () => { + checkAdminPermission() + return apiGet('/api/system/ocr/health', {}, true) + } +} + +// ============================================================================= +// === 智能体配置分组 === +// ============================================================================= + +export const agentConfigApi = { + /** + * 获取智能体配置 + * @param {string} agentName - 智能体名称 + * @returns {Promise} - 智能体配置 + */ + getAgentConfig: async (agentName) => { + checkAdminPermission() + return apiGet(`/api/chat/agent/${agentName}/config`, {}, true) + }, + + /** + * 保存智能体配置 + * @param {string} agentName - 智能体名称 + * @param {Object} config - 配置对象 + * @returns {Promise} - 保存结果 + */ + saveAgentConfig: async (agentName, config) => { + checkAdminPermission() + return apiPost(`/api/chat/agent/${agentName}/config`, config, {}, true) + }, + + /** + * 设置默认智能体 + * @param {string} agentId - 智能体ID + * @returns {Promise} - 设置结果 + */ + setDefaultAgent: async (agentId) => { + checkAdminPermission() + return apiPost('/api/chat/agent/default', { agent_id: agentId }, {}, true) + } +} + diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 07fd0ad9..7585003c 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -1056,9 +1056,9 @@ const getAgentHistory = async () => { } try { - console.log(`正在获取智能体[${props.agentId}]的历史记录,对话ID: ${currentChatId.value}`); + console.debug(`正在获取智能体[${props.agentId}]的历史记录,对话ID: ${currentChatId.value}`); const response = await chatApi.getAgentHistory(props.agentId, currentChatId.value); - console.log('智能体历史记录:', response); + console.debug('智能体历史记录:', response); // 如果成功获取历史记录并且是数组 if (response && Array.isArray(response.history)) { @@ -1133,12 +1133,13 @@ const convertServerHistoryToMessages = (serverHistory) => { } } - console.log("conversations", conversations); + console.debug("conversations", conversations); return conversations; }; // 组件挂载时加载状态 onMounted(async () => { + // 独立页面模式的时候从这里加载,在AgentView页面可能会重复加载 await initAll(); }); @@ -1147,7 +1148,7 @@ onMounted(async () => { onMounted(() => { watch(() => props.agentId, async (newAgentId, oldAgentId) => { try { - console.log("智能体ID变化", oldAgentId, "->", newAgentId); + console.debug("智能体ID变化", oldAgentId, "->", newAgentId); // 如果变化了,重置会话并加载新数据 if (newAgentId !== oldAgentId) { diff --git a/web/src/components/ChatComponent.vue b/web/src/components/ChatComponent.vue index 1f4510b8..97c3addb 100644 --- a/web/src/components/ChatComponent.vue +++ b/web/src/components/ChatComponent.vue @@ -156,7 +156,7 @@ import MessageComponent from '@/components/MessageComponent.vue' import RefsSidebar from '@/components/RefsSidebar.vue' import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue' import { chatApi } from '@/apis/auth_api' -import { knowledgeBaseApi } from '@/apis/admin_api' +import { databaseApi } from '@/apis/knowledge_api' const props = defineProps({ conv: Object, @@ -461,7 +461,7 @@ const loadDatabases = () => { } try { - knowledgeBaseApi.getDatabases() + databaseApi.getDatabases() .then(data => { console.log(data) opts.databases = data.databases diff --git a/web/src/components/DebugComponent.vue b/web/src/components/DebugComponent.vue index 51e641f3..3719a542 100644 --- a/web/src/components/DebugComponent.vue +++ b/web/src/components/DebugComponent.vue @@ -114,7 +114,7 @@ import { RobotOutlined } from '@ant-design/icons-vue'; import dayjs from 'dayjs'; -import { logApi, systemConfigApi } from '@/apis/admin_api'; +import { configApi, agentConfigApi } from '@/apis/system_api'; import { chatApi } from '@/apis/auth_api'; const configStore = useConfigStore() @@ -210,7 +210,7 @@ const fetchLogs = async () => { state.fetching = true; try { error.value = ''; - const logData = await logApi.getLogs(); + const logData = await configApi.getLogs(); state.rawLogs = logData.log.split('\n').filter(line => line.trim()); await nextTick(); @@ -399,7 +399,7 @@ const printAgentConfig = async () => { // 获取每个智能体的配置 for (const agent of agentsData.agents) { try { - const agentConfig = await systemConfigApi.getAgentConfig(agent.name); + const agentConfig = await agentConfigApi.getAgentConfig(agent.name); console.log(`智能体 "${agent.name}" 配置:`, JSON.stringify(agentConfig, null, 2)); } catch (err) { console.log(`智能体 "${agent.name}" 配置获取失败:`, err.message); diff --git a/web/src/components/KnowledgeGraphViewer.vue b/web/src/components/KnowledgeGraphViewer.vue index c84c7d89..c24e94a3 100644 --- a/web/src/components/KnowledgeGraphViewer.vue +++ b/web/src/components/KnowledgeGraphViewer.vue @@ -11,15 +11,15 @@ :loading="loadingDatabases" @change="onDatabaseChange" > - {{ db.name }} ({{ db.row_count || 0 }} 文件) - + -
-
import { ref, reactive, onMounted, onUnmounted, computed, watch, nextTick } from 'vue' import { message } from 'ant-design-vue' -import { - SearchOutlined, - ReloadOutlined, +import { + SearchOutlined, + ReloadOutlined, ClearOutlined, - CloseOutlined, - PlusOutlined, - MinusOutlined, - HomeOutlined + CloseOutlined, + PlusOutlined, + MinusOutlined, + HomeOutlined } from '@ant-design/icons-vue' import Sigma from 'sigma' import { NodeBorderProgram } from '@sigma/node-border' import EdgeCurveProgram, { EdgeCurvedArrowProgram } from '@sigma/edge-curve' import { EdgeArrowProgram } from 'sigma/rendering' -import { - getAvailableDatabases, - getGraphLabels, - getSubgraph, - getFullGraph, - getGraphStats, - expandNodeNeighbors -} from '@/apis/graph_api' +import { lightragApi } from '@/apis/graph_api' import { useGraphStore } from '@/stores/graphStore' import '@/assets/css/sigma.css' @@ -340,13 +333,13 @@ const loadAvailableDatabases = async () => { await loadGraphLabels(selectedDatabase.value) return } - + loadingDatabases.value = true try { - const response = await getAvailableDatabases() + const response = await lightragApi.getDatabases() if (response.success) { availableDatabases.value = response.data.databases || [] - + // 如果有初始数据库 ID,优先选择它 if (props.initialDatabaseId && availableDatabases.value.some(db => db.db_id === props.initialDatabaseId)) { selectedDatabase.value = props.initialDatabaseId @@ -367,10 +360,10 @@ const loadAvailableDatabases = async () => { // 加载图标签 const loadGraphLabels = async (dbId) => { if (!dbId) return - + loadingLabels.value = true try { - const response = await getGraphLabels(dbId) + const response = await lightragApi.getLabels(dbId) if (response.success) { availableLabels.value = response.data.labels || [] } @@ -385,16 +378,16 @@ const loadGraphLabels = async (dbId) => { // 数据库切换处理 const onDatabaseChange = async (dbId) => { if (!dbId) return - + selectedDatabase.value = dbId selectedLabel.value = '*' - + // 清空当前图谱 clearGraph() - + // 加载新数据库的标签 await loadGraphLabels(dbId) - + message.info(`已切换到数据库: ${availableDatabases.value.find(db => db.db_id === dbId)?.name || dbId}`) } @@ -472,14 +465,14 @@ const registerEvents = () => { const graph = sigmaInstance.getGraph() if (graph.hasNode(node)) { console.log('Clicked node:', node) - + // 立即设置选中节点,显示详情面板 graphStore.setSelectedNode(node, false) // 先不移动相机 - + // 获取节点数据并确保设置成功 const nodeData = graph.getNodeAttributes(node) console.log('Node data:', nodeData) - + // 延迟一点后再移动相机,确保详情面板已显示 setTimeout(() => { if (graphStore.selectedNode === node) { @@ -487,7 +480,7 @@ const registerEvents = () => { graphStore.setSelectedNode(node, true) // 现在移动相机 } }, 100) - + } else { console.warn('Clicked node does not exist in graph:', node) } @@ -629,13 +622,13 @@ const loadGraphData = async () => { try { const [graphResponse, statsResponse] = await Promise.all([ - getSubgraph({ + lightragApi.getSubgraph({ db_id: selectedDatabase.value, node_label: selectedLabel.value || '*', max_depth: searchParams.max_depth, max_nodes: searchParams.max_nodes }), - getGraphStats(selectedDatabase.value) + lightragApi.getStats(selectedDatabase.value) ]) if (graphResponse.success && statsResponse.success) { @@ -798,7 +791,7 @@ const expandNode = async (nodeId) => { expanding.value = true try { - const response = await expandNodeNeighbors({ + const response = await lightragApi.getSubgraph({ db_id: selectedDatabase.value, node_label: nodeId, max_depth: 1, @@ -862,18 +855,18 @@ const resetCamera = () => { try { const camera = sigmaInstance.getCamera() const graph = sigmaInstance.getGraph() - + // 如果图为空,直接重置 if (graph.order === 0) { camera.animatedReset({ duration: 500 }) return } - + // 计算图的边界以确保所有节点都可见 const nodes = graph.nodes() if (nodes.length > 0) { const bounds = { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity } - + nodes.forEach(node => { const attrs = graph.getNodeAttributes(node) if (typeof attrs.x === 'number' && typeof attrs.y === 'number') { @@ -883,13 +876,13 @@ const resetCamera = () => { bounds.maxY = Math.max(bounds.maxY, attrs.y) } }) - + // 只有在边界有效时才使用fitBounds if (isFinite(bounds.minX) && isFinite(bounds.maxX) && isFinite(bounds.minY) && isFinite(bounds.maxY)) { const centerX = (bounds.minX + bounds.maxX) / 2 const centerY = (bounds.minY + bounds.maxY) / 2 const padding = 50 - + camera.animate({ x: centerX, y: centerY, @@ -905,7 +898,7 @@ const resetCamera = () => { } else { camera.animatedReset({ duration: 500 }) } - + console.log('Camera reset completed') message.success('视图已重置') } catch (error) { @@ -940,16 +933,16 @@ const getEntityColor = (entityType) => { // 面板拖拽功能 const startDragPanel = (type, event) => { event.preventDefault() - + if (!sigmaContainer.value) return - + const currentPosition = type === 'node' ? nodePanelPosition.value : edgePanelPosition.value - + // 获取容器位置,计算相对于容器的鼠标位置 const containerRect = sigmaContainer.value.getBoundingClientRect() const relativeX = event.clientX - containerRect.left const relativeY = event.clientY - containerRect.top - + dragging.value = { active: true, type: type, @@ -958,33 +951,33 @@ const startDragPanel = (type, event) => { initialX: currentPosition.x, initialY: currentPosition.y } - + document.addEventListener('mousemove', onDragPanel) document.addEventListener('mouseup', stopDragPanel) } const onDragPanel = (event) => { if (!dragging.value.active || !sigmaContainer.value) return - + // 获取容器位置和尺寸 const containerRect = sigmaContainer.value.getBoundingClientRect() - + // 计算当前鼠标相对于容器的位置 const currentRelativeX = event.clientX - containerRect.left const currentRelativeY = event.clientY - containerRect.top - + // 计算拖拽偏移 const deltaX = currentRelativeX - dragging.value.startX const deltaY = currentRelativeY - dragging.value.startY - + const maxX = containerRect.width - 320 // 面板宽度为300px + 一些边距 const maxY = containerRect.height - 200 // 面板高度约为200px - + const newPosition = { x: Math.max(0, Math.min(maxX, dragging.value.initialX + deltaX)), y: Math.max(0, Math.min(maxY, dragging.value.initialY + deltaY)) } - + if (dragging.value.type === 'node') { nodePanelPosition.value = newPosition } else { @@ -1008,7 +1001,7 @@ onUnmounted(() => { // 清理拖拽事件监听器 document.removeEventListener('mousemove', onDragPanel) document.removeEventListener('mouseup', stopDragPanel) - + if (sigmaInstance) { sigmaInstance.kill() sigmaInstance = null @@ -1029,7 +1022,7 @@ watch(() => graphStore.selectedNode, (nodeId) => { if (nodeId && graphStore.moveToSelectedNode && sigmaInstance) { try { const graph = sigmaInstance.getGraph() - + // 检查节点是否存在 if (!graph.hasNode(nodeId)) { console.warn('Selected node does not exist in graph:', nodeId) @@ -1038,7 +1031,7 @@ watch(() => graphStore.selectedNode, (nodeId) => { } const nodeAttributes = graph.getNodeAttributes(nodeId) - + // 检查节点属性是否有效 if (!nodeAttributes || typeof nodeAttributes.x !== 'number' || typeof nodeAttributes.y !== 'number') { console.warn('Invalid node attributes for node:', nodeId, nodeAttributes) @@ -1048,40 +1041,40 @@ watch(() => graphStore.selectedNode, (nodeId) => { const camera = sigmaInstance.getCamera() const currentState = camera.getState() - - console.log('Moving camera to node:', nodeId, { - x: nodeAttributes.x, + + console.log('Moving camera to node:', nodeId, { + x: nodeAttributes.x, y: nodeAttributes.y, - currentRatio: currentState.ratio + currentRatio: currentState.ratio }) - + // 计算合适的缩放比例,避免过度缩放 const currentRatio = currentState.ratio || 1.0 const targetRatio = Math.max(0.1, Math.min(currentRatio * 0.7, 0.6)) - + // 验证节点位置是否有效 const isValidPosition = ( - typeof nodeAttributes.x === 'number' && + typeof nodeAttributes.x === 'number' && typeof nodeAttributes.y === 'number' && - !isNaN(nodeAttributes.x) && + !isNaN(nodeAttributes.x) && !isNaN(nodeAttributes.y) && - isFinite(nodeAttributes.x) && + isFinite(nodeAttributes.x) && isFinite(nodeAttributes.y) ) - + if (!isValidPosition) { console.warn('Invalid node position, skipping camera movement:', nodeAttributes) return } - + // 移动相机到节点位置,使用安全的缩放比例 camera.animate( - { - x: nodeAttributes.x, - y: nodeAttributes.y, + { + x: nodeAttributes.x, + y: nodeAttributes.y, ratio: targetRatio // 使用计算出的安全缩放比例 }, - { + { duration: 600 // 适中的动画时间 } ) @@ -1216,7 +1209,7 @@ watch(() => graphStore.selectedNode, (nodeId) => { .detail-item { display: flex; margin-bottom: 12px; - + &:last-child { margin-bottom: 0; } @@ -1312,20 +1305,20 @@ watch(() => graphStore.selectedNode, (nodeId) => { max-height: 240px; overflow-y: auto; overflow-x: hidden; - + /* 自定义滚动条样式 */ &::-webkit-scrollbar { width: 4px; } - + &::-webkit-scrollbar-track { background: transparent; } - + &::-webkit-scrollbar-thumb { background: #d9d9d9; border-radius: 2px; - + &:hover { background: #bfbfbf; } @@ -1342,7 +1335,7 @@ watch(() => graphStore.selectedNode, (nodeId) => { font-size: 12px; min-width: 0; transition: background-color 0.2s ease; - + span { white-space: nowrap; overflow: hidden; @@ -1351,7 +1344,7 @@ watch(() => graphStore.selectedNode, (nodeId) => { min-width: 0; color: #595959; } - + &:hover { background-color: #f5f5f5; } diff --git a/web/src/main.js b/web/src/main.js index 0249a8a6..753db609 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -18,8 +18,6 @@ app.use(Antd) // 预加载信息配置 import { useInfoStore } from '@/stores/info' const infoStore = useInfoStore() -infoStore.loadInfoConfig().then(() => { - console.log('应用信息配置预加载完成') -}) +infoStore.loadInfoConfig() app.mount('#app') diff --git a/web/src/stores/config.js b/web/src/stores/config.js index 91b78a54..e3c73c5d 100644 --- a/web/src/stores/config.js +++ b/web/src/stores/config.js @@ -1,6 +1,6 @@ import { ref, computed } from 'vue' import { defineStore } from 'pinia' -import { systemConfigApi } from '@/apis/admin_api' +import { configApi } from '@/apis/system_api' export const useCounterStore = defineStore('counter', () => { const count = ref(0) @@ -21,7 +21,7 @@ export const useConfigStore = defineStore('config', () => { function setConfigValue(key, value) { config.value[key] = value - systemConfigApi.updateConfigItems({ [key]: value }) + configApi.updateConfigBatch({ [key]: value }) .then(data => { console.debug('Success:', data) setConfig(data) @@ -35,7 +35,7 @@ export const useConfigStore = defineStore('config', () => { } // 发送到服务器 - systemConfigApi.updateConfigItems(items) + configApi.updateConfigBatch(items) .then(data => { console.debug('Success:', data) setConfig(data) @@ -43,7 +43,7 @@ export const useConfigStore = defineStore('config', () => { } function refreshConfig() { - systemConfigApi.getSystemConfig() + configApi.getConfig() .then(data => { console.log("config", data) setConfig(data) diff --git a/web/src/stores/database.js b/web/src/stores/database.js index 36c9b7ea..21846553 100644 --- a/web/src/stores/database.js +++ b/web/src/stores/database.js @@ -1,6 +1,6 @@ import { ref, computed } from 'vue' import { defineStore } from 'pinia' -import { knowledgeBaseApi } from '@/apis/admin_api' +import { databaseApi } from '@/apis/knowledge_api' export const useDatabaseStore = defineStore('database', () => { const db = ref({}) @@ -9,7 +9,7 @@ export const useDatabaseStore = defineStore('database', () => { } async function refreshDatabase() { - const res = await knowledgeBaseApi.getDatabases() + const res = await databaseApi.getDatabases() console.log("database", res) setDatabase(res.databases) } diff --git a/web/src/stores/info.js b/web/src/stores/info.js index 5e5b8da3..ecbee59a 100644 --- a/web/src/stores/info.js +++ b/web/src/stores/info.js @@ -1,6 +1,6 @@ import { ref, computed } from 'vue' import { defineStore } from 'pinia' -import { infoApi } from '@/apis/public_api' +import { infoApi } from '@/apis/system_api' export const useInfoStore = defineStore('info', () => { // 状态 @@ -53,7 +53,7 @@ export const useInfoStore = defineStore('info', () => { if (response.success && response.data) { setInfoConfig(response.data) - console.log('信息配置加载成功:', response.data) + console.debug('信息配置加载成功:', response.data) return response.data } else { console.warn('信息配置加载失败,使用默认配置') @@ -74,7 +74,7 @@ export const useInfoStore = defineStore('info', () => { if (response.success && response.data) { setInfoConfig(response.data) - console.log('信息配置重新加载成功:', response.data) + console.debug('信息配置重新加载成功:', response.data) return response.data } else { console.warn('信息配置重新加载失败') diff --git a/web/src/views/AgentView.vue b/web/src/views/AgentView.vue index 8d154cc4..6a766611 100644 --- a/web/src/views/AgentView.vue +++ b/web/src/views/AgentView.vue @@ -229,7 +229,7 @@ import AgentChatComponent from '@/components/AgentChatComponent.vue'; import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue'; import { useUserStore } from '@/stores/user'; import { chatApi } from '@/apis/auth_api'; -import { systemConfigApi } from '@/apis/admin_api'; +import { agentConfigApi } from '@/apis/system_api'; // 路由 const router = useRouter(); @@ -276,7 +276,7 @@ const setAsDefaultAgent = async () => { if (!selectedAgentId.value || !userStore.isAdmin) return; try { - await systemConfigApi.setDefaultAgent(selectedAgentId.value); + await agentConfigApi.setDefaultAgent(selectedAgentId.value); defaultAgentId.value = selectedAgentId.value; message.success('已将当前智能体设为默认'); } catch (error) { @@ -326,7 +326,7 @@ const fetchDefaultAgent = async () => { try { const data = await chatApi.getDefaultAgent(); defaultAgentId.value = data.default_agent_id; - console.log("Default agent ID:", defaultAgentId.value); + console.debug("Default agent ID:", defaultAgentId.value); } catch (error) { console.error('获取默认智能体错误:', error); } @@ -394,7 +394,7 @@ const loadAgentConfig = async () => { try { // 从服务器加载配置 - const response = await systemConfigApi.getAgentConfig(selectedAgentId.value); + const response = await agentConfigApi.getAgentConfig(selectedAgentId.value); if (response.success && response.config) { // 合并服务器配置 Object.keys(response.config).forEach(key => { @@ -408,7 +408,7 @@ const loadAgentConfig = async () => { } } }); - console.log(`从服务器加载 ${selectedAgentId.value} 配置成功, ${JSON.stringify(agentConfig.value)}`); + // console.log(`从服务器加载 ${selectedAgentId.value} 配置成功, ${JSON.stringify(agentConfig.value)}`); } } catch (error) { console.error('从服务器加载配置出错:', error); @@ -428,7 +428,7 @@ const saveConfig = async () => { try { // 保存配置到服务器 - await systemConfigApi.saveAgentConfig(selectedAgentId.value, agentConfig.value); + await agentConfigApi.saveAgentConfig(selectedAgentId.value, agentConfig.value); // 提示保存成功 message.success('配置已保存到服务器'); console.log("保存配置:", agentConfig.value); @@ -447,7 +447,7 @@ const resetConfig = async () => { try { // 保存空配置到服务器,相当于重置 - await systemConfigApi.saveAgentConfig(selectedAgentId.value, {}); + await agentConfigApi.saveAgentConfig(selectedAgentId.value, {}); // 重新加载默认配置 await loadAgentConfig(); message.info('配置已重置'); diff --git a/web/src/views/DataBaseInfoView.vue b/web/src/views/DataBaseInfoView.vue index 96713ddc..6916f50c 100644 --- a/web/src/views/DataBaseInfoView.vue +++ b/web/src/views/DataBaseInfoView.vue @@ -136,7 +136,7 @@ name="file" :multiple="true" :disabled="state.chunkLoading" - :action="'/api/data/upload?db_id=' + databaseId" + :action="'/api/knowledge/files/upload?db_id=' + databaseId" :headers="getAuthHeaders()" @change="handleFileUpload" @drop="handleDrop" @@ -435,7 +435,8 @@ import { message, Modal } from 'ant-design-vue'; import { useRoute, useRouter } from 'vue-router'; import { useConfigStore } from '@/stores/config' import { useUserStore } from '@/stores/user' -import { knowledgeBaseApi, ocrApi } from '@/apis/admin_api' +import { databaseApi, documentApi, queryApi, fileApi } from '@/apis/knowledge_api' +import { ocrApi } from '@/apis/system_api' import { ReadOutlined, LeftOutlined, @@ -509,7 +510,7 @@ const checkOcrHealth = async () => { state.ocrHealthChecking = true; try { - const healthData = await ocrApi.checkHealth(); + const healthData = await ocrApi.getHealth(); ocrHealthStatus.value = healthData.services; } catch (error) { console.error('OCR健康检查失败:', error); @@ -636,7 +637,7 @@ const loadQueryParams = async () => { state.queryParamsLoading = true try { - const response = await knowledgeBaseApi.getKbQueryParams(databaseId.value) + const response = await queryApi.getKnowledgeBaseQueryParams(databaseId.value) queryParams.value = response.params?.options || [] // 初始化meta对象的默认值 @@ -677,10 +678,7 @@ const onQuery = () => { meta.db_id = database.value.db_id try { - knowledgeBaseApi.queryTest({ - query: queryText.value.trim(), - meta: meta - }) + queryApi.queryTest(database.value.db_id, queryText.value.trim(), meta) .then(data => { console.log(data) queryResult.value = data @@ -737,7 +735,7 @@ const deleteDatabse = () => { cancelText: '取消', onOk: () => { state.lock = true - knowledgeBaseApi.deleteDatabase(databaseId.value) + databaseApi.deleteDatabase(databaseId.value) .then(data => { console.log(data) message.success(data.message || '删除成功') @@ -774,7 +772,7 @@ const openFileDetail = (record) => { state.lock = true; try { - knowledgeBaseApi.getDocumentDetail(databaseId.value, record.file_id) + documentApi.getDocumentInfo(databaseId.value, record.file_id) .then(data => { console.log(data); if (data.status == "failed") { @@ -837,7 +835,7 @@ const getDatabaseInfo = () => { state.lock = true state.databaseLoading = true return new Promise((resolve, reject) => { - knowledgeBaseApi.getDatabaseInfo(db_id) + databaseApi.getDatabaseInfo(db_id) .then(async data => { database.value = data // 加载查询参数 @@ -859,7 +857,7 @@ const getDatabaseInfo = () => { const deleteFile = (fileId) => { state.lock = true console.debug("deleteFile", databaseId.value, fileId) - return knowledgeBaseApi.deleteFile(databaseId.value, fileId) + return documentApi.deleteDocument(databaseId.value, fileId) .then(data => { console.log(data) message.success(data.message || '删除成功') @@ -964,11 +962,7 @@ const addFiles = (items, contentType = 'file') => { content_type: contentType }; - knowledgeBaseApi.addFiles({ - db_id: databaseId.value, - items: items, - params: params - }) + documentApi.addDocuments(databaseId.value, items, params) .then(data => { console.log('处理结果:', data); if (data.status === 'success') { @@ -1116,7 +1110,7 @@ const handleEditSubmit = () => { const updateDatabaseInfo = async () => { try { state.lock = true; - const response = await knowledgeBaseApi.updateDatabaseInfo(databaseId.value, { + const response = await databaseApi.updateDatabase(databaseId.value, { name: editForm.name, description: editForm.description }); diff --git a/web/src/views/DataBaseView.vue b/web/src/views/DataBaseView.vue index a76d8c01..8a552673 100644 --- a/web/src/views/DataBaseView.vue +++ b/web/src/views/DataBaseView.vue @@ -131,7 +131,7 @@ import { useConfigStore } from '@/stores/config'; import { message } from 'ant-design-vue' import { ReadFilled, DatabaseOutlined, ThunderboltOutlined } from '@ant-design/icons-vue' import { BookPlus, Database, Zap } from 'lucide-vue-next'; -import { knowledgeBaseApi } from '@/apis/admin_api'; +import { databaseApi, typeApi } from '@/apis/knowledge_api'; import HeaderComponent from '@/components/HeaderComponent.vue'; const route = useRoute() @@ -172,7 +172,7 @@ const supportedKbTypes = ref({}) // 加载支持的知识库类型 const loadSupportedKbTypes = async () => { try { - const data = await knowledgeBaseApi.getSupportedKbTypes() + const data = await typeApi.getKnowledgeBaseTypes() supportedKbTypes.value = data.kb_types console.log('支持的知识库类型:', supportedKbTypes.value) } catch (error) { @@ -190,7 +190,7 @@ const loadSupportedKbTypes = async () => { const loadDatabases = () => { state.loading = true // loadGraph() - knowledgeBaseApi.getDatabases() + databaseApi.getDatabases() .then(data => { console.log(data) databases.value = data.databases @@ -309,7 +309,7 @@ const createDatabase = () => { } } - knowledgeBaseApi.createDatabase(requestData) + databaseApi.createDatabase(requestData) .then(data => { console.log('创建成功:', data) loadDatabases() diff --git a/web/src/views/GraphView.vue b/web/src/views/GraphView.vue index 61f0dcc5..b550d41a 100644 --- a/web/src/views/GraphView.vue +++ b/web/src/views/GraphView.vue @@ -72,7 +72,7 @@ :fileList="fileList" :max-count="1" :disabled="disabled" - action="/api/data/upload" + action="/api/knowledge/files/upload" :headers="getAuthHeaders()" @change="handleFileUpload" @drop="handleDrop" @@ -94,7 +94,7 @@ import { message, Button as AButton } from 'ant-design-vue'; import { useConfigStore } from '@/stores/config'; import { UploadOutlined, SyncOutlined } from '@ant-design/icons-vue'; import HeaderComponent from '@/components/HeaderComponent.vue'; -import { graphApi } from '@/apis/admin_api'; +import { neo4jApi } from '@/apis/graph_api'; import { useUserStore } from '@/stores/user'; const configStore = useConfigStore(); @@ -130,10 +130,10 @@ const unindexedCount = computed(() => { const loadGraphInfo = () => { state.loadingGraphInfo = true - graphApi.getGraphInfo() + neo4jApi.getInfo() .then(data => { console.log(data) - graphInfo.value = data + graphInfo.value = data.data state.loadingGraphInfo = false }) .catch(error => { @@ -187,7 +187,7 @@ const getGraphData = () => { const addDocumentByFile = () => { state.precessing = true const files = fileList.value.filter(file => file.status === 'done').map(file => file.response.file_path) - graphApi.addByJsonl(files[0]) + neo4jApi.addEntities(files[0]) .then((data) => { if (data.status === 'success') { message.success(data.message); @@ -205,7 +205,7 @@ const addDocumentByFile = () => { const loadSampleNodes = () => { state.fetching = true - graphApi.getNodes('neo4j', sampleNodeCount.value) + neo4jApi.getSampleNodes('neo4j', sampleNodeCount.value) .then((data) => { graphData.nodes = data.result.nodes graphData.edges = data.result.edges @@ -242,7 +242,7 @@ const onSearch = () => { } state.searchLoading = true - graphApi.queryNode(state.searchInput) + neo4jApi.queryNode(state.searchInput) .then((data) => { if (!data.result || !data.result.nodes || !data.result.edges) { throw new Error('返回数据格式不正确'); @@ -368,7 +368,7 @@ const indexNodes = () => { } state.indexing = true; - graphApi.indexNodes('neo4j') + neo4jApi.indexEntities('neo4j') .then(data => { message.success(data.message || '索引添加成功'); // 刷新图谱信息 diff --git a/web/src/views/LoginView.vue b/web/src/views/LoginView.vue index 846a4113..439663b5 100644 --- a/web/src/views/LoginView.vue +++ b/web/src/views/LoginView.vue @@ -155,7 +155,7 @@ import { useRouter } from 'vue-router'; import { useUserStore } from '@/stores/user'; import { message } from 'ant-design-vue'; import { chatApi } from '@/apis/auth_api'; -import { authApi, healthApi } from '@/apis/public_api'; +import { healthApi } from '@/apis/system_api'; import { UserOutlined, LockOutlined, WechatOutlined, QrcodeOutlined, ThunderboltOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue'; import loginBg from '@/assets/pics/login_bg.jpg'; @@ -304,7 +304,7 @@ const checkFirstRunStatus = async () => { const checkServerHealth = async () => { try { healthChecking.value = true; - const response = await healthApi.check(); + const response = await healthApi.checkHealth(); if (response.status === 'ok') { serverStatus.value = 'ok'; } else { diff --git a/web/src/views/SettingView.vue b/web/src/views/SettingView.vue index 8d654f11..10005286 100644 --- a/web/src/views/SettingView.vue +++ b/web/src/views/SettingView.vue @@ -131,7 +131,7 @@ import TableConfigComponent from '@/components/TableConfigComponent.vue'; import ModelProvidersComponent from '@/components/ModelProvidersComponent.vue'; import UserManagementComponent from '@/components/UserManagementComponent.vue'; import { notification, Button } from 'ant-design-vue'; -import { systemConfigApi } from '@/apis/admin_api' +import { configApi } from '@/apis/system_api' import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue'; const configStore = useConfigStore() @@ -207,7 +207,7 @@ const sendRestart = () => { console.log('Restarting...') message.loading({ content: '重新加载模型中', key: "restart", duration: 0 }); - systemConfigApi.restartServer() + configApi.restartSystem() .then(() => { console.log('Restarted') message.success({ content: '重新加载完成!', key: "restart", duration: 2 });