Expose graph node type catalog from agent

This commit is contained in:
tian 2026-04-28 16:29:52 +08:00
parent 45fadc6699
commit 551c6016ff
3 changed files with 213 additions and 0 deletions

View File

@ -176,3 +176,48 @@ func TestHandleInfoIncludesCurrentConfigSummary(t *testing.T) {
t.Fatalf("overlays = %#v", got["overlays"])
}
}
func TestHandleGraphNodeTypesListsRegisteredMediaNodes(t *testing.T) {
s := &Server{}
req := httptest.NewRequest(http.MethodGet, "/v1/graph-node-types", nil)
rr := httptest.NewRecorder()
s.handleGraphNodeTypes(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status code: got %d body=%s", rr.Code, rr.Body.String())
}
var got struct {
Items []struct {
Type string `json:"type"`
Label string `json:"label"`
Category string `json:"category"`
Icon string `json:"icon"`
Description string `json:"description"`
Defaults map[string]any `json:"defaults"`
} `json:"items"`
}
if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil {
t.Fatalf("decode response: %v", err)
}
seen := map[string]bool{}
for _, item := range got.Items {
seen[item.Type] = true
if item.Label == "" || item.Category == "" || item.Icon == "" || item.Description == "" {
t.Fatalf("node type metadata incomplete: %#v", item)
}
if item.Defaults["type"] != item.Type {
t.Fatalf("defaults should include node type %q, got %#v", item.Type, item.Defaults)
}
}
for _, want := range []string{
"input_rtsp", "input_file", "preprocess", "ai_scrfd", "ai_scrfd_sliding",
"ai_face_det", "ai_face_recog", "ai_yolo", "ai_shoe_det", "tracker",
"logic_gate", "event_fusion", "region_event", "action_recog", "det_post",
"osd", "publish", "storage", "alarm", "gate", "zlm_http",
} {
if !seen[want] {
t.Fatalf("missing registered node type %q in %#v", want, got.Items)
}
}
}

View File

