import ByreAPI import SwiftUI typealias FindingState = Components.Schemas.FindingState private let findingHead = Re(#"^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@"#) private func stateText(_ r: Review) -> String { switch r.state { case .running: "reviewing on \(r.vendor) (\(r.model))…" case .done: reviewCounts(r) case .failed: "failed" case .skipped: "skipped: no changes" } } @MainActor private enum SnapshotPatches { static var changes: [String: Task] = [:] static var patches: [String: Task] = [:] static func patch(_ api: API, _ taskId: String, _ n: Int, _ path: String) async throws -> String? { let key = "\(taskId)/\(n)/\(path)" if let cached = patches[key] { return try await cached.value } let fetched = Task { let ck = "\(taskId)/\(n)" let pending = changes[ck] ?? Task { try await api.client.getTaskChanges(path: .init(taskId: taskId), query: .init(snapshot: n)).ok.body.json } changes[ck] = pending let c: TaskChanges do { c = try await pending.value } catch { changes[ck] = nil throw error } guard let f = c.files.first(where: { $0.path == path }), !f.binary else { return nil } return try await api.client.getTaskDiff(path: .init(taskId: taskId), query: .init(base: c.base, tree: c.tree, path: f.path, oldPath: f.oldPath)).ok.body.json.patch } patches[key] = fetched do { return try await fetched.value } catch { patches[key] = nil throw error } } } private func linesAround(_ patch: String, _ line: Int, _ end: Int) -> [(kind: PatchLine, text: String, at: Int)] { var lines: [(kind: PatchLine, text: String, at: Int)] = [] var n = 0 var inHunk = false for l in patch.components(separatedBy: "\n") { if let m = findingHead.exec(l) { n = Int(m[1]!) ?? 0 inHunk = true continue } if !inHunk { continue } if l.hasPrefix("+") { lines.append((.add, l, n)) n += 1 } else if l.hasPrefix("-") { lines.append((.del, l, n)) } else if l.hasPrefix(" ") { lines.append((.ctx, l, n)) n += 1 } } return lines.filter { $0.at >= line - 3 && $0.at <= end + 3 } } struct ReviewsView: View { @Environment(Store.self) private var store @ViewState private var reviews: [Review]? @ViewState private var error = "" @ViewState private var target = 0 var body: some View { VStack(spacing: 0) { content } .frame(maxHeight: .infinity, alignment: .top) .task(id: store.selectedTaskId) { target += 1 reviews = nil error = "" await load() let off = store.selectedFeed?.appended.add { events in if events.contains(where: isReviewEvent) { Task { await load() } } } await untilCancelled() off?() target += 1 } } private func load() async { guard let taskId = store.selectedTaskId else { return } target += 1 let started = target do { let fresh = try await store.api.client.listReviews(path: .init(taskId: taskId)).ok.body.json if started == target { reviews = fresh error = "" } } catch { if started == target { reviews = nil self.error = errorText(error) } } } @ViewBuilder private var content: some View { if let taskId = store.selectedTaskId { if !error.isEmpty { EmptyText(text: error, error: true) } else if let reviews { if let lastReview = reviews.last { let n = store.reviewed ?? lastReview.snapshotN let shown = reviews.last { $0.snapshotN == n } let ns = Array(Set([n] + reviews.map(\.snapshotN))).sorted(by: >) let next = ns.filter { $0 > n }.last let prev = ns.first { $0 < n } HStack(spacing: 8) { Picker("", selection: Binding(get: { n }, set: { store.pickReview($0) })) { ForEach(ns, id: \.self) { s in Text("snapshot #\(s)").tag(s) } } .labelsHidden() .pickerStyle(.menu) .fixedSize() .help("Snapshot whose review to show") step(prev ?? n - 1, "← #\(prev ?? n - 1)", ns) step(next ?? n + 1, "#\(next ?? n + 1) →", ns) } .modifier(HeaderBar()) ScrollView { VStack(alignment: .leading, spacing: 12) { if let shown { ReviewCard(review: shown, taskId: taskId) .id(shown.id) } else { Muted(text: "No review of snapshot #\(n) yet.") } } .padding(.horizontal, 16) .padding(.top, 8) .padding(.bottom, 16) } } else { EmptyText(text: "No reviews yet. Each new snapshot of the task is reviewed by an agent from the other vendor.") } } else { EmptyText(text: "Loading…") } } else { EmptyText(text: "No task selected.") } } private func step(_ to: Int, _ label: String, _ ns: [Int]) -> some View { Button(label) { store.pickReview(to) } .buttonStyle(SmallLinkButtonStyle(color: Palette.text)) .disabled(!ns.contains(to)) } } struct ReviewCard: View { let review: Review let taskId: String? var body: some View { VStack(alignment: .leading, spacing: 6) { HStack(alignment: .firstTextBaseline, spacing: 10) { Text("snapshot #\(review.snapshotN)") .font(Fonts.font(11, weight: 700)) .foregroundStyle(Palette.text) Text(stateText(review)) .foregroundStyle(review.state == .failed ? Palette.warn : Palette.muted) Spacer(minLength: 0) Text(ago(review.createdAt)) .help(isoText(review.createdAt)) if review.state == .running { StopReviewButton(reviewId: review.id) } } .font(Fonts.font(11)) .foregroundStyle(Palette.muted) if review.state == .failed { Text(review.error) .foregroundStyle(Palette.danger) .textSelection(.enabled) } if review.state == .done { ReviewResult(review: review, taskId: taskId) } } .padding(.horizontal, 14) .padding(.vertical, 10) .frame(maxWidth: .infinity, alignment: .leading) .background(Palette.raised, in: RoundedRectangle(cornerRadius: 10)) .overlay(RoundedRectangle(cornerRadius: 10).stroke(Palette.border)) } } private struct StopReviewButton: View { let reviewId: String @Environment(Store.self) private var store @Environment(Overlays.self) private var overlays @ViewState private var busy = false var body: some View { Button("Stop") { busy = true Task { do { _ = try await store.api.client.stopReview(path: .init(reviewId: reviewId)).ok } catch { busy = false overlays.showAlert(errorText(error)) } } } .buttonStyle(WebButtonStyle(danger: true)) .disabled(busy) .help("End the reviewer and record this review as failed") } } private struct ReviewResult: View { let review: Review let taskId: String? var body: some View { let res = review.result?.value ?? [:] let fs = findings(review) let md = MDContext(taskId: taskId) VStack(alignment: .leading, spacing: 8) { MarkdownView(blocks: parseMarkdown(jsString(res["summary"] ?? "")), ctx: md) ReviewImages(value: res["images"] ?? nil) if !fs.isEmpty { VStack(alignment: .leading, spacing: 8) { ForEach(Array(fs.enumerated()), id: \.offset) { i, f in FindingRow(review: review, index: i, finding: f, taskId: taskId) } } } } } } private struct FindingRow: View { let review: Review let index: Int let finding: JSONDict let taskId: String? var body: some View { let f = finding let severity = jsString(f["severity"] ?? "") let path = str(f, "path") ?? "" let line = max(0, int(f, "line") ?? 0) let endRaw = int(f, "end_line") ?? 0 let end = endRaw > line ? endRaw : line let whereText = path.isEmpty ? "" : "\(path)\(line > 0 ? ":\(line)\(end > line ? "–\(end)" : "")" : "")" let md = MDContext(taskId: taskId) VStack(alignment: .leading, spacing: 4) { HStack(alignment: .firstTextBaseline, spacing: 8) { Text(severity) .font(Fonts.font(11, weight: 700)) .foregroundStyle(severity == "critical" || severity == "high" ? Palette.danger : severity == "medium" ? Palette.warn : Palette.muted) if let category = str(f, "category"), !category.isEmpty { Text(category).font(Fonts.font(11)).foregroundStyle(Palette.muted) } Text(jsString(f["title"] ?? "")) .textSelection(.enabled) .fixedSize(horizontal: false, vertical: true) if !whereText.isEmpty { Text(whereText).font(Fonts.font(11)).foregroundStyle(Palette.muted).textSelection(.enabled) } Spacer(minLength: 0) if let taskId { FindingActions(taskId: taskId, review: review, index: index, finding: f, whereText: whereText) } } if let taskId, !path.isEmpty, line > 0 { FindingDiff(taskId: taskId, n: review.snapshotN, path: path, line: line, end: end) } MarkdownView(blocks: parseMarkdown(jsString(f["detail"] ?? "")), ctx: md) ReviewImages(value: f["images"] ?? nil) } .padding(.top, 6) .overlay(alignment: .top) { Rectangle().fill(Palette.border).frame(height: 1) } } } private struct ReviewImages: View { let value: (any Sendable)? var body: some View { let imgs = (value as? [(any Sendable)?] ?? []).map { $0 as? JSONDict ?? [:] } if !imgs.isEmpty { FlowLayout(spacing: 10) { ForEach(Array(imgs.enumerated()), id: \.offset) { _, img in let blob = jsString(img["blob"] ?? "") let caption = str(img, "caption") ?? "" VStack(alignment: .leading, spacing: 2) { ByreImage(source: .blob(blob), src: "/api/blobs/\(blob)", maxHeight: 360) if !caption.isEmpty { Text(caption).font(Fonts.font(11)).foregroundStyle(Palette.muted) } } } } .padding(.vertical, 8) } } } private struct FindingDiff: View { let taskId: String let n: Int let path: String let line: Int let end: Int @Environment(Store.self) private var store @ViewState private var rows: [DiffRow]? @ViewState private var failure: String? var body: some View { Group { if let failure { Text("diff not available: \(failure)") .font(Fonts.font(11)) .foregroundStyle(Palette.muted) .padding(.horizontal, 10) .padding(.vertical, 4) .frame(maxWidth: .infinity, alignment: .leading) .modifier(DiffBox()) } else if let rows, !rows.isEmpty { DiffLines(rows: rows) .modifier(DiffBox()) } } .task(id: "\(taskId)/\(n)/\(path)/\(line)/\(end)") { failure = nil do { let patch = try await SnapshotPatches.patch(store.api, taskId, n, path) let lines = patch.map { linesAround($0, line, end) } ?? [] var out: [DiffRow] = [] for (i, d) in lines.enumerated() { if i > 0 { let prev = lines[i - 1] if d.kind != .del && prev.kind != .del && d.at > prev.at + 1 { out.append(DiffRow(text: "⋯", kind: .hunk)) } } let mark = d.kind != .del && d.at >= line && d.at <= end out.append(DiffRow(text: d.text, kind: d.kind, lineno: d.kind == .del ? "" : String(d.at), mark: mark)) } rows = out } catch { failure = errorText(error) } } } } private struct DiffBox: ViewModifier { func body(content: Content) -> some View { content .background(Palette.bg) .clipShape(RoundedRectangle(cornerRadius: 6)) .overlay(RoundedRectangle(cornerRadius: 6).stroke(Palette.border)) .padding(.top, 2) } } private struct FindingActions: View { let taskId: String let review: Review let index: Int let finding: JSONDict let whereText: String @Environment(Store.self) private var store @Environment(Overlays.self) private var overlays @ViewState private var done: FindingState? @ViewState private var busy = false var body: some View { if let s = done ?? review.findingStates.additionalProperties[String(index)] { Text(s == .fixed ? "fix sent" : "ignored") .font(Fonts.font(11)) .foregroundStyle(Palette.muted) } else { HStack(spacing: 6) { Button("fix") { fix() } .buttonStyle(SmallLinkButtonStyle()) .help("Send this finding to the task's agent to fix") Button("ignore") { ignore() } .buttonStyle(SmallLinkButtonStyle()) .help("Mark this finding done without fixing it; Merge won't warn about it") } .disabled(busy) } } private func mark(_ s: FindingState) async throws { _ = try await store.api.client.setFindingState(path: .init(reviewId: review.id, index: index), body: .json(.init(state: s))).ok } private func fix() { let f = finding let text = "Fix this finding from the review of snapshot #\(review.snapshotN):\n\n**[\(jsString(f["severity"] ?? ""))] \(jsString(f["title"] ?? ""))**\(whereText.isEmpty ? "" : " (`\(whereText)`)")\n\n\(jsString(f["detail"] ?? ""))" busy = true Task { do { let sessions = try await store.api.client.listSessions(path: .init(taskId: taskId)).ok.body.json if let live = sessions.last(where: { $0.state != .completed }) { _ = try await store.api.client.sendMessage(path: .init(sessionId: live.id), body: .json(.init(text: text))).accepted } else { _ = try await store.api.client.createSession(path: .init(taskId: taskId), body: .json(.init(prompt: text))).created } } catch { busy = false overlays.showAlert(errorText(error)) return } done = .fixed do { try await mark(.fixed) } catch { overlays.showAlert("fix sent, but not recorded: \(errorText(error))") } } } private func ignore() { busy = true Task { do { try await mark(.ignored) done = .ignored } catch { busy = false overlays.showAlert(errorText(error)) } } } }