Let the human comment on each decision in the ready-for-merge card
This commit is contained in:
+95
-12
@@ -1396,12 +1396,25 @@ private struct ReadyBubble: View {
|
||||
var body: some View {
|
||||
let task = taskId.flatMap { store.findTask($0) }
|
||||
let current = latest && task?.status == .active
|
||||
AttentionCard(open: current) {
|
||||
var ctx = MDContext(taskId: taskId)
|
||||
if current, let task {
|
||||
let busy = dismissing
|
||||
let focus = $focus
|
||||
let base = ctx
|
||||
func discuss(_ item: MDItem) -> AnyView {
|
||||
var inner = base
|
||||
inner.discuss = discuss
|
||||
return AnyView(DiscussItem(item: item, key: discussKey(task, item.index), busy: busy, focus: focus, ctx: inner, send: { send(task) }))
|
||||
}
|
||||
ctx.discuss = discuss
|
||||
}
|
||||
return AttentionCard(open: current) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
AttentionHeader(label: discard ? "suggests discarding" : "ready for merge", when: when, open: current)
|
||||
MarkdownView(blocks: blocks, ctx: MDContext(taskId: taskId))
|
||||
MarkdownView(blocks: blocks, ctx: ctx)
|
||||
if current, let task {
|
||||
let busy = store.taskBusy(task.id)
|
||||
let commented = comments(task).count
|
||||
AttentionReply(key: draftKey(task), placeholder: "Something else to tell the agent; sending it dismisses this", busy: dismissing, focus: $focus, send: { send(task) }) {
|
||||
Button("Dismiss") {
|
||||
dismiss(task, then: nil)
|
||||
@@ -1410,7 +1423,13 @@ private struct ReadyBubble: View {
|
||||
.help(discard ? "Drop this suggestion; the task stays open" : "Drop this ready call; the task stays open")
|
||||
DiscardButton(task: task, busy: busy)
|
||||
} trailing: {
|
||||
if !discard {
|
||||
if commented > 0 {
|
||||
Button(commented == 1 ? "Send 1 comment" : "Send \(commented) comments") {
|
||||
send(task)
|
||||
}
|
||||
.buttonStyle(WebButtonStyle(primary: true))
|
||||
.help("Send your comments to the agent in one message; sending dismisses this")
|
||||
} else if !discard {
|
||||
MergeButton(task: task, busy: busy, unmerged: unmerged, keepOpen: true, primary: task.readyMerge == ._continue)
|
||||
MergeButton(task: task, busy: busy, unmerged: unmerged, keepOpen: false, primary: task.readyMerge != ._continue)
|
||||
}
|
||||
@@ -1425,10 +1444,40 @@ private struct ReadyBubble: View {
|
||||
"ready:\(task.id):\(when.timeIntervalSince1970)"
|
||||
}
|
||||
|
||||
private func discussKey(_ task: ByreTask, _ i: Int) -> String {
|
||||
"\(draftKey(task)):discuss:\(i)"
|
||||
}
|
||||
|
||||
private func keys(_ task: ByreTask) -> [String] {
|
||||
discussItems(blocks).map { discussKey(task, $0.index) } + [draftKey(task)]
|
||||
}
|
||||
|
||||
private func comments(_ task: ByreTask) -> [(MDItem, String)] {
|
||||
discussItems(blocks).compactMap { item in
|
||||
let key = discussKey(task, item.index)
|
||||
let text = trimmed(store.drafts?.textAnswer(key) ?? "")
|
||||
if text.isEmpty && !store.images.has(key) {
|
||||
return nil
|
||||
}
|
||||
return (item, text)
|
||||
}
|
||||
}
|
||||
|
||||
private func message(_ task: ByreTask) -> String {
|
||||
let other = trimmed(store.drafts?.textAnswer(draftKey(task)) ?? "")
|
||||
let parts = comments(task).map { item, text in
|
||||
item.quote.components(separatedBy: "\n").map { "> \($0)" }.joined(separator: "\n") + (text.isEmpty ? "" : "\n\n\(text)")
|
||||
}
|
||||
if parts.isEmpty {
|
||||
return other
|
||||
}
|
||||
let intro = discard ? "Comments on your suggestion to discard:" : "Comments on your ready-for-merge overview:"
|
||||
return ([intro] + parts + (other.isEmpty ? [] : [other])).joined(separator: "\n\n")
|
||||
}
|
||||
|
||||
private func send(_ task: ByreTask) {
|
||||
let key = draftKey(task)
|
||||
let text = store.drafts?.textAnswer(key) ?? ""
|
||||
if trimmed(text).isEmpty && !store.images.has(key) {
|
||||
let text = message(task)
|
||||
if text.isEmpty && !keys(task).contains(where: { store.images.has($0) }) {
|
||||
return
|
||||
}
|
||||
dismiss(task, then: text)
|
||||
@@ -1436,19 +1485,23 @@ private struct ReadyBubble: View {
|
||||
|
||||
private func dismiss(_ task: ByreTask, then text: String?) {
|
||||
dismissing = true
|
||||
let key = draftKey(task)
|
||||
let imgs = store.images.take(key)
|
||||
let keys = keys(task)
|
||||
let imgs = keys.map { store.images.take($0) }
|
||||
Task {
|
||||
do {
|
||||
let ids = text == nil ? nil : try await uploadImages(store.api, imgs)
|
||||
let ids = text == nil ? nil : try await uploadImages(store.api, imgs.flatMap { $0 })
|
||||
_ = try await store.api.client.dismissReady(path: .init(taskId: task.id)).ok
|
||||
if let text {
|
||||
try await store.messageTask(task.id, text, images: ids)
|
||||
}
|
||||
store.drafts?.dropTextAnswer(key)
|
||||
store.paneFocus.setDraft(key, false)
|
||||
for key in keys {
|
||||
store.drafts?.dropTextAnswer(key)
|
||||
store.paneFocus.setDraft(key, false)
|
||||
}
|
||||
} catch {
|
||||
store.images.restore(key, imgs)
|
||||
for (key, imgs) in zip(keys, imgs) {
|
||||
store.images.restore(key, imgs)
|
||||
}
|
||||
overlays.showAlert(errorText(error))
|
||||
}
|
||||
dismissing = false
|
||||
@@ -1456,6 +1509,36 @@ private struct ReadyBubble: View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct DiscussItem: View {
|
||||
let item: MDItem
|
||||
let key: String
|
||||
let busy: Bool
|
||||
var focus: FocusState<String?>.Binding
|
||||
let ctx: MDContext
|
||||
let send: () -> Void
|
||||
@Environment(Store.self) private var store
|
||||
|
||||
var body: some View {
|
||||
let open = otherOpen(store, key)
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 8) {
|
||||
ItemBody(item: item, ctx: ctx)
|
||||
if !open {
|
||||
Button("Discuss") {
|
||||
store.drafts?.setTextAnswer(key, "")
|
||||
}
|
||||
.buttonStyle(SmallLinkButtonStyle())
|
||||
.help("Comment on this; your comments go to the agent together")
|
||||
.disabled(busy)
|
||||
}
|
||||
}
|
||||
if open {
|
||||
OtherEntry(key: key, placeholder: "Your comment on this", busy: busy, focus: focus, send: send)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ToolResult: View {
|
||||
let result: String?
|
||||
let unfinished: Bool
|
||||
|
||||
Reference in New Issue
Block a user