190 lines
5.5 KiB
Python
190 lines
5.5 KiB
Python
from fastapi import APIRouter, HTTPException
|
||
from typing import Dict, Any, List
|
||
from pathlib import Path
|
||
import json
|
||
from jingrow.utils.fs import atomic_write_json
|
||
from jingrow.utils.jingrow_api import get_record_id, create_record
|
||
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
@router.post("/jingrow/node-definitions/export")
|
||
async def export_node_definition(payload: Dict[str, Any]):
|
||
"""
|
||
导出节点定义(metadata + schema)为 JSON 文件:backend/nodes/{type}/{type}.json
|
||
"""
|
||
try:
|
||
metadata = payload.get("metadata") or {}
|
||
schema = payload.get("schema") or {}
|
||
node_type = metadata.get("type")
|
||
if not node_type:
|
||
raise ValueError("metadata.type is required")
|
||
|
||
export_data = {"metadata": metadata, **(schema or {})}
|
||
|
||
current_file = Path(__file__).resolve()
|
||
jingrow_root = current_file.parents[1] # 修正路径层级
|
||
new_root = jingrow_root / "ai" / "pagetype" / "local_ai_agent" / "nodes"
|
||
target = new_root / node_type / f"{node_type}.json"
|
||
atomic_write_json(target, export_data)
|
||
return {"success": True, "path": str(target)}
|
||
except Exception as e:
|
||
raise HTTPException(status_code=400, detail=str(e))
|
||
|
||
|
||
@router.post("/jingrow/node-definitions/import-local")
|
||
async def import_local_node_definitions():
|
||
"""
|
||
扫描本地节点定义目录,按 metadata 去重后导入到 Local Ai Node。
|
||
"""
|
||
try:
|
||
current_file = Path(__file__).resolve()
|
||
jingrow_root = current_file.parents[1] # 修正路径层级
|
||
nodes_root = jingrow_root / "ai" / "pagetype" / "local_ai_agent" / "nodes"
|
||
if not nodes_root.exists():
|
||
return {"success": True, "matched": 0, "imported": 0, "skipped_existing": 0}
|
||
|
||
matched: int = 0
|
||
imported: int = 0
|
||
skipped: int = 0
|
||
errors: List[str] = []
|
||
|
||
for node_dir in nodes_root.iterdir():
|
||
if not node_dir.is_dir():
|
||
continue
|
||
json_file = node_dir / f"{node_dir.name}.json"
|
||
if not json_file.exists():
|
||
continue
|
||
matched += 1
|
||
try:
|
||
with open(json_file, "r", encoding="utf-8") as f:
|
||
data = json.load(f)
|
||
if not isinstance(data, dict):
|
||
continue
|
||
metadata = data.get("metadata") or {}
|
||
node_type = metadata.get("type")
|
||
if not node_type:
|
||
continue
|
||
|
||
# 去重:按 node_type 查询是否已存在
|
||
exists_res = get_record_id(
|
||
pagetype="Local Ai Node",
|
||
field="node_type",
|
||
value=node_type,
|
||
)
|
||
if exists_res.get("success"):
|
||
skipped += 1
|
||
continue
|
||
|
||
# 生成 schema(移除 metadata 的剩余部分)
|
||
schema = dict(data)
|
||
schema.pop("metadata", None)
|
||
|
||
payload = {
|
||
"node_type": node_type,
|
||
"node_label": metadata.get("label") or node_type,
|
||
"node_icon": metadata.get("icon") or "fa-cube",
|
||
"node_color": metadata.get("color") or "#6b7280",
|
||
"node_group": metadata.get("group") or "",
|
||
"node_component": metadata.get("component_type") or "GenericNode",
|
||
"node_description": metadata.get("description") or "",
|
||
"status": "Published",
|
||
"node_schema": schema,
|
||
}
|
||
|
||
res = create_record("Local Ai Node", payload)
|
||
if res.get("success"):
|
||
imported += 1
|
||
else:
|
||
errors.append(f"{node_type}: {res.get('error')}")
|
||
except Exception as e:
|
||
errors.append(f"{json_file.name}: {str(e)}")
|
||
|
||
return {
|
||
"success": True,
|
||
"matched": matched,
|
||
"imported": imported,
|
||
"skipped_existing": skipped,
|
||
"errors": errors,
|
||
}
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=str(e))
|
||
|
||
|
||
@router.get("/jingrow/node-definitions/metadata")
|
||
async def get_all_node_metadata():
|
||
"""
|
||
获取所有节点的元数据,用于流程编排界面
|
||
"""
|
||
try:
|
||
current_file = Path(__file__).resolve()
|
||
jingrow_root = current_file.parents[1] # 修正路径层级
|
||
nodes_root = jingrow_root / "ai" / "pagetype" / "local_ai_agent" / "nodes"
|
||
|
||
if not nodes_root.exists():
|
||
return {"success": True, "data": {}}
|
||
|
||
metadata_map = {}
|
||
|
||
for node_dir in nodes_root.iterdir():
|
||
if not node_dir.is_dir():
|
||
continue
|
||
json_file = node_dir / f"{node_dir.name}.json"
|
||
if not json_file.exists():
|
||
continue
|
||
|
||
try:
|
||
with open(json_file, "r", encoding="utf-8") as f:
|
||
data = json.load(f)
|
||
|
||
metadata = data.get("metadata") or {}
|
||
node_type = metadata.get("type")
|
||
if not node_type:
|
||
continue
|
||
|
||
metadata_map[node_type] = {
|
||
"type": node_type,
|
||
"label": metadata.get("label") or node_type,
|
||
"icon": metadata.get("icon") or "fa-cube",
|
||
"color": metadata.get("color") or "#6b7280",
|
||
"description": metadata.get("description") or "",
|
||
"group": metadata.get("group") or "其他",
|
||
"component": metadata.get("component_type") or "GenericNode"
|
||
}
|
||
except Exception:
|
||
continue
|
||
|
||
return {"success": True, "data": metadata_map}
|
||
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=str(e))
|
||
|
||
|
||
@router.get("/jingrow/node-definitions/schema/{node_type}")
|
||
async def get_node_schema(node_type: str):
|
||
"""
|
||
获取指定节点类型的Schema配置
|
||
"""
|
||
try:
|
||
current_file = Path(__file__).resolve()
|
||
jingrow_root = current_file.parents[1]
|
||
nodes_root = jingrow_root / "ai" / "pagetype" / "local_ai_agent" / "nodes"
|
||
json_file = nodes_root / node_type / f"{node_type}.json"
|
||
|
||
if not json_file.exists():
|
||
raise HTTPException(status_code=404, detail=f"节点类型 {node_type} 不存在")
|
||
|
||
with open(json_file, "r", encoding="utf-8") as f:
|
||
data = json.load(f)
|
||
|
||
schema = dict(data)
|
||
schema.pop("metadata", None)
|
||
|
||
return {"success": True, "data": schema}
|
||
|
||
except FileNotFoundError:
|
||
raise HTTPException(status_code=404, detail=f"节点类型 {node_type} 不存在")
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=str(e))
|