2025-06-22 23:07:57 +08:00
|
|
|
import os
|
2025-07-22 17:29:38 +08:00
|
|
|
from collections import deque
|
2025-09-01 22:37:03 +08:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import yaml
|
2025-09-02 01:08:42 +08:00
|
|
|
from fastapi import APIRouter, Body, Depends, HTTPException
|
2024-10-02 20:11:28 +08:00
|
|
|
|
2025-09-23 10:48:44 +08:00
|
|
|
from src.storage.db.models import User
|
2025-09-01 22:37:03 +08:00
|
|
|
from server.utils.auth_middleware import get_admin_user, get_superadmin_user
|
2025-09-02 01:08:42 +08:00
|
|
|
from src import config, graph_base
|
2025-09-22 22:02:57 +08:00
|
|
|
from src.models.chat import test_chat_model_status, test_all_chat_models_status
|
2025-06-22 23:07:57 +08:00
|
|
|
from src.utils.logging_config import logger
|
2024-10-02 20:11:28 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
system = APIRouter(prefix="/system", tags=["system"])
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# === 健康检查分组 ===
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
@system.get("/health")
|
|
|
|
|
async def health_check():
|
|
|
|
|
"""系统健康检查接口(公开接口)"""
|
|
|
|
|
return {"status": "ok", "message": "服务正常运行"}
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
# =============================================================================
|
|
|
|
|
# === 配置管理分组 ===
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
@system.get("/config")
|
|
|
|
|
def get_config(current_user: User = Depends(get_admin_user)):
|
|
|
|
|
"""获取系统配置"""
|
|
|
|
|
return config.dump_config()
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
@system.post("/config")
|
2025-09-01 22:37:03 +08:00
|
|
|
async def update_config_single(key=Body(...), value=Body(...), current_user: User = Depends(get_admin_user)) -> dict:
|
2025-07-22 17:29:38 +08:00
|
|
|
"""更新单个配置项"""
|
|
|
|
|
config[key] = value
|
|
|
|
|
config.save()
|
|
|
|
|
return config.dump_config()
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
@system.post("/config/update")
|
2025-09-01 22:37:03 +08:00
|
|
|
async def update_config_batch(items: dict = Body(...), current_user: User = Depends(get_admin_user)) -> dict:
|
2025-07-22 17:29:38 +08:00
|
|
|
"""批量更新配置项"""
|
|
|
|
|
config.update(items)
|
|
|
|
|
config.save()
|
|
|
|
|
return config.dump_config()
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
@system.post("/restart")
|
|
|
|
|
async def restart_system(current_user: User = Depends(get_superadmin_user)):
|
|
|
|
|
"""重启系统(仅超级管理员)"""
|
|
|
|
|
graph_base.start()
|
|
|
|
|
return {"message": "系统已重启"}
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
@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)
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
log = "".join(last_lines)
|
2025-07-22 17:29:38 +08:00
|
|
|
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)}")
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
# =============================================================================
|
|
|
|
|
# === 信息管理分组 ===
|
|
|
|
|
# =============================================================================
|
2025-05-24 11:29:45 +08:00
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-06-22 23:07:57 +08:00
|
|
|
def load_info_config():
|
|
|
|
|
"""加载信息配置文件"""
|
|
|
|
|
try:
|
|
|
|
|
# 配置文件路径
|
2025-09-23 10:48:44 +08:00
|
|
|
brand_file_path = os.environ.get("YUXI_BRAND_FILE_PATH", "src/config/static/info.local.yaml")
|
2025-08-24 21:07:55 +08:00
|
|
|
config_path = Path(brand_file_path)
|
2025-06-22 23:07:57 +08:00
|
|
|
|
|
|
|
|
# 检查文件是否存在
|
|
|
|
|
if not config_path.exists():
|
2025-06-27 01:47:52 +08:00
|
|
|
logger.debug(f"The config file {config_path} does not exist, using default config")
|
2025-09-23 10:48:44 +08:00
|
|
|
config_path = Path("src/config/static/info.template.yaml")
|
2025-06-22 23:07:57 +08:00
|
|
|
|
|
|
|
|
# 读取配置文件
|
2025-09-01 22:37:03 +08:00
|
|
|
with open(config_path, encoding="utf-8") as file:
|
2025-06-22 23:07:57 +08:00
|
|
|
config = yaml.safe_load(file)
|
|
|
|
|
|
|
|
|
|
return config
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-06-27 01:47:52 +08:00
|
|
|
logger.error(f"Failed to load info config: {e}")
|
2025-06-22 23:07:57 +08:00
|
|
|
return get_default_info_config()
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-06-22 23:07:57 +08:00
|
|
|
def get_default_info_config():
|
|
|
|
|
"""获取默认信息配置"""
|
|
|
|
|
return {
|
2025-09-01 22:37:03 +08:00
|
|
|
"organization": {"name": "江南语析", "logo": "/favicon.svg", "avatar": "/avatar.jpg"},
|
2025-06-22 23:07:57 +08:00
|
|
|
"branding": {
|
2025-08-08 22:57:14 +08:00
|
|
|
"name": "Yuxi-Know",
|
2025-06-22 23:07:57 +08:00
|
|
|
"title": "Yuxi-Know",
|
|
|
|
|
"subtitle": "大模型驱动的知识库管理工具",
|
2025-09-01 22:37:03 +08:00
|
|
|
"description": "结合知识库与知识图谱,提供更准确、更全面的回答",
|
2025-06-22 23:07:57 +08:00
|
|
|
},
|
2025-09-01 22:37:03 +08:00
|
|
|
"features": ["📚 灵活知识库", "🕸️ 知识图谱集成", "🤖 多模型支持"],
|
2025-10-15 22:24:33 +08:00
|
|
|
"footer": {"copyright": "© 江南语析 2025 [WIP] v0.3.0"},
|
2025-06-22 23:07:57 +08:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
@system.get("/info")
|
2025-06-22 23:07:57 +08:00
|
|
|
async def get_info_config():
|
|
|
|
|
"""获取系统信息配置(公开接口,无需认证)"""
|
|
|
|
|
try:
|
|
|
|
|
config = load_info_config()
|
2025-09-01 22:37:03 +08:00
|
|
|
return {"success": True, "data": config}
|
2025-06-22 23:07:57 +08:00
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取信息配置失败: {e}")
|
|
|
|
|
raise HTTPException(status_code=500, detail="获取信息配置失败")
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
@system.post("/info/reload")
|
|
|
|
|
async def reload_info_config(current_user: User = Depends(get_admin_user)):
|
|
|
|
|
"""重新加载信息配置"""
|
2025-06-22 23:07:57 +08:00
|
|
|
try:
|
|
|
|
|
config = load_info_config()
|
2025-09-01 22:37:03 +08:00
|
|
|
return {"success": True, "message": "配置重新加载成功", "data": config}
|
2025-06-22 23:07:57 +08:00
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"重新加载信息配置失败: {e}")
|
|
|
|
|
raise HTTPException(status_code=500, detail="重新加载信息配置失败")
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
# =============================================================================
|
|
|
|
|
# === OCR服务分组 ===
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
@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
|
2025-09-01 22:37:03 +08:00
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
stats = get_ocr_stats()
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
return {"status": "success", "stats": stats, "message": "OCR统计信息获取成功"}
|
2025-07-22 17:29:38 +08:00
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取OCR统计信息失败: {str(e)}")
|
2025-09-01 22:37:03 +08:00
|
|
|
return {"status": "error", "stats": {}, "message": f"获取OCR统计信息失败: {str(e)}"}
|
2025-07-22 17:29:38 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@system.get("/ocr/health")
|
2025-07-21 19:25:07 +08:00
|
|
|
async def check_ocr_services_health(current_user: User = Depends(get_admin_user)):
|
|
|
|
|
"""
|
|
|
|
|
检查所有OCR服务的健康状态
|
|
|
|
|
返回各个OCR服务的可用性信息
|
|
|
|
|
"""
|
2025-10-25 14:26:47 +08:00
|
|
|
from src.plugins.document_processor_factory import DocumentProcessorFactory
|
2025-07-21 19:25:07 +08:00
|
|
|
|
|
|
|
|
try:
|
2025-10-25 14:26:47 +08:00
|
|
|
# 使用统一的健康检查接口
|
|
|
|
|
health_status = DocumentProcessorFactory.check_all_health()
|
|
|
|
|
|
|
|
|
|
# 转换为旧格式以保持API兼容性
|
|
|
|
|
formatted_status = {}
|
|
|
|
|
for service_name, health_info in health_status.items():
|
|
|
|
|
formatted_status[service_name] = {
|
|
|
|
|
"status": health_info.get("status", "unknown"),
|
|
|
|
|
"message": health_info.get("message", ""),
|
|
|
|
|
"details": health_info.get("details", {}),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 计算整体健康状态
|
|
|
|
|
overall_status = (
|
|
|
|
|
"healthy" if any(svc["status"] == "healthy" for svc in formatted_status.values()) else "unhealthy"
|
2025-09-01 22:37:03 +08:00
|
|
|
)
|
2025-07-21 19:25:07 +08:00
|
|
|
|
2025-10-25 14:26:47 +08:00
|
|
|
return {
|
|
|
|
|
"overall_status": overall_status,
|
|
|
|
|
"services": formatted_status,
|
|
|
|
|
"message": "OCR服务健康检查完成",
|
|
|
|
|
}
|
2025-07-21 19:25:07 +08:00
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-10-25 14:26:47 +08:00
|
|
|
logger.error(f"OCR健康检查失败: {str(e)}")
|
|
|
|
|
return {
|
|
|
|
|
"overall_status": "error",
|
|
|
|
|
"services": {},
|
|
|
|
|
"message": f"OCR健康检查失败: {str(e)}",
|
|
|
|
|
}
|
2025-09-22 22:02:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# === 聊天模型状态检查分组 ===
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@system.get("/chat-models/status")
|
|
|
|
|
async def get_chat_model_status(provider: str, model_name: str, current_user: User = Depends(get_admin_user)):
|
|
|
|
|
"""获取指定聊天模型的状态"""
|
|
|
|
|
logger.debug(f"Checking chat model status: {provider}/{model_name}")
|
|
|
|
|
try:
|
|
|
|
|
status = await test_chat_model_status(provider, model_name)
|
|
|
|
|
return {"status": status, "message": "success"}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取聊天模型状态失败 {provider}/{model_name}: {e}")
|
|
|
|
|
return {
|
|
|
|
|
"message": f"获取聊天模型状态失败: {e}",
|
|
|
|
|
"status": {"provider": provider, "model_name": model_name, "status": "error", "message": str(e)},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@system.get("/chat-models/all/status")
|
|
|
|
|
async def get_all_chat_models_status(current_user: User = Depends(get_admin_user)):
|
|
|
|
|
"""获取所有聊天模型的状态"""
|
|
|
|
|
logger.debug("Checking all chat models status")
|
|
|
|
|
try:
|
|
|
|
|
status = await test_all_chat_models_status()
|
|
|
|
|
return {"status": status, "message": "success"}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取所有聊天模型状态失败: {e}")
|
|
|
|
|
return {"message": f"获取所有聊天模型状态失败: {e}", "status": {"models": {}, "total": 0, "available": 0}}
|