File size: 3,586 Bytes
4fc4790
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import OpenClawProtocol
import SwiftUI

@MainActor
struct AgentEventsWindow: View {
    private let store = AgentEventStore.shared

    var body: some View {
        VStack(alignment: .leading, spacing: 6) {
            HStack {
                Text("Agent Events")
                    .font(.title3.weight(.semibold))
                Spacer()
                Button("Clear") { self.store.clear() }
                    .buttonStyle(.bordered)
            }
            .padding(.bottom, 4)

            ScrollView {
                LazyVStack(alignment: .leading, spacing: 8) {
                    ForEach(self.store.events.reversed(), id: \.seq) { evt in
                        EventRow(event: evt)
                    }
                }
            }
        }
        .padding(12)
        .frame(minWidth: 520, minHeight: 360)
    }
}

private struct EventRow: View {
    let event: ControlAgentEvent

    var body: some View {
        VStack(alignment: .leading, spacing: 2) {
            HStack(spacing: 6) {
                Text(self.event.stream.uppercased())
                    .font(.caption2.weight(.bold))
                    .padding(.horizontal, 6)
                    .padding(.vertical, 2)
                    .background(self.tint)
                    .foregroundStyle(Color.white)
                    .clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
                Text("run " + self.event.runId)
                    .font(.caption.monospaced())
                    .foregroundStyle(.secondary)
                Spacer()
                Text(self.formattedTs)
                    .font(.caption2)
                    .foregroundStyle(.secondary)
            }
            if let json = self.prettyJSON(event.data) {
                Text(json)
                    .font(.caption.monospaced())
                    .foregroundStyle(.primary)
                    .textSelection(.enabled)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding(.top, 2)
            }
        }
        .padding(8)
        .background(
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color.primary.opacity(0.04)))
    }

    private var tint: Color {
        switch self.event.stream {
        case "job": .blue
        case "tool": .orange
        case "assistant": .green
        default: .gray
        }
    }

    private var formattedTs: String {
        let date = Date(timeIntervalSince1970: event.ts / 1000)
        let f = DateFormatter()
        f.dateFormat = "HH:mm:ss.SSS"
        return f.string(from: date)
    }

    private func prettyJSON(_ dict: [String: OpenClawProtocol.AnyCodable]) -> String? {
        let normalized = dict.mapValues { $0.value }
        guard JSONSerialization.isValidJSONObject(normalized),
              let data = try? JSONSerialization.data(withJSONObject: normalized, options: [.prettyPrinted]),
              let str = String(data: data, encoding: .utf8)
        else { return nil }
        return str
    }
}

struct AgentEventsWindow_Previews: PreviewProvider {
    static var previews: some View {
        let sample = ControlAgentEvent(
            runId: "abc",
            seq: 1,
            stream: "tool",
            ts: Date().timeIntervalSince1970 * 1000,
            data: [
                "phase": OpenClawProtocol.AnyCodable("start"),
                "name": OpenClawProtocol.AnyCodable("bash"),
            ],
            summary: nil)
        AgentEventStore.shared.append(sample)
        return AgentEventsWindow()
    }
}