修复bugagent
Some checks are pending
CI / host-build (push) Waiting to run
CI / rk3588-cross-build (push) Waiting to run

This commit is contained in:
sladro 2026-01-10 22:46:18 +08:00
parent d83f2eb749
commit ce8ae2b448
2 changed files with 423 additions and 1 deletions

View File

@ -71,6 +71,28 @@ func (s *Server) handleConfigUIState(w http.ResponseWriter, r *http.Request) {
root, _ := readUIRootConfig(s.agentCfg.ConfigPath) root, _ := readUIRootConfig(s.agentCfg.ConfigPath)
mode := "instances"
warnings := []string{}
if st, err := os.Stat(s.agentCfg.ConfigPath); err != nil {
if os.IsNotExist(err) {
warnings = append(warnings, "config file not found: "+s.agentCfg.ConfigPath)
} else {
warnings = append(warnings, "config stat failed: "+err.Error())
}
} else if st.IsDir() {
warnings = append(warnings, "config path is a directory: "+s.agentCfg.ConfigPath)
}
instances := root.Instances
// If current config is graphs-only, do a best-effort import so GUI can edit.
if len(instances) == 0 && len(root.Graphs) > 0 {
mode = "graphs_imported"
inst, warn := inferInstancesFromGraphs(root.Graphs)
if len(inst) > 0 {
instances = inst
}
warnings = append(warnings, warn...)
}
// Force templates/instances-only view. // Force templates/instances-only view.
// templates = union(current templates, built-in templates) // templates = union(current templates, built-in templates)
tplKeys := map[string]bool{} tplKeys := map[string]bool{}
@ -90,12 +112,412 @@ func (s *Server) handleConfigUIState(w http.ResponseWriter, r *http.Request) {
"ok": true, "ok": true,
"global": root.Global, "global": root.Global,
"queue": root.Queue, "queue": root.Queue,
"instances": root.Instances, "instances": instances,
"templates": mergedTpls, "templates": mergedTpls,
"mode": mode,
"warnings": warnings,
} }
writeJSON(w, http.StatusOK, resp) writeJSON(w, http.StatusOK, resp)
} }
func inferInstancesFromGraphs(graphs []map[string]any) ([]UIInstance, []string) {
instances := []UIInstance{}
warnings := []string{}
used := map[string]int{}
for _, g := range graphs {
gname := asString(g["name"])
nodes := asSlice(g["nodes"])
if len(nodes) == 0 {
continue
}
// Try templates in priority order.
if inst, ok := inferFaceDetRecog(gname, nodes); ok {
inst.Name = uniquify(inst.Name, used)
instances = append(instances, inst)
continue
}
if inst, ok := inferFaceDet(gname, nodes); ok {
inst.Name = uniquify(inst.Name, used)
instances = append(instances, inst)
continue
}
if inst, ok := inferYoloAlarmMinio(gname, nodes); ok {
inst.Name = uniquify(inst.Name, used)
instances = append(instances, inst)
continue
}
if inst, ok := inferYolo(gname, nodes); ok {
inst.Name = uniquify(inst.Name, used)
instances = append(instances, inst)
continue
}
if inst, ok := inferTranscode(gname, nodes); ok {
inst.Name = uniquify(inst.Name, used)
instances = append(instances, inst)
continue
}
if gname == "" {
gname = "noname"
}
warnings = append(warnings, "graph not recognized for templates/instances import: "+gname)
}
return instances, warnings
}
func inferTranscode(graphName string, nodes []any) (UIInstance, bool) {
in := findNodeByType(nodes, "input_rtsp")
pub := findNodeByType(nodes, "publish")
if in == nil || pub == nil {
return UIInstance{}, false
}
if findNodeByType(nodes, "ai_yolo") != nil || findNodeByType(nodes, "ai_face_det") != nil || findNodeByType(nodes, "ai_face_recog") != nil {
return UIInstance{}, false
}
name := deriveNameFromPublish(pub, graphName)
params := map[string]any{}
params["name"] = name
if url := asString(in["url"]); url != "" {
params["url"] = url
} else {
return UIInstance{}, false
}
params["fps"] = asIntOr(in["fps"], asIntOr(pub["fps"], 25))
params["src_w"] = asIntOr(in["width"], 1920)
params["src_h"] = asIntOr(in["height"], 1080)
params["gop"] = asIntOr(pub["gop"], 50)
params["bitrate_kbps"] = asIntOr(pub["bitrate_kbps"], 2000)
applyPublishOutputsToParams(pub, params)
return UIInstance{Name: name, Template: "transcode_rtsp_hls", Params: params}, true
}
func inferYolo(graphName string, nodes []any) (UIInstance, bool) {
in := findNodeByType(nodes, "input_rtsp")
ai := findNodeByType(nodes, "ai_yolo")
pub := findNodeByType(nodes, "publish")
if in == nil || ai == nil || pub == nil {
return UIInstance{}, false
}
if findNodeByType(nodes, "alarm") != nil {
return UIInstance{}, false
}
name := deriveNameFromPublish(pub, graphName)
params := map[string]any{}
params["name"] = name
url := asString(in["url"])
model := asString(ai["model_path"])
if url == "" || model == "" {
return UIInstance{}, false
}
params["url"] = url
params["model_path"] = model
params["fps"] = asIntOr(in["fps"], asIntOr(pub["fps"], 25))
params["src_w"] = asIntOr(in["width"], 1920)
params["src_h"] = asIntOr(in["height"], 1080)
params["gop"] = asIntOr(pub["gop"], 50)
params["bitrate_kbps"] = asIntOr(pub["bitrate_kbps"], 2000)
applyPublishOutputsToParams(pub, params)
return UIInstance{Name: name, Template: "yolo_rtsp_hls", Params: params}, true
}
func inferYoloAlarmMinio(graphName string, nodes []any) (UIInstance, bool) {
in := findNodeByType(nodes, "input_rtsp")
ai := findNodeByType(nodes, "ai_yolo")
alarm := findNodeByType(nodes, "alarm")
pub := findNodeByType(nodes, "publish")
if in == nil || ai == nil || alarm == nil || pub == nil {
return UIInstance{}, false
}
name := deriveNameFromPublish(pub, graphName)
params := map[string]any{}
params["name"] = name
url := asString(in["url"])
model := asString(ai["model_path"])
if url == "" || model == "" {
return UIInstance{}, false
}
params["url"] = url
params["model_path"] = model
params["fps"] = asIntOr(in["fps"], asIntOr(pub["fps"], 25))
params["src_w"] = asIntOr(in["width"], 1920)
params["src_h"] = asIntOr(in["height"], 1080)
params["gop"] = asIntOr(pub["gop"], 50)
params["bitrate_kbps"] = asIntOr(pub["bitrate_kbps"], 2000)
applyPublishOutputsToParams(pub, params)
// cooldown
if rules := asSlice(alarm["rules"]); len(rules) > 0 {
if r0 := asMap(rules[0]); r0 != nil {
params["cooldown_ms"] = asIntOr(r0["cooldown_ms"], 3000)
}
}
// minio from snapshot/clip upload
actions := asMap(alarm["actions"])
upl := firstMinioUpload(actions)
if upl == nil {
return UIInstance{}, false
}
ep := asString(upl["endpoint"])
bucket := asString(upl["bucket"])
ak := asString(upl["access_key"])
sk := asString(upl["secret_key"])
if ep == "" || bucket == "" || ak == "" || sk == "" {
return UIInstance{}, false
}
params["minio_endpoint"] = ep
params["minio_bucket"] = bucket
params["minio_ak"] = ak
params["minio_sk"] = sk
if region := asString(upl["region"]); region != "" {
params["minio_region"] = region
} else {
params["minio_region"] = "us-east-1"
}
return UIInstance{Name: name, Template: "yolo_alarm_minio", Params: params}, true
}
func inferFaceDet(graphName string, nodes []any) (UIInstance, bool) {
in := findNodeByType(nodes, "input_rtsp")
det := findNodeByType(nodes, "ai_face_det")
pub := findNodeByType(nodes, "publish")
if in == nil || det == nil || pub == nil {
return UIInstance{}, false
}
if findNodeByType(nodes, "ai_face_recog") != nil {
return UIInstance{}, false
}
name := deriveNameFromPublish(pub, graphName)
params := map[string]any{}
params["name"] = name
url := asString(in["url"])
model := asString(det["model_path"])
if url == "" || model == "" {
return UIInstance{}, false
}
params["url"] = url
params["det_model_path"] = model
params["fps"] = asIntOr(in["fps"], asIntOr(pub["fps"], 25))
params["src_w"] = asIntOr(in["width"], 1920)
params["src_h"] = asIntOr(in["height"], 1080)
params["gop"] = asIntOr(pub["gop"], 50)
params["bitrate_kbps"] = asIntOr(pub["bitrate_kbps"], 2000)
applyPublishOutputsToParams(pub, params)
return UIInstance{Name: name, Template: "face_det_rtsp_hls", Params: params}, true
}
func inferFaceDetRecog(graphName string, nodes []any) (UIInstance, bool) {
in := findNodeByType(nodes, "input_rtsp")
det := findNodeByType(nodes, "ai_face_det")
rec := findNodeByType(nodes, "ai_face_recog")
pub := findNodeByType(nodes, "publish")
if in == nil || det == nil || rec == nil || pub == nil {
return UIInstance{}, false
}
name := deriveNameFromPublish(pub, graphName)
params := map[string]any{}
params["name"] = name
url := asString(in["url"])
detModel := asString(det["model_path"])
recModel := asString(rec["model_path"])
if url == "" || detModel == "" || recModel == "" {
return UIInstance{}, false
}
params["url"] = url
params["det_model_path"] = detModel
params["recog_model_path"] = recModel
params["fps"] = asIntOr(in["fps"], asIntOr(pub["fps"], 25))
params["src_w"] = asIntOr(in["width"], 1920)
params["src_h"] = asIntOr(in["height"], 1080)
params["gop"] = asIntOr(pub["gop"], 50)
params["bitrate_kbps"] = asIntOr(pub["bitrate_kbps"], 2000)
applyPublishOutputsToParams(pub, params)
if th := asMap(rec["threshold"]); th != nil {
params["thr_accept"] = asFloatOr(th["accept"], 0.45)
params["thr_margin"] = asFloatOr(th["margin"], 0.05)
} else {
params["thr_accept"] = 0.45
params["thr_margin"] = 0.05
}
if gal := asMap(rec["gallery"]); gal != nil {
if p := asString(gal["path"]); p != "" {
params["gallery_path"] = p
} else {
params["gallery_path"] = "./models/face_gallery.db"
}
} else {
params["gallery_path"] = "./models/face_gallery.db"
}
return UIInstance{Name: name, Template: "face_det_recog_rtsp_hls", Params: params}, true
}
func findNodeByType(nodes []any, typ string) map[string]any {
for _, n := range nodes {
m := asMap(n)
if m == nil {
continue
}
if asString(m["type"]) == typ {
return m
}
}
return nil
}
func deriveNameFromPublish(pub map[string]any, fallback string) string {
outs := asSlice(pub["outputs"])
for _, o := range outs {
om := asMap(o)
if om == nil {
continue
}
if asString(om["proto"]) != "rtsp_server" {
continue
}
p := asString(om["path"])
if strings.HasPrefix(p, "/live/") {
n := strings.TrimPrefix(p, "/live/")
n = strings.Trim(n, "/")
if n != "" {
return n
}
}
}
if strings.TrimSpace(fallback) != "" {
return fallback
}
return "cam"
}
func applyPublishOutputsToParams(pub map[string]any, params map[string]any) {
outs := asSlice(pub["outputs"])
for _, o := range outs {
om := asMap(o)
if om == nil {
continue
}
proto := asString(om["proto"])
if proto == "rtsp_server" {
params["rtsp_port"] = asIntOr(om["port"], asIntOr(params["rtsp_port"], 8555))
}
if proto == "hls" {
if p := asString(om["path"]); p != "" {
params["hls_path"] = p
}
}
}
}
func firstMinioUpload(actions map[string]any) map[string]any {
if actions == nil {
return nil
}
// Prefer snapshot.upload.
if snap := asMap(actions["snapshot"]); snap != nil {
if up := asMap(snap["upload"]); up != nil {
if asString(up["type"]) == "minio" {
return up
}
}
}
if clip := asMap(actions["clip"]); clip != nil {
if up := asMap(clip["upload"]); up != nil {
if asString(up["type"]) == "minio" {
return up
}
}
}
return nil
}
func uniquify(name string, used map[string]int) string {
base := name
if base == "" {
base = "cam"
}
if used[base] == 0 {
used[base] = 1
return base
}
used[base]++
return fmt.Sprintf("%s_%d", base, used[base])
}
func asMap(v any) map[string]any {
if v == nil {
return nil
}
if m, ok := v.(map[string]any); ok {
return m
}
return nil
}
func asSlice(v any) []any {
if v == nil {
return nil
}
if s, ok := v.([]any); ok {
return s
}
return nil
}
func asString(v any) string {
if v == nil {
return ""
}
if s, ok := v.(string); ok {
return strings.TrimSpace(s)
}
return ""
}
func asIntOr(v any, def int) int {
if v == nil {
return def
}
switch x := v.(type) {
case float64:
return int(x)
case int:
return x
case int32:
return int(x)
case int64:
return int(x)
case string:
// ignore parse errors
return def
default:
return def
}
}
func asFloatOr(v any, def float64) float64 {
if v == nil {
return def
}
switch x := v.(type) {
case float64:
return x
case int:
return float64(x)
case int32:
return float64(x)
case int64:
return float64(x)
default:
return def
}
}
func (s *Server) handleConfigUIPlan(w http.ResponseWriter, r *http.Request) { func (s *Server) handleConfigUIPlan(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
errorJSON(w, http.StatusMethodNotAllowed, "method not allowed") errorJSON(w, http.StatusMethodNotAllowed, "method not allowed")

Binary file not shown.