const std = @import("std");const debug_trace = @import("../shared/debug_trace.zig");const image_attachments = @import("../images/image_attachments.zig");const io_mod = @import("../shared/io.zig");const types = @import("../shared/types.zig");const worker_runtime = @import("../agent/worker_runtime.zig");const paste_blocks = @import("../input/pasted_blocks.zig");const registered_entities = @import("../input/registered_entities.zig");const core_input_runtime = @import("../input/runtime.zig");const entity_spans = @import("../shared/entity_spans.zig");const visual_layout = @import("../../ui/input/visual_layout.zig");const sort_utils = @import("../shared/sort_utils.zig"); pub const ReviewEntry = struct { draft: worker_runtime.QueuedPromptDraft, pasted_blocks: std.ArrayList(paste_blocks.PastedBlock) = .empty, image_tokens: std.ArrayList(entity_spans.ImageTokenSpan) = .empty, dirty: bool = false, fn deinit(self: *ReviewEntry, alloc: std.mem.Allocator) void { worker_runtime.freeQueuedPromptDraft(alloc, self.draft); paste_blocks.deinitBlocks(alloc, &self.pasted_blocks); self.image_tokens.deinit(alloc); }}; pub const State = struct { entries: []ReviewEntry = &.{}, selected_index: ?usize = null, reason: ?worker_runtime.QueueReviewReason = null, visible: bool = false, selected_dirty: bool = false, pub fn deinit(self: *State, alloc: std.mem.Allocator) void { self.clear(alloc); } pub fn clear(self: *State, alloc: std.mem.Allocator) void { for (self.entries) |*entry| entry.deinit(alloc); if (self.entries.len > 0) alloc.free(self.entries); self.* = .{}; } pub fn active(self: *const State) bool { return self.reason != null; }}; pub const PromptAdmission = enum { enqueue, replaced,}; pub fn Runtime(comptime App: type) type { return struct { pub fn requestCancelAndOpen(app: *App) bool { return openAfterPause(app, app.worker.requestCancelWithQueueReview()); } pub fn pauseAndOpenAfterModalCancel(app: *App) bool { return openAfterPause(app, app.worker.beginQueueReview(.post_cancel)); } pub fn routeVertical(app: *App, direction: visual_layout.Direction) !bool { const state = &app.queued_prompt_review; if (state.active()) { if (state.visible) { const index = state.selected_index orelse return false; switch (direction) { .up => { if (index > 0) { if (state.selected_dirty) try stashSelected(app); state.selected_index = index - 1; loadSelected(app) catch |err| { state.selected_index = index; loadSelected(app) catch {}; return err; }; traceNavigation(state, "older"); } }, .down => { if (index + 1 < state.entries.len) { if (state.selected_dirty) try stashSelected(app); state.selected_index = index + 1; loadSelected(app) catch |err| { state.selected_index = index; loadSelected(app) catch {}; return err; }; traceNavigation(state, "newer"); } else { try hideDraft(app); } }, } app.shell.render_requests.request(.footer); return true; } if (direction == .up and composerIsEmpty(app)) { try loadSelected(app); app.shell.render_requests.request(.footer); return true; } return false; } if (direction != .up or !composerIsEmpty(app)) return false; if (!app.worker.beginQueueReview(.manual)) return false; _ = ensureOpen(app, .manual) catch |err| { debug_trace.logf("input", "queue review snapshot failed reason=manual err={s}", .{@errorName(err)}); app.shell.render_requests.request(.footer); return true; }; app.shell.render_requests.request(.footer); return true; } pub fn markVisibleSelectionDirty(app: *App) void { if (!app.queued_prompt_review.visible) return; app.queued_prompt_review.selected_dirty = true; } pub fn hideVisibleDraft(app: *App) !bool { if (!app.queued_prompt_review.visible) return false; try hideDraft(app); app.shell.render_requests.request(.footer); return true; } pub fn cancelAllHiddenPostCancelQueued(app: *App) bool { const state = &app.queued_prompt_review; if (!postCancelAllAvailable(app)) return false; const dropped = state.entries.len; app.worker.clearQueuedPrompts( app.alloc, app.input_runtime.kill_ring.images.items, ); app.shell.render_requests.finishSubmittedPromptTransition(); state.clear(app.alloc); app.input_runtime.inputResetState().clearCurrent(app.alloc); paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); releasePendingImages(app); debug_trace.eventf("input", "queue_review_cancelled_all", .{}, "dropped={d}", .{dropped}); app.shell.render_requests.request(.footer); return true; } pub fn postCancelAllAvailable(app: *const App) bool { const state = &app.queued_prompt_review; return state.active() and state.reason.? == .post_cancel and !state.visible and composerIsEmpty(app); } pub fn submitPausedQueueUnchanged(app: *App) !bool { if (!app.queued_prompt_review.active() and app.worker.queueReviewReason() == null) return false; if (!try commitDirtyEntries(app)) return error.QueuedPromptNoLongerAvailable; app.input_runtime.inputResetState().clearCurrent(app.alloc); paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); releasePendingImages(app); finishReview(app, "unchanged_queue"); return true; } pub fn deleteEmptyVisibleDraft(app: *App) !bool { const state = &app.queued_prompt_review; if (!state.visible or !composerIsEmpty(app)) return false; const index = state.selected_index orelse return error.QueueReviewSelectionMissing; if (index >= state.entries.len) return error.QueueReviewSelectionMissing; const turn_id = state.entries[index].draft.turn_id; if (!app.worker.deleteQueuedPromptDraft( app.alloc, turn_id, app.input_runtime.kill_ring.images.items, )) { return error.QueuedPromptNoLongerAvailable; } var removed = state.entries[index]; const remaining_len = state.entries.len - 1; if (remaining_len == 0) { app.alloc.free(state.entries); state.entries = &.{}; removed.deinit(app.alloc); app.shell.render_requests.finishSubmittedPromptTransition(); debug_trace.eventf("input", "queue_review_draft_deleted", .{ .turn_id = turn_id }, "remaining=0", .{}); app.input_runtime.inputResetState().clearCurrent(app.alloc); paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); releasePendingImages(app); finishReview(app, "deleted_last_prompt"); return true; } const next = try app.alloc.alloc(ReviewEntry, remaining_len); var next_index: usize = 0; for (state.entries, 0..) |entry, entry_index| { if (entry_index == index) continue; next[next_index] = entry; next_index += 1; } app.alloc.free(state.entries); state.entries = next; removed.deinit(app.alloc); debug_trace.eventf("input", "queue_review_draft_deleted", .{ .turn_id = turn_id }, "remaining={d}", .{remaining_len}); state.selected_index = @min(index, remaining_len - 1); state.visible = false; state.selected_dirty = false; try loadSelected(app); app.shell.render_requests.request(.footer); return true; } pub fn promptAdmission( app: *App, review_input: []const u8, review_pasted_blocks: []const paste_blocks.PastedBlock, review_image_tokens: []const entity_spans.ImageTokenSpan, review_skill_tokens: []const registered_entities.SkillTokenSpan, ) !PromptAdmission { const state = &app.queued_prompt_review; if (!state.active() or !state.visible) return .enqueue; const index = state.selected_index orelse return error.QueueReviewSelectionMissing; if (index >= state.entries.len) return error.QueueReviewSelectionMissing; var pasted_blocks = try dupePastedBlocks( app.alloc, review_pasted_blocks, ); var pasted_blocks_owned = true; defer if (pasted_blocks_owned) paste_blocks.deinitBlocks(app.alloc, &pasted_blocks); var image_tokens: std.ArrayList(entity_spans.ImageTokenSpan) = .empty; defer image_tokens.deinit(app.alloc); try image_tokens.appendSlice(app.alloc, review_image_tokens); try writeSelectedDraft( app, review_input, app.pending_images.items, review_skill_tokens, ); paste_blocks.deinitBlocks( app.alloc, &state.entries[index].pasted_blocks, ); state.entries[index].pasted_blocks = pasted_blocks; pasted_blocks = .empty; pasted_blocks_owned = false; state.entries[index].image_tokens.deinit(app.alloc); state.entries[index].image_tokens = image_tokens; image_tokens = .empty; if (!try commitDirtyEntries(app)) return error.QueuedPromptNoLongerAvailable; finishReview(app, "edited_prompts"); return .replaced; } pub fn resumeAfterNewPrompt(app: *App) void { if (!app.queued_prompt_review.active() and app.worker.queueReviewReason() == null) return; finishReview(app, "new_prompt"); } pub fn reset(app: *App) void { app.queued_prompt_review.clear(app.alloc); } fn openAfterPause(app: *App, paused: bool) bool { if (!paused) return false; if (app.queued_prompt_review.active()) { app.queued_prompt_review.reason = .post_cancel; return true; } _ = ensureOpen(app, .post_cancel) catch |err| { debug_trace.logf("input", "queue review snapshot failed reason=post_cancel err={s}", .{@errorName(err)}); app.shell.render_requests.request(.footer); return true; }; return true; } fn ensureOpen(app: *App, reason: worker_runtime.QueueReviewReason) !bool { const drafts = try app.worker.snapshotQueuedPromptDrafts(app.alloc); if (drafts.len == 0) { worker_runtime.freeQueuedPromptDrafts(app.alloc, drafts); return false; } var drafts_owned = true; errdefer if (drafts_owned) worker_runtime.freeQueuedPromptDrafts(app.alloc, drafts); const entries = try app.alloc.alloc(ReviewEntry, drafts.len); var entries_owned = true; errdefer if (entries_owned) app.alloc.free(entries); for (entries, drafts) |*entry, draft| { entry.* = .{ .draft = draft }; } var tokens_initialized: usize = 0; errdefer for (entries[0..tokens_initialized]) |*entry| { entry.image_tokens.deinit(app.alloc); }; for (entries) |*entry| { const has_review_tokens = if (entry.draft.review_draft) |review| review.image_tokens.len > 0 else false; if (!has_review_tokens) { entry.image_tokens = try imageTokensForQueuedPrompt( app.alloc, entry.draft.reviewInput(), entry.draft.images, ); } tokens_initialized += 1; } for (entries) |*entry| { if (entry.draft.review_draft) |*review| { if (review.pasted_blocks.len > 0) { entry.pasted_blocks = .{ .items = review.pasted_blocks, .capacity = review.pasted_blocks.len, }; review.pasted_blocks = &.{}; } if (review.image_tokens.len > 0) { entry.image_tokens = .{ .items = review.image_tokens, .capacity = review.image_tokens.len, }; review.image_tokens = &.{}; } } } app.alloc.free(drafts); drafts_owned = false; app.queued_prompt_review.clear(app.alloc); const show_draft = composerIsEmpty(app); app.queued_prompt_review = .{ .entries = entries, .selected_index = entries.len - 1, .reason = reason, .visible = false, }; entries_owned = false; tokens_initialized = 0; if (show_draft) try loadSelected(app); return true; } fn loadSelected(app: *App) !void { const state = &app.queued_prompt_review; const index = state.selected_index orelse return error.QueueReviewSelectionMissing; if (index >= state.entries.len) return error.QueueReviewSelectionMissing; const entry = &state.entries[index]; const draft = entry.draft; const review_input = draft.reviewInput(); const review_skill_spans = draft.reviewSkillDisplaySpans(); const images = try types.dupeImageAttachmentSlice(app.alloc, draft.images); var images_transferred = false; errdefer if (!images_transferred) types.freeImageAttachmentSlice(app.alloc, images); var skill_tokens = try dupeVisualSkillTokens( app.alloc, review_skill_spans, ); errdefer deinitVisualSkillTokens(app.alloc, &skill_tokens); var prepared_input = try app.input_runtime.textReplacementState().prepare( app.alloc, review_input, ); defer prepared_input.deinit(app.alloc); try app.pending_images.ensureTotalCapacity(app.alloc, images.len); releasePendingImages(app); if (images.len > 0) { app.pending_images.appendSliceAssumeCapacity(images); app.alloc.free(images); } images_transferred = true; app.input_runtime.textReplacementState().commit(app.alloc, &prepared_input); app.input_runtime.entities.image_tokens.deinit(app.alloc); app.input_runtime.entities.image_tokens = entry.image_tokens; entry.image_tokens = .empty; app.input_runtime.entities.skill_tokens.deinit(app.alloc); app.input_runtime.entities.skill_tokens = skill_tokens; skill_tokens = .empty; paste_blocks.deinitBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); app.input_runtime.entities.pasted_blocks = entry.pasted_blocks; entry.pasted_blocks = .empty; state.visible = true; state.selected_dirty = false; } fn stashSelected(app: *App) !void { const state = &app.queued_prompt_review; const index = state.selected_index orelse return error.QueueReviewSelectionMissing; if (index >= state.entries.len) return error.QueueReviewSelectionMissing; try writeSelectedDraft( app, app.input_runtime.edit_state.input.items, app.pending_images.items, app.input_runtime.entities.skill_tokens.items, ); paste_blocks.deinitBlocks(app.alloc, &state.entries[index].pasted_blocks); state.entries[index].pasted_blocks = app.input_runtime.entities.pasted_blocks; app.input_runtime.entities.pasted_blocks = .empty; state.entries[index].image_tokens.deinit(app.alloc); state.entries[index].image_tokens = app.input_runtime.entities.image_tokens; app.input_runtime.entities.image_tokens = .empty; debug_trace.eventf( "input", "queue_review_draft_stashed", .{ .turn_id = state.entries[index].draft.turn_id }, "prompt_bytes={d}", .{state.entries[index].draft.prompt.len}, ); } fn writeSelectedDraft( app: *App, prompt: []const u8, images: []const types.ImageAttachment, skill_tokens: []const registered_entities.SkillTokenSpan, ) !void { const state = &app.queued_prompt_review; const index = state.selected_index orelse return error.QueueReviewSelectionMissing; if (index >= state.entries.len) return error.QueueReviewSelectionMissing; const prompt_copy = try app.alloc.dupe(u8, prompt); errdefer app.alloc.free(prompt_copy); const images_copy = try types.dupeImageAttachmentSlice(app.alloc, images); errdefer types.freeImageAttachmentSlice(app.alloc, images_copy); const skill_display_spans = try dupeSkillDisplaySpans(app.alloc, skill_tokens); errdefer worker_runtime.freeSkillDisplaySpans(app.alloc, skill_display_spans); const draft = &state.entries[index].draft; types.freeImageAttachmentSlice(app.alloc, draft.images); if (draft.review_draft) |review| { worker_runtime.freeQueueReviewDraft(app.alloc, review); } draft.images = images_copy; draft.review_draft = .{ .input = prompt_copy, .skill_display_spans = skill_display_spans, }; state.entries[index].dirty = true; state.selected_dirty = false; } fn commitDirtyEntries(app: *App) !bool { const drafts = try buildDirtyDraftsForCommit(app); defer worker_runtime.freeQueuedPromptDrafts(app.alloc, drafts); if (drafts.len == 0) return true; if (!try app.worker.replaceQueuedPromptDrafts(app.alloc, drafts)) return false; debug_trace.eventf("input", "queue_review_batch_committed", .{}, "updated={d}", .{drafts.len}); return true; } fn buildDirtyDraftsForCommit(app: *App) ![]worker_runtime.QueuedPromptDraft { const state = &app.queued_prompt_review; var dirty_count: usize = 0; for (state.entries) |entry| dirty_count += @intFromBool(entry.dirty); if (dirty_count == 0) return &.{}; const commits = try app.alloc.alloc(worker_runtime.QueuedPromptDraft, dirty_count); var filled: usize = 0; errdefer { for (commits[0..filled]) |draft| worker_runtime.freeQueuedPromptDraft(app.alloc, draft); app.alloc.free(commits); } for (state.entries) |entry| { if (!entry.dirty) continue; const review_input = entry.draft.reviewInput(); const review_skill_spans = entry.draft.reviewSkillDisplaySpans(); const expanded = try paste_blocks.expand( app.alloc, review_input, entry.pasted_blocks.items, ); defer if (expanded.owned) app.alloc.free(expanded.text); const trimmed = std.mem.trim(u8, expanded.text, " \t\r\n"); const prompt_copy = try app.alloc.dupe(u8, trimmed); errdefer app.alloc.free(prompt_copy); const images_copy = try types.dupeImageAttachmentSlice(app.alloc, entry.draft.images); errdefer types.freeImageAttachmentSlice(app.alloc, images_copy); const spans_copy = try projectSkillDisplaySpansForCommit( app.alloc, review_input, expanded.text, entry.pasted_blocks.items, review_skill_spans, ); errdefer worker_runtime.freeSkillDisplaySpans( app.alloc, spans_copy, );Lines 1–500 of 1310. Load more
const std = @import("std"); const debug_trace = @import("../shared/debug_trace.zig"); const image_attachments = @import("../images/image_attachments.zig"); const io_mod = @import("../shared/io.zig"); const types = @import("../shared/types.zig"); const worker_runtime = @import("../agent/worker_runtime.zig"); const paste_blocks = @import("../input/pasted_blocks.zig"); const registered_entities = @import("../input/registered_entities.zig"); const core_input_runtime = @import("../input/runtime.zig"); const entity_spans = @import("../shared/entity_spans.zig"); const visual_layout = @import("../../ui/input/visual_layout.zig"); const sort_utils = @import("../shared/sort_utils.zig"); pub const ReviewEntry = struct { draft: worker_runtime.QueuedPromptDraft, pasted_blocks: std.ArrayList(paste_blocks.PastedBlock) = .empty, image_tokens: std.ArrayList(entity_spans.ImageTokenSpan) = .empty, dirty: bool = false, fn deinit(self: *ReviewEntry, alloc: std.mem.Allocator) void { worker_runtime.freeQueuedPromptDraft(alloc, self.draft); paste_blocks.deinitBlocks(alloc, &self.pasted_blocks); self.image_tokens.deinit(alloc); } }; pub const State = struct { entries: []ReviewEntry = &.{}, selected_index: ?usize = null, reason: ?worker_runtime.QueueReviewReason = null, visible: bool = false, selected_dirty: bool = false, pub fn deinit(self: *State, alloc: std.mem.Allocator) void { self.clear(alloc); } pub fn clear(self: *State, alloc: std.mem.Allocator) void { for (self.entries) |*entry| entry.deinit(alloc); if (self.entries.len > 0) alloc.free(self.entries); self.* = .{}; } pub fn active(self: *const State) bool { return self.reason != null; } }; pub const PromptAdmission = enum { enqueue, replaced, }; pub fn Runtime(comptime App: type) type { return struct { pub fn requestCancelAndOpen(app: *App) bool { return openAfterPause(app, app.worker.requestCancelWithQueueReview()); } pub fn pauseAndOpenAfterModalCancel(app: *App) bool { return openAfterPause(app, app.worker.beginQueueReview(.post_cancel)); } pub fn routeVertical(app: *App, direction: visual_layout.Direction) !bool { const state = &app.queued_prompt_review; if (state.active()) { if (state.visible) { const index = state.selected_index orelse return false; switch (direction) { .up => { if (index > 0) { if (state.selected_dirty) try stashSelected(app); state.selected_index = index - 1; loadSelected(app) catch |err| { state.selected_index = index; loadSelected(app) catch {}; return err; }; traceNavigation(state, "older"); } }, .down => { if (index + 1 < state.entries.len) { if (state.selected_dirty) try stashSelected(app); state.selected_index = index + 1; loadSelected(app) catch |err| { state.selected_index = index; loadSelected(app) catch {}; return err; }; traceNavigation(state, "newer"); } else { try hideDraft(app); } }, } app.shell.render_requests.request(.footer); return true; } if (direction == .up and composerIsEmpty(app)) { try loadSelected(app); app.shell.render_requests.request(.footer); return true; } return false; } if (direction != .up or !composerIsEmpty(app)) return false; if (!app.worker.beginQueueReview(.manual)) return false; _ = ensureOpen(app, .manual) catch |err| { debug_trace.logf("input", "queue review snapshot failed reason=manual err={s}", .{@errorName(err)}); app.shell.render_requests.request(.footer); return true; }; app.shell.render_requests.request(.footer); return true; } pub fn markVisibleSelectionDirty(app: *App) void { if (!app.queued_prompt_review.visible) return; app.queued_prompt_review.selected_dirty = true; } pub fn hideVisibleDraft(app: *App) !bool { if (!app.queued_prompt_review.visible) return false; try hideDraft(app); app.shell.render_requests.request(.footer); return true; } pub fn cancelAllHiddenPostCancelQueued(app: *App) bool { const state = &app.queued_prompt_review; if (!postCancelAllAvailable(app)) return false; const dropped = state.entries.len; app.worker.clearQueuedPrompts( app.alloc, app.input_runtime.kill_ring.images.items, ); app.shell.render_requests.finishSubmittedPromptTransition(); state.clear(app.alloc); app.input_runtime.inputResetState().clearCurrent(app.alloc); paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); releasePendingImages(app); debug_trace.eventf("input", "queue_review_cancelled_all", .{}, "dropped={d}", .{dropped}); app.shell.render_requests.request(.footer); return true; } pub fn postCancelAllAvailable(app: *const App) bool { const state = &app.queued_prompt_review; return state.active() and state.reason.? == .post_cancel and !state.visible and composerIsEmpty(app); } pub fn submitPausedQueueUnchanged(app: *App) !bool { if (!app.queued_prompt_review.active() and app.worker.queueReviewReason() == null) return false; if (!try commitDirtyEntries(app)) return error.QueuedPromptNoLongerAvailable; app.input_runtime.inputResetState().clearCurrent(app.alloc); paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); releasePendingImages(app); finishReview(app, "unchanged_queue"); return true; } pub fn deleteEmptyVisibleDraft(app: *App) !bool { const state = &app.queued_prompt_review; if (!state.visible or !composerIsEmpty(app)) return false; const index = state.selected_index orelse return error.QueueReviewSelectionMissing; if (index >= state.entries.len) return error.QueueReviewSelectionMissing; const turn_id = state.entries[index].draft.turn_id; if (!app.worker.deleteQueuedPromptDraft( app.alloc, turn_id, app.input_runtime.kill_ring.images.items, )) { return error.QueuedPromptNoLongerAvailable; } var removed = state.entries[index]; const remaining_len = state.entries.len - 1; if (remaining_len == 0) { app.alloc.free(state.entries); state.entries = &.{}; removed.deinit(app.alloc); app.shell.render_requests.finishSubmittedPromptTransition(); debug_trace.eventf("input", "queue_review_draft_deleted", .{ .turn_id = turn_id }, "remaining=0", .{}); app.input_runtime.inputResetState().clearCurrent(app.alloc); paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); releasePendingImages(app); finishReview(app, "deleted_last_prompt"); return true; } const next = try app.alloc.alloc(ReviewEntry, remaining_len); var next_index: usize = 0; for (state.entries, 0..) |entry, entry_index| { if (entry_index == index) continue; next[next_index] = entry; next_index += 1; } app.alloc.free(state.entries); state.entries = next; removed.deinit(app.alloc); debug_trace.eventf("input", "queue_review_draft_deleted", .{ .turn_id = turn_id }, "remaining={d}", .{remaining_len}); state.selected_index = @min(index, remaining_len - 1); state.visible = false; state.selected_dirty = false; try loadSelected(app); app.shell.render_requests.request(.footer); return true; } pub fn promptAdmission( app: *App, review_input: []const u8, review_pasted_blocks: []const paste_blocks.PastedBlock, review_image_tokens: []const entity_spans.ImageTokenSpan, review_skill_tokens: []const registered_entities.SkillTokenSpan, ) !PromptAdmission { const state = &app.queued_prompt_review; if (!state.active() or !state.visible) return .enqueue; const index = state.selected_index orelse return error.QueueReviewSelectionMissing; if (index >= state.entries.len) return error.QueueReviewSelectionMissing; var pasted_blocks = try dupePastedBlocks( app.alloc, review_pasted_blocks, ); var pasted_blocks_owned = true; defer if (pasted_blocks_owned) paste_blocks.deinitBlocks(app.alloc, &pasted_blocks); var image_tokens: std.ArrayList(entity_spans.ImageTokenSpan) = .empty; defer image_tokens.deinit(app.alloc); try image_tokens.appendSlice(app.alloc, review_image_tokens); try writeSelectedDraft( app, review_input, app.pending_images.items, review_skill_tokens, ); paste_blocks.deinitBlocks( app.alloc, &state.entries[index].pasted_blocks, ); state.entries[index].pasted_blocks = pasted_blocks; pasted_blocks = .empty; pasted_blocks_owned = false; state.entries[index].image_tokens.deinit(app.alloc); state.entries[index].image_tokens = image_tokens; image_tokens = .empty; if (!try commitDirtyEntries(app)) return error.QueuedPromptNoLongerAvailable; finishReview(app, "edited_prompts"); return .replaced; } pub fn resumeAfterNewPrompt(app: *App) void { if (!app.queued_prompt_review.active() and app.worker.queueReviewReason() == null) return; finishReview(app, "new_prompt"); } pub fn reset(app: *App) void { app.queued_prompt_review.clear(app.alloc); } fn openAfterPause(app: *App, paused: bool) bool { if (!paused) return false; if (app.queued_prompt_review.active()) { app.queued_prompt_review.reason = .post_cancel; return true; } _ = ensureOpen(app, .post_cancel) catch |err| { debug_trace.logf("input", "queue review snapshot failed reason=post_cancel err={s}", .{@errorName(err)}); app.shell.render_requests.request(.footer); return true; }; return true; } fn ensureOpen(app: *App, reason: worker_runtime.QueueReviewReason) !bool { const drafts = try app.worker.snapshotQueuedPromptDrafts(app.alloc); if (drafts.len == 0) { worker_runtime.freeQueuedPromptDrafts(app.alloc, drafts); return false; } var drafts_owned = true; errdefer if (drafts_owned) worker_runtime.freeQueuedPromptDrafts(app.alloc, drafts); const entries = try app.alloc.alloc(ReviewEntry, drafts.len); var entries_owned = true; errdefer if (entries_owned) app.alloc.free(entries); for (entries, drafts) |*entry, draft| { entry.* = .{ .draft = draft }; } var tokens_initialized: usize = 0; errdefer for (entries[0..tokens_initialized]) |*entry| { entry.image_tokens.deinit(app.alloc); }; for (entries) |*entry| { const has_review_tokens = if (entry.draft.review_draft) |review| review.image_tokens.len > 0 else false; if (!has_review_tokens) { entry.image_tokens = try imageTokensForQueuedPrompt( app.alloc, entry.draft.reviewInput(), entry.draft.images, ); } tokens_initialized += 1; } for (entries) |*entry| { if (entry.draft.review_draft) |*review| { if (review.pasted_blocks.len > 0) { entry.pasted_blocks = .{ .items = review.pasted_blocks, .capacity = review.pasted_blocks.len, }; review.pasted_blocks = &.{}; } if (review.image_tokens.len > 0) { entry.image_tokens = .{ .items = review.image_tokens, .capacity = review.image_tokens.len, }; review.image_tokens = &.{}; } } } app.alloc.free(drafts); drafts_owned = false; app.queued_prompt_review.clear(app.alloc); const show_draft = composerIsEmpty(app); app.queued_prompt_review = .{ .entries = entries, .selected_index = entries.len - 1, .reason = reason, .visible = false, }; entries_owned = false; tokens_initialized = 0; if (show_draft) try loadSelected(app); return true; } fn loadSelected(app: *App) !void { const state = &app.queued_prompt_review; const index = state.selected_index orelse return error.QueueReviewSelectionMissing; if (index >= state.entries.len) return error.QueueReviewSelectionMissing; const entry = &state.entries[index]; const draft = entry.draft; const review_input = draft.reviewInput(); const review_skill_spans = draft.reviewSkillDisplaySpans(); const images = try types.dupeImageAttachmentSlice(app.alloc, draft.images); var images_transferred = false; errdefer if (!images_transferred) types.freeImageAttachmentSlice(app.alloc, images); var skill_tokens = try dupeVisualSkillTokens( app.alloc, review_skill_spans, ); errdefer deinitVisualSkillTokens(app.alloc, &skill_tokens); var prepared_input = try app.input_runtime.textReplacementState().prepare( app.alloc, review_input, ); defer prepared_input.deinit(app.alloc); try app.pending_images.ensureTotalCapacity(app.alloc, images.len); releasePendingImages(app); if (images.len > 0) { app.pending_images.appendSliceAssumeCapacity(images); app.alloc.free(images); } images_transferred = true; app.input_runtime.textReplacementState().commit(app.alloc, &prepared_input); app.input_runtime.entities.image_tokens.deinit(app.alloc); app.input_runtime.entities.image_tokens = entry.image_tokens; entry.image_tokens = .empty; app.input_runtime.entities.skill_tokens.deinit(app.alloc); app.input_runtime.entities.skill_tokens = skill_tokens; skill_tokens = .empty; paste_blocks.deinitBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); app.input_runtime.entities.pasted_blocks = entry.pasted_blocks; entry.pasted_blocks = .empty; state.visible = true; state.selected_dirty = false; } fn stashSelected(app: *App) !void { const state = &app.queued_prompt_review; const index = state.selected_index orelse return error.QueueReviewSelectionMissing; if (index >= state.entries.len) return error.QueueReviewSelectionMissing; try writeSelectedDraft( app, app.input_runtime.edit_state.input.items, app.pending_images.items, app.input_runtime.entities.skill_tokens.items, ); paste_blocks.deinitBlocks(app.alloc, &state.entries[index].pasted_blocks); state.entries[index].pasted_blocks = app.input_runtime.entities.pasted_blocks; app.input_runtime.entities.pasted_blocks = .empty; state.entries[index].image_tokens.deinit(app.alloc); state.entries[index].image_tokens = app.input_runtime.entities.image_tokens; app.input_runtime.entities.image_tokens = .empty; debug_trace.eventf( "input", "queue_review_draft_stashed", .{ .turn_id = state.entries[index].draft.turn_id }, "prompt_bytes={d}", .{state.entries[index].draft.prompt.len}, ); } fn writeSelectedDraft( app: *App, prompt: []const u8, images: []const types.ImageAttachment, skill_tokens: []const registered_entities.SkillTokenSpan, ) !void { const state = &app.queued_prompt_review; const index = state.selected_index orelse return error.QueueReviewSelectionMissing; if (index >= state.entries.len) return error.QueueReviewSelectionMissing; const prompt_copy = try app.alloc.dupe(u8, prompt); errdefer app.alloc.free(prompt_copy); const images_copy = try types.dupeImageAttachmentSlice(app.alloc, images); errdefer types.freeImageAttachmentSlice(app.alloc, images_copy); const skill_display_spans = try dupeSkillDisplaySpans(app.alloc, skill_tokens); errdefer worker_runtime.freeSkillDisplaySpans(app.alloc, skill_display_spans); const draft = &state.entries[index].draft; types.freeImageAttachmentSlice(app.alloc, draft.images); if (draft.review_draft) |review| { worker_runtime.freeQueueReviewDraft(app.alloc, review); } draft.images = images_copy; draft.review_draft = .{ .input = prompt_copy, .skill_display_spans = skill_display_spans, }; state.entries[index].dirty = true; state.selected_dirty = false; } fn commitDirtyEntries(app: *App) !bool { const drafts = try buildDirtyDraftsForCommit(app); defer worker_runtime.freeQueuedPromptDrafts(app.alloc, drafts); if (drafts.len == 0) return true; if (!try app.worker.replaceQueuedPromptDrafts(app.alloc, drafts)) return false; debug_trace.eventf("input", "queue_review_batch_committed", .{}, "updated={d}", .{drafts.len}); return true; } fn buildDirtyDraftsForCommit(app: *App) ![]worker_runtime.QueuedPromptDraft { const state = &app.queued_prompt_review; var dirty_count: usize = 0; for (state.entries) |entry| dirty_count += @intFromBool(entry.dirty); if (dirty_count == 0) return &.{}; const commits = try app.alloc.alloc(worker_runtime.QueuedPromptDraft, dirty_count); var filled: usize = 0; errdefer { for (commits[0..filled]) |draft| worker_runtime.freeQueuedPromptDraft(app.alloc, draft); app.alloc.free(commits); } for (state.entries) |entry| { if (!entry.dirty) continue; const review_input = entry.draft.reviewInput(); const review_skill_spans = entry.draft.reviewSkillDisplaySpans(); const expanded = try paste_blocks.expand( app.alloc, review_input, entry.pasted_blocks.items, ); defer if (expanded.owned) app.alloc.free(expanded.text); const trimmed = std.mem.trim(u8, expanded.text, " \t\r\n"); const prompt_copy = try app.alloc.dupe(u8, trimmed); errdefer app.alloc.free(prompt_copy); const images_copy = try types.dupeImageAttachmentSlice(app.alloc, entry.draft.images); errdefer types.freeImageAttachmentSlice(app.alloc, images_copy); const spans_copy = try projectSkillDisplaySpansForCommit( app.alloc, review_input, expanded.text, entry.pasted_blocks.items, review_skill_spans, ); errdefer worker_runtime.freeSkillDisplaySpans( app.alloc, spans_copy, );