@ -0,0 +1,167 @@
package httpapi
import "net/http"
type graphNodeParam struct {
Key string `json:"key"`
Label string `json:"label"`
Type string `json:"type"`
Step string `json:"step,omitempty"`
Placeholder string `json:"placeholder,omitempty"`
Options []string `json:"options,omitempty"`
}
type graphNodeTypeInfo struct {
Type string `json:"type"`
Label string `json:"label"`
Category string `json:"category"`
Icon string `json:"icon"`
Description string `json:"description"`
Defaults map[string]any `json:"defaults"`
Params []graphNodeParam `json:"params,omitempty"`
}
func (s *Server) handleGraphNodeTypes(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
errorJSON(w, http.StatusMethodNotAllowed, "method not allowed")
return
}
if !s.authorize(r, false) {
errorJSON(w, http.StatusUnauthorized, "unauthorized")
return
}
writeJSON(w, http.StatusOK, map[string]any{
"items": graphNodeTypesCatalog(),
})
}
func graphNodeTypesCatalog() []graphNodeTypeInfo {
return []graphNodeTypeInfo{
nodeType("input_rtsp", "RTSP 输入", "输入", "camera", "从网络摄像机或流媒体地址读取视频流。", "source", map[string]any{"url": "${rtsp_url}"}, []graphNodeParam{
textParam("url", "RTSP 地址", "${rtsp_url}"),
numberParam("fps", "输入帧率", "1"),
numberParam("width", "宽度", "1"),
numberParam("height", "高度", "1"),
boolParam("force_tcp", "强制 TCP"),
numberParam("reconnect_sec", "重连间隔秒", "1"),
}),
nodeType("input_file", "文件输入", "输入", "file", "从本地视频文件读取帧,常用于离线验证和回放。", "source", nil, []graphNodeParam{
textParam("path", "文件路径", ""),
numberParam("fps", "回放帧率", "1"),
boolParam("loop", "循环播放"),
}),
nodeType("preprocess", "图像预处理", "处理", "adjust", "调整尺寸、格式和硬件加速路径,为后续推理或编码准备图像。", "filter", map[string]any{"dst_format": "rgb"}, []graphNodeParam{
numberParam("dst_w", "输出宽度", "1"),
numberParam("dst_h", "输出高度", "1"),
selectParam("dst_format", "输出格式", []string{"rgb", "nv12", "bgr"}),
selectParam("resize_mode", "缩放方式", []string{"stretch", "letterbox"}),
boolParam("use_rga", "使用 RGA"),
textParam("rga_gate", "RGA 通道", ""),
}),
nodeType("ai_scrfd", "SCRFD 人脸检测", "AI 推理", "scan-face", "使用 SCRFD 模型做人脸检测,适合固定输入尺寸场景。", "filter", nil, faceDetParams()),
nodeType("ai_scrfd_sliding", "滑窗人脸检测", "AI 推理", "scan-face", "使用滑窗方式执行 SCRFD 人脸检测,适合高分辨率画面。", "filter", nil, faceDetParams()),
nodeType("ai_face_det", "人脸检测", "AI 推理", "face", "通用人脸检测节点,输出人脸框和质量信息。", "filter", nil, faceDetParams()),
nodeType("ai_face_recog", "人脸识别", "AI 推理", "face-id", "对检测到的人脸进行特征提取和人脸库匹配。", "filter", nil, []graphNodeParam{
textParam("model_path", "模型路径", ""),
numberParam("infer_fps", "推理帧率", "0.1"),
boolParam("align", "人脸对齐"),
boolParam("emit_embedding", "输出特征"),
numberParam("max_faces", "最大人脸数", "1"),
}),
nodeType("ai_yolo", "YOLO 目标检测", "AI 推理", "target", "使用 YOLO 模型检测人员、PPE 或其他目标。", "filter", nil, []graphNodeParam{
textParam("model_path", "模型路径", ""),
numberParam("infer_fps", "推理帧率", "0.1"),
numberParam("model_w", "模型宽度", "1"),
numberParam("model_h", "模型高度", "1"),
numberParam("conf", "置信度", "0.01"),
numberParam("nms", "NMS", "0.01"),
}),
nodeType("ai_shoe_det", "鞋靴检测", "AI 推理", "shoe", "检测鞋靴和工鞋相关目标,可配合逻辑节点判断违规。", "filter", nil, []graphNodeParam{
textParam("model_path", "模型路径", ""),
numberParam("infer_fps", "推理帧率", "0.1"),
numberParam("conf", "置信度", "0.01"),
numberParam("nms", "NMS", "0.01"),
boolParam("append_detections", "追加检测结果"),
}),
nodeType("tracker", "目标跟踪", "处理", "route", "对检测目标分配跟踪 ID保持跨帧目标状态。", "filter", nil, []graphNodeParam{
textParam("mode", "跟踪模式", ""),
boolParam("per_class", "按类别跟踪"),
numberParam("high_th", "高阈值", "0.01"),
numberParam("low_th", "低阈值", "0.01"),
numberParam("iou_th", "IOU 阈值", "0.01"),
numberParam("max_age_ms", "最大保留毫秒", "1"),
}),
nodeType("logic_gate", "规则判断", "规则", "branch", "根据检测、跟踪或颜色分析结果进行业务规则判断。", "filter", nil, []graphNodeParam{
textParam("mode", "逻辑模式", ""),
boolParam("debug", "调试输出"),
numberParam("anchor_class", "锚点类别", "1"),
numberParam("boots_class", "鞋靴类别", "1"),
numberParam("violation_class", "违规类别", "1"),
}),
nodeType("event_fusion", "事件融合", "规则", "merge", "融合多路事件,减少重复告警并形成更稳定的业务事件。", "filter", nil, nil),
nodeType("region_event", "区域事件", "规则", "region", "基于区域、越线或停留规则生成区域行为事件。", "filter", nil, nil),
nodeType("action_recog", "行为识别", "AI 推理", "activity", "识别人员行为或动作事件。", "filter", nil, nil),
nodeType("det_post", "检测后处理", "处理", "filter", "对检测结果做过滤、映射、合并或类别转换。", "filter", nil, nil),
nodeType("osd", "画面叠加", "输出", "overlay", "在视频帧上绘制检测框、文字、人脸识别和事件信息。", "filter", nil, []graphNodeParam{
boolParam("draw_bbox", "绘制框"),
boolParam("draw_text", "绘制文字"),
boolParam("draw_face_det", "绘制人脸检测"),
boolParam("draw_face_recog", "绘制人脸识别"),
numberParam("line_width", "线宽", "0.1"),
numberParam("font_scale", "字体缩放", "0.1"),
}),
nodeType("publish", "视频输出", "输出", "broadcast", "编码并发布 RTSP、HLS 或其他视频输出。", "sink", nil, []graphNodeParam{
selectParam("codec", "编码", []string{"h264", "h265"}),
numberParam("fps", "输出帧率", "1"),
numberParam("gop", "GOP", "1"),
numberParam("bitrate_kbps", "码率 kbps", "1"),
boolParam("use_mpp", "使用 MPP"),
boolParam("use_ffmpeg_mux", "FFmpeg 封装"),
}),
nodeType("storage", "本地存储", "输出", "database", "保存帧、事件或中间结果到本地存储。", "sink", nil, nil),
nodeType("alarm", "告警动作", "输出", "bell", "根据规则触发日志、抓图、录像片段、外部接口等动作。", "sink", nil, []graphNodeParam{
numberParam("eval_fps", "评估帧率", "0.1"),
}),
nodeType("gate", "流控闸门", "系统", "gate", "控制流程分支或限流,保护下游节点。", "filter", nil, nil),
nodeType("zlm_http", "ZLMediaKit HTTP", "系统", "server", "提供 ZLMediaKit 相关 HTTP 文件服务能力。", "sink", nil, nil),
}
}
func nodeType(t, label, category, icon, description, role string, defaults map[string]any, params []graphNodeParam) graphNodeTypeInfo {
if defaults == nil {
defaults = map[string]any{}
}
defaults["id"] = t
defaults["type"] = t
defaults["role"] = role
defaults["enable"] = true
return graphNodeTypeInfo{Type: t, Label: label, Category: category, Icon: icon, Description: description, Defaults: defaults, Params: params}
}
func textParam(key, label, placeholder string) graphNodeParam {
return graphNodeParam{Key: key, Label: label, Type: "text", Placeholder: placeholder}
}
func numberParam(key, label, step string) graphNodeParam {
return graphNodeParam{Key: key, Label: label, Type: "number", Step: step}
}
func boolParam(key, label string) graphNodeParam {
return graphNodeParam{Key: key, Label: label, Type: "boolean"}
}
func selectParam(key, label string, options []string) graphNodeParam {
return graphNodeParam{Key: key, Label: label, Type: "select", Options: options}
}
func faceDetParams() []graphNodeParam {
return []graphNodeParam{
textParam("model_path", "模型路径", ""),
numberParam("infer_fps", "推理帧率", "0.1"),
numberParam("model_w", "模型宽度", "1"),
numberParam("model_h", "模型高度", "1"),
numberParam("conf_thresh", "置信度", "0.01"),
numberParam("nms_thresh", "NMS", "0.01"),
numberParam("max_faces", "最大人脸数", "1"),
}
}

View File

@ -138,6 +138,7 @@ func New(agentCfg config.AgentConfig, baseDir string, ms *mediaserver.Client, st
mux.HandleFunc("/v1/media-server/binary", s.handleMediaBinaryUpdate)
mux.HandleFunc("/v1/media-server/binary/rollback", s.handleMediaBinaryRollback)
mux.HandleFunc("/v1/agent/binary", s.handleAgentBinaryUpdate)
mux.HandleFunc("/v1/graph-node-types", s.handleGraphNodeTypes)
mux.HandleFunc("/v1/graphs", s.handleGraphs)
mux.HandleFunc("/v1/graphs/", s.handleGraphDetail)
mux.HandleFunc("/v1/logs/recent", s.handleLogsRecent)