Let a queued message be taken back into the composer

This commit is contained in:
Ian Gulliver
2026-10-17 15:45:27 -07:00
parent e67576ac53
commit 658f78934c
12 changed files with 218 additions and 37 deletions
+44 -4
View File
@@ -264,6 +264,29 @@ final class ChatModel {
}
}
func withdraw(_ p: PendingSend, _ overlays: Overlays) async {
guard let taskId, let inputId = p.inputId else {
return
}
let key = "message:\(taskId)"
do {
var imgs: [PendingImage] = []
for id in p.imageIds {
let data = try await ImageCache.fetch(.blob(id), store.api)
guard let image = NSImage(data: data) else {
throw ApiError(status: 0, message: "not an image")
}
imgs.append(PendingImage(data: data, image: image))
}
_ = try await store.api.client.withdrawMessage(path: .init(taskId: taskId, inputId: Int64(inputId))).noContent
builder.withdrawn(inputId)
store.drafts?.restoreText(key, p.text)
store.images.restore(key, imgs)
} catch {
overlays.showAlert(errorText(error))
}
}
func interrupt(_ sessionId: String, _ overlays: Overlays) {
Task {
do {
@@ -525,7 +548,7 @@ private struct TranscriptScroll: View {
RowView(row: row, model: model, nested: false)
}
ForEach(model.builder.pending) { p in
PendingView(pending: p)
PendingView(pending: p, model: model)
}
}
}
@@ -647,13 +670,30 @@ private final class ScannerView: NSView {
private struct PendingView: View {
let pending: PendingSend
let model: ChatModel
@Environment(Overlays.self) private var overlays
@ViewState private var withdrawing = false
var body: some View {
TrailingBubble {
VStack(alignment: .leading, spacing: 2) {
Text(pending.queued ? "queued…" : "sending…")
.font(Fonts.font(11, italic: true))
.foregroundStyle(Palette.muted)
HStack(spacing: 8) {
Text(pending.queued ? "queued…" : "sending…")
.font(Fonts.font(11, italic: true))
.foregroundStyle(Palette.muted)
if pending.inputId != nil {
Button("×") {
withdrawing = true
Task {
await model.withdraw(pending, overlays)
withdrawing = false
}
}
.buttonStyle(ThumbRemoveStyle())
.disabled(withdrawing)
.help("Take this message back into the composer")
}
}
if !pending.images.isEmpty {
FlowLayout(spacing: 6) {
ForEach(pending.images) { img in