Add review workflows, tree-shaped workflows and a workflow editor
This commit is contained in:
+18
-58
@@ -146,7 +146,7 @@ final class ChatModel {
|
||||
}
|
||||
|
||||
func working(_ row: Row) -> Bool {
|
||||
row.pending && (running || subagents && row.inActive) || row.holdsActive && (running || subagents)
|
||||
row.pending && (running || subagents && row.inActive || row.inReview) || row.holdsActive && (running || subagents)
|
||||
}
|
||||
|
||||
var hasOlder: Bool {
|
||||
@@ -286,12 +286,6 @@ final class ChatModel {
|
||||
}
|
||||
}
|
||||
|
||||
func showReview(_ n: Int, _ task: String?) {
|
||||
if let row = reviewRow(n, task) {
|
||||
scrollTo(row.id)
|
||||
}
|
||||
}
|
||||
|
||||
func showFinding(_ reviewId: String, _ index: Int) {
|
||||
let row = builder.root.rows.first { r in
|
||||
if case .finding(let i) = r.kind {
|
||||
@@ -308,7 +302,7 @@ final class ChatModel {
|
||||
guard let taskId else {
|
||||
return []
|
||||
}
|
||||
let latest = latestReview(store.transcript)
|
||||
let latest = latestReviewedSnapshot(store.transcript)
|
||||
let task = store.findTask(taskId)
|
||||
let active = task.map { $0.status == .active && !$0.merging && !$0.mergingIntoParent && $0.mergeQueuedAt == nil && !$0.mergeAfterReview } ?? false
|
||||
var questions: [Int] = []
|
||||
@@ -320,7 +314,7 @@ final class ChatModel {
|
||||
case .question where row.question?.state == .open && row.taskId == taskId:
|
||||
questions.append(row.id)
|
||||
case .finding(let i):
|
||||
if let r = row.review, r.id == latest?.id, findingOpen(r, i) {
|
||||
if let r = row.review, r.snapshotN == latest, findingOpen(r, i) {
|
||||
open.append(row.id)
|
||||
}
|
||||
case .ready(_, _, let d, _) where row.id == builder.lastReady && active:
|
||||
@@ -1241,6 +1235,20 @@ struct RowView: View {
|
||||
}
|
||||
}
|
||||
|
||||
case .reviewer(let agentModel):
|
||||
DetailsBox(nested: nested, working: row.active, status: (row.status, row.statusState == .done ? Palette.muted : row.statusState == .error ? Palette.danger : Palette.accent)) {
|
||||
Text(styled([(row.review?.stage ?? "review", 600, nil), (agentModel.isEmpty ? "" : " · \(agentModel)", 400, Palette.muted), (row.text.isEmpty ? "" : " \(row.text)", 400, Palette.muted)]))
|
||||
.lineLimit(1)
|
||||
} content: {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
ForEach(row.body!.rows) { child in
|
||||
RowView(row: child, model: model, nested: true)
|
||||
}
|
||||
}
|
||||
.padding(.top, 6)
|
||||
.padding(.bottom, 4)
|
||||
}
|
||||
|
||||
case .group:
|
||||
DetailsBox(nested: nested, working: model.working(row)) {
|
||||
SubagentTag(show: row.tag)
|
||||
@@ -1318,9 +1326,7 @@ struct RowView: View {
|
||||
}
|
||||
|
||||
case .snapshot(let snap):
|
||||
SnapshotRow(snap: snap, review: row.review, taskId: row.taskId, inherited: row.taskId != model.taskId, forks: row.forks) {
|
||||
model.showReview(snap.n, row.taskId)
|
||||
}
|
||||
SnapshotRow(snap: snap, taskId: row.taskId, inherited: row.taskId != model.taskId, forks: row.forks)
|
||||
|
||||
case .question:
|
||||
if let item = row.question {
|
||||
@@ -1727,14 +1733,11 @@ func taskLink(_ store: Store, _ id: String) -> AttributedString {
|
||||
|
||||
private struct SnapshotRow: View {
|
||||
let snap: Snapshot
|
||||
let review: Review?
|
||||
let taskId: String?
|
||||
let inherited: Bool
|
||||
let forks: [String]
|
||||
let showReview: () -> Void
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
@Environment(\.paneAccent) private var accent
|
||||
@ViewState private var forking = false
|
||||
|
||||
var body: some View {
|
||||
@@ -1768,9 +1771,6 @@ private struct SnapshotRow: View {
|
||||
.buttonStyle(SmallLinkButtonStyle())
|
||||
.disabled(forking)
|
||||
.help("Start a new task from this snapshot, its agent continuing the conversation as it stood here")
|
||||
if ![.answer, .fork, .askHuman].contains(snap.trigger) {
|
||||
reviewButton
|
||||
}
|
||||
ForEach(forks, id: \.self) { id in
|
||||
Text(AttributedString("forked as ") + taskLink(store, id))
|
||||
.fixedSize()
|
||||
@@ -1782,44 +1782,4 @@ private struct SnapshotRow: View {
|
||||
.help("\(snap.trigger.rawValue.replacingOccurrences(of: "_", with: " ")) · \(time)")
|
||||
}
|
||||
|
||||
private var reviewButton: some View {
|
||||
let n = review.map(findingCount) ?? 0
|
||||
return Button {
|
||||
showReview()
|
||||
} label: {
|
||||
Text("review")
|
||||
.font(Fonts.font(11, weight: n > 0 ? 700 : 400))
|
||||
.overlay(alignment: .topTrailing) {
|
||||
if let review {
|
||||
switch review.state {
|
||||
case .running, .failed:
|
||||
EmptyView()
|
||||
case .skipped:
|
||||
Text("●").font(Fonts.font(11, weight: 700)).foregroundStyle(Palette.muted).help("skipped: no changes")
|
||||
.fixedSize()
|
||||
.offset(x: 13, y: -7)
|
||||
case .done:
|
||||
if n == 0 {
|
||||
Text("✓").font(Fonts.font(11, weight: 700)).foregroundStyle(Palette.ok).help(review.findingStates.additionalProperties.isEmpty ? "no findings" : "every finding addressed")
|
||||
.fixedSize()
|
||||
.offset(x: 13, y: -7)
|
||||
} else {
|
||||
Text("\(n)")
|
||||
.font(Fonts.font(10, weight: 700))
|
||||
.foregroundStyle(Palette.danger)
|
||||
.frame(height: 14)
|
||||
.padding(.horizontal, 5)
|
||||
.background(Palette.dangerTint, in: Capsule())
|
||||
.help("\(n) open findings")
|
||||
.fixedSize()
|
||||
.offset(x: 15, y: -9)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(SmallLinkButtonStyle(color: n > 0 ? Palette.warn : Palette.accent, border: review?.state == .running ? accent : Palette.border))
|
||||
.disabled(review == nil || review?.state == .running)
|
||||
.help(review?.state == .running ? "Review in progress" : review == nil ? "Not reviewed" : "Scroll to the review of snapshot \(snap.n)")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user