Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions displays/src/tabs/ai_playground/enhanced.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1922,7 +1922,7 @@ impl EnhancedAiPlayground {
match live {
Ok(Some(thread)) if viewer.as_ref().is_some_and(|v| v.may_steer(thread.assignee.as_ref())) => {
let kind = if thread.is_busy() && kind == "start" {
"queue"
"steer"
} else {
kind
};
Expand Down Expand Up @@ -2046,7 +2046,7 @@ impl EnhancedAiPlayground {
});
}

/// Resumes the held queue, removes a queued message, or takes one back into the composer.
/// Resumes the held queue, or removes, sends now or takes back one queued message.
fn apply_queue_action(&mut self, tid: &str, action: QueueAction) {
let thread = RecordId::new("agent_thread", tid);
match action {
Expand All @@ -2059,6 +2059,20 @@ impl EnhancedAiPlayground {
}
});
}
QueueAction::SendNow(id) => {
self.waiting.retain(|w| w.id != id);
PlatformSpawner::spawn(async move {
match AgentTurn::take_back(&id).await {
Ok(Some(turn)) => {
if let Err(e) = AgentTurn::ask_with(&thread, "steer", &turn.text, &turn.images).await {
log::warn!("could not send a queued message now: {e}");
}
}
Ok(None) => {}
Err(e) => log::warn!("could not take a queued message out of the queue: {e}"),
}
});
}
QueueAction::Edit(id) => {
self.waiting.retain(|w| w.id != id);
let tx = self.taken_back_tx.clone();
Expand Down
20 changes: 8 additions & 12 deletions displays/src/ui_tools/agent_chat/composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl Composer {
ui.add_enabled_ui(enabled, |ui| {
self.attachment_strip(ui);
let hint = if busy {
"The agent is working: Enter queues this for when it finishes"
"The agent is working: Enter tells it now; the queue button holds a message for after this turn"
} else {
"Message the agent (Shift+Enter for a new line; paste or drop pictures and files)"
};
Expand Down Expand Up @@ -169,7 +169,7 @@ impl Composer {
});
if enter {
action = self
.submit(text, if busy { "queue" } else { "start" })
.submit(text, if busy { "steer" } else { "start" })
.or(action);
ui.memory_mut(|m| m.request_focus(text_id));
}
Expand Down Expand Up @@ -264,7 +264,7 @@ impl Composer {
ui.vertical(|ui| {
if ui
.add_enabled(ready, Button::new(icons::SEND_NOW).min_size(half))
.on_hover_text("Send now: tell the agent while it works")
.on_hover_text("Send now: the agent reads it after its current step (Enter)")
.clicked()
{
action = self.submit(text, "steer");
Expand Down Expand Up @@ -300,11 +300,7 @@ impl Composer {
#[cfg(any(target_os = "ios", target_os = "android"))]
let send_size = vec2(SIDE_BUTTON_W, height);
let (icon, hover, kind) = if busy {
(
icons::QUEUE,
"Queue: send when the agent finishes this turn (Enter)",
"queue",
)
(icons::QUEUE, "Queue: send when the agent finishes this turn", "queue")
} else {
(icons::SEND, "Send (Enter)", "start")
};
Expand Down Expand Up @@ -471,7 +467,7 @@ mod tests {
}

#[test]
fn enter_sends_when_idle_and_queues_while_busy() {
fn enter_sends_when_idle_and_steers_while_busy() {
let ctx = Context::default();
let mut composer = Composer::default();
let mut text = "check the disks".to_string();
Expand All @@ -493,16 +489,16 @@ mod tests {
assert!(text.is_empty(), "the box is cleared once sent");

text = "then the event log".to_string();
let queued = frame(
let steered = frame(
&ctx,
&mut composer,
&mut text,
true,
vec![enter(Modifiers::NONE)],
);
assert!(
matches!(queued, Some(ComposerAction::Send { kind: "queue", .. })),
"{queued:?}"
matches!(steered, Some(ComposerAction::Send { kind: "steer", .. })),
"{steered:?}"
);
}

Expand Down
67 changes: 65 additions & 2 deletions displays/src/ui_tools/agent_chat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ pub fn approve_all_chip(ui: &mut Ui, thread: &AgentThread, steerable: bool) -> b
pub enum QueueAction {
Remove(RecordId),
Edit(RecordId),
SendNow(RecordId),
Resume,
}

Expand Down Expand Up @@ -229,7 +230,7 @@ pub fn queue_strip(ui: &mut Ui, waiting: &[QueuedTurn]) -> Option<QueueAction> {
for turn in waiting {
ui.push_id(turn.id.key_string(), |ui| {
ui.horizontal(|ui| {
let buttons = 2.0 * (24.0 + ui.spacing().item_spacing.x);
let buttons = 3.0 * (24.0 + ui.spacing().item_spacing.x);
let first = turn
.text
.lines()
Expand Down Expand Up @@ -263,6 +264,13 @@ pub fn queue_strip(ui: &mut Ui, waiting: &[QueuedTurn]) -> Option<QueueAction> {
{
action = Some(QueueAction::Edit(turn.id.clone()));
}
if ui
.small_button(icons::SEND_NOW)
.on_hover_text("Send now: the agent reads it after its current step")
.clicked()
{
action = Some(QueueAction::SendNow(turn.id.clone()));
}
});
});
});
Expand Down Expand Up @@ -401,7 +409,62 @@ pub fn hovered(ui: &Ui, rect: Rect) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use eframe::egui::{Context, RawInput};
use eframe::egui::{Context, Event, Modifiers, PointerButton, RawInput, pos2};

/// Every action the strip answers to a click anywhere in its top 80 points, right to left.
fn strip_actions(waiting: &[QueuedTurn]) -> Vec<QueueAction> {
let ctx = Context::default();
let run = |events: Vec<Event>| {
let input = RawInput {
events,
screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(420.0, 200.0))),
..Default::default()
};
let mut action = None;
let mut out = ctx.run_ui(input, |ui| action = queue_strip(ui, waiting));
out.textures_delta.clear();
action
};
let mut found: Vec<QueueAction> = Vec::new();
for y in (2..80).step_by(4) {
for x in (2..420).rev().step_by(4) {
let pos = pos2(x as f32, y as f32);
let button = |pressed| Event::PointerButton {
pos,
button: PointerButton::Primary,
pressed,
modifiers: Modifiers::NONE,
};
let action =
run(vec![Event::PointerMoved(pos), button(true)]).or(run(vec![button(false)]));
if let Some(action) = action.filter(|a| !found.contains(a)) {
found.push(action);
}
}
}
found
}

#[test]
fn each_queued_message_can_be_removed_taken_back_or_sent_now() {
let id = RecordId::new("agent_turn", "q1");
let waiting = vec![QueuedTurn {
id: id.clone(),
text: "i plugged in the customer's two drives as well".into(),
status: "queued".into(),
tech: None,
image_names: Vec::new(),
created_at: None,
}];
assert_eq!(
strip_actions(&waiting),
vec![
QueueAction::Remove(id.clone()),
QueueAction::Edit(id.clone()),
QueueAction::SendNow(id)
]
);
}

#[test]
fn the_context_colour_ramps_from_green_through_amber_to_red() {
Expand Down
Loading