macOS client store and API
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user