feat: preview node media artifacts in dashboard

This commit is contained in:
lpf
2026-03-09 01:26:49 +08:00
parent 2d5a384342
commit 94cd67b487
6 changed files with 402 additions and 1 deletions

View File

@@ -438,7 +438,7 @@ func TestHandleWebUINodesIncludesP2PSummary(t *testing.T) {
if err := os.MkdirAll(filepath.Join(workspace, "memory"), 0755); err != nil {
t.Fatalf("mkdir memory: %v", err)
}
if err := os.WriteFile(filepath.Join(workspace, "memory", "nodes-dispatch-audit.jsonl"), []byte("{\"node\":\"edge-b\",\"used_transport\":\"webrtc\",\"fallback_from\":\"\",\"duration_ms\":12}\n"), 0644); err != nil {
if err := os.WriteFile(filepath.Join(workspace, "memory", "nodes-dispatch-audit.jsonl"), []byte("{\"node\":\"edge-b\",\"used_transport\":\"webrtc\",\"fallback_from\":\"\",\"duration_ms\":12,\"artifacts\":[{\"name\":\"snap.png\",\"kind\":\"image\",\"mime_type\":\"image/png\",\"storage\":\"inline\",\"content_base64\":\"iVBORw0KGgo=\"}]}\n"), 0644); err != nil {
t.Fatalf("write audit: %v", err)
}
srv.SetNodeP2PStatusHandler(func() map[string]interface{} {
@@ -467,4 +467,9 @@ func TestHandleWebUINodesIncludesP2PSummary(t *testing.T) {
if len(dispatches) != 1 {
t.Fatalf("expected dispatch audit rows, got %+v", body["dispatches"])
}
first, _ := dispatches[0].(map[string]interface{})
artifacts, _ := first["artifacts"].([]interface{})
if len(artifacts) != 1 {
t.Fatalf("expected artifact previews in dispatch row, got %+v", first)
}
}

View File

@@ -19,6 +19,8 @@ type NodesTool struct {
auditPath string
}
const nodeAuditArtifactPreviewLimit = 32768
func NewNodesTool(m *nodes.Manager, r *nodes.Router, auditPath string) *NodesTool {
return &NodesTool{manager: m, router: r, auditPath: strings.TrimSpace(auditPath)}
}
@@ -159,6 +161,9 @@ func (t *NodesTool) writeAudit(req nodes.Request, resp nodes.Response, mode stri
if len(kinds) > 0 {
row["artifact_kinds"] = kinds
}
if previews := artifactAuditPreviews(resp.Payload["artifacts"]); len(previews) > 0 {
row["artifacts"] = previews
}
}
b, _ := json.Marshal(row)
f, err := os.OpenFile(t.auditPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
@@ -194,3 +199,56 @@ func artifactAuditSummary(raw interface{}) (int, []string) {
}
return len(items), kinds
}
func artifactAuditPreviews(raw interface{}) []map[string]interface{} {
items, ok := raw.([]interface{})
if !ok {
if typed, ok := raw.([]map[string]interface{}); ok {
items = make([]interface{}, 0, len(typed))
for _, item := range typed {
items = append(items, item)
}
}
}
if len(items) == 0 {
return nil
}
out := make([]map[string]interface{}, 0, len(items))
for _, item := range items {
row, ok := item.(map[string]interface{})
if !ok || len(row) == 0 {
continue
}
entry := map[string]interface{}{}
for _, key := range []string{"name", "kind", "mime_type", "storage", "path", "url", "source_path"} {
if value, ok := row[key]; ok && strings.TrimSpace(fmt.Sprint(value)) != "" {
entry[key] = value
}
}
if size, ok := row["size_bytes"]; ok {
entry["size_bytes"] = size
}
if text, _ := row["content_text"].(string); strings.TrimSpace(text) != "" {
entry["content_text"] = trimAuditContent(text)
}
if b64, _ := row["content_base64"].(string); strings.TrimSpace(b64) != "" {
entry["content_base64"] = trimAuditContent(b64)
entry["content_base64_truncated"] = len(b64) > nodeAuditArtifactPreviewLimit
}
if truncated, ok := row["truncated"].(bool); ok && truncated {
entry["truncated"] = true
}
if len(entry) > 0 {
out = append(out, entry)
}
}
return out
}
func trimAuditContent(raw string) string {
raw = strings.TrimSpace(raw)
if len(raw) <= nodeAuditArtifactPreviewLimit {
return raw
}
return raw[:nodeAuditArtifactPreviewLimit]
}