macOS client store and API

This commit is contained in:
Ian Gulliver
2026-10-16 22:46:54 -07:00
parent b28968a0bc
commit a9bec7a7ea
22 changed files with 1508 additions and 251 deletions
+11 -71
View File
@@ -1,51 +1,5 @@
import SwiftUI
enum JSON: Decodable {
case string(String)
case number(Double)
case bool(Bool)
case object([String: JSON])
case array([JSON])
case null
init(from decoder: Decoder) throws {
let c = try decoder.singleValueContainer()
if c.decodeNil() {
self = .null
} else if let v = try? c.decode(Bool.self) {
self = .bool(v)
} else if let v = try? c.decode(Double.self) {
self = .number(v)
} else if let v = try? c.decode(String.self) {
self = .string(v)
} else if let v = try? c.decode([JSON].self) {
self = .array(v)
} else {
self = .object(try c.decode([String: JSON].self))
}
}
subscript(key: String) -> JSON? {
if case .object(let o) = self {
return o[key]
}
return nil
}
var string: String? {
if case .string(let s) = self {
return s
}
return nil
}
}
struct Event: Decodable {
let seq: Int64
let kind: String
let payload: JSON
}
struct Row: Identifiable {
let id: Int64
let kind: String
@@ -53,16 +7,16 @@ struct Row: Identifiable {
var tools: [String] = []
}
func buildRows(_ events: [Event]) -> [Row] {
func buildRows(_ events: [ByreEvent]) -> [Row] {
var rows: [Row] = []
for e in events {
switch e.kind {
case "user.message", "agent.message", "agent.thinking":
rows.append(Row(id: e.seq, kind: e.kind, text: e.payload["text"]?.string ?? ""))
rows.append(Row(id: e.seq, kind: e.kind, text: e.payload.value["text"] as? String ?? ""))
case "agent.tool_call":
let name = e.payload["name"]?.string ?? "?"
let input = e.payload["input"]
let detail = input?["file_path"]?.string ?? input?["command"]?.string ?? input?["pattern"]?.string ?? ""
let name = e.payload.value["name"] as? String ?? "?"
let input = e.payload.value["input"] as? [String: (any Sendable)?]
let detail = input?["file_path"] as? String ?? input?["command"] as? String ?? input?["pattern"] as? String ?? ""
let line = "\(name) \(detail)"
if rows.last?.kind == "tools" {
rows[rows.count - 1].tools.append(line)
@@ -139,7 +93,7 @@ enum Container: String, CaseIterable {
}
struct TranscriptSpike: View {
@Environment(Server.self) private var server
@Environment(Store.self) private var store
@ViewState private var all: [Row] = []
@ViewState private var shown = 0
@ViewState private var container = Container.lazyVStack
@@ -219,8 +173,8 @@ struct TranscriptSpike: View {
}
}
}
.task {
await load()
.onChange(of: store.transcript.count, initial: true) {
load()
}
.task(id: playing) {
while playing && shown < all.count {
@@ -234,26 +188,12 @@ struct TranscriptSpike: View {
}
}
private func load() async {
guard !server.taskId.isEmpty else {
private func load() {
guard let feed = store.selectedFeed, feed.ready else {
return
}
let start = Date()
var got: [Event] = []
var after: Int64 = 0
do {
while true {
let page = try await server.get([Event].self, "/api/tasks/\(server.taskId)/events", [URLQueryItem(name: "after", value: String(after)), URLQueryItem(name: "limit", value: "1000")])
got += page
if page.count < 1000 {
break
}
after = page.last!.seq
}
} catch {
server.log("transcript: \(error)")
return
}
let got = feed.events
events = got.count
all = buildRows(got)
shown = all.count > 200 ? all.count - 100 : all.count