Skip to content

Commit b7f3335

Browse files
authored
Feat: Agentic chat UX changes (#660)
1 parent cb247ad commit b7f3335

File tree

2 files changed

+103
-73
lines changed

2 files changed

+103
-73
lines changed

crates/q_cli/src/cli/chat/mod.rs

Lines changed: 84 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ pub async fn chat(initial_input: Option<String>) -> Result<ExitCode> {
119119
if is_interactive {
120120
queue!(
121121
output,
122+
cursor::MoveToColumn(0),
122123
style::SetAttribute(Attribute::Reset),
123124
style::ResetColor,
124125
cursor::Show
@@ -130,63 +131,6 @@ pub async fn chat(initial_input: Option<String>) -> Result<ExitCode> {
130131
result.map(|_| ExitCode::SUCCESS)
131132
}
132133

133-
/// Testing helper
134-
fn split_tool_use_event(value: &Map<String, serde_json::Value>) -> Vec<ChatResponseStream> {
135-
let tool_use_id = value.get("tool_use_id").unwrap().as_str().unwrap().to_string();
136-
let name = value.get("name").unwrap().as_str().unwrap().to_string();
137-
let args_str = value.get("args").unwrap().to_string();
138-
let split_point = args_str.len() / 2;
139-
vec![
140-
ChatResponseStream::ToolUseEvent {
141-
tool_use_id: tool_use_id.clone(),
142-
name: name.clone(),
143-
input: None,
144-
stop: None,
145-
},
146-
ChatResponseStream::ToolUseEvent {
147-
tool_use_id: tool_use_id.clone(),
148-
name: name.clone(),
149-
input: Some(args_str.split_at(split_point).0.to_string()),
150-
stop: None,
151-
},
152-
ChatResponseStream::ToolUseEvent {
153-
tool_use_id: tool_use_id.clone(),
154-
name: name.clone(),
155-
input: Some(args_str.split_at(split_point).1.to_string()),
156-
stop: None,
157-
},
158-
ChatResponseStream::ToolUseEvent {
159-
tool_use_id: tool_use_id.clone(),
160-
name: name.clone(),
161-
input: None,
162-
stop: Some(true),
163-
},
164-
]
165-
}
166-
167-
/// Testing helper
168-
fn create_stream(model_responses: serde_json::Value) -> StreamingClient {
169-
let mut mock = Vec::new();
170-
for response in model_responses.as_array().unwrap() {
171-
let mut stream = Vec::new();
172-
for event in response.as_array().unwrap() {
173-
match event {
174-
serde_json::Value::String(assistant_text) => {
175-
stream.push(ChatResponseStream::AssistantResponseEvent {
176-
content: assistant_text.to_string(),
177-
});
178-
},
179-
serde_json::Value::Object(tool_use) => {
180-
stream.append(&mut split_tool_use_event(tool_use));
181-
},
182-
other => panic!("Unexpected value: {:?}", other),
183-
}
184-
}
185-
mock.push(stream);
186-
}
187-
StreamingClient::mock(mock)
188-
}
189-
190134
/// The tools that can be used by the model.
191135
#[derive(Debug, Clone)]
192136
pub struct ToolConfiguration {
@@ -330,7 +274,10 @@ Hi, I'm <g>Amazon Q</g>. Ask me anything.
330274
};
331275
},
332276
Err(err) => {
333-
bail!("An error occurred reading the model's response: {:?}", err);
277+
bail!(
278+
"We're having trouble responding right now, please try again later: {:?}",
279+
err
280+
);
334281
},
335282
}
336283

@@ -508,24 +455,36 @@ Hi, I'm <g>Amazon Q</g>. Ask me anything.
508455
if self.tool_use_recursions > MAX_TOOL_USE_RECURSIONS {
509456
bail!("Exceeded max tool use recursion limit: {}", MAX_TOOL_USE_RECURSIONS);
510457
}
511-
for (_, tool) in &queued_tools {
512-
queue!(self.output, style::Print(format!("{}\n", "▔".repeat(terminal_width))))?;
513-
queue!(self.output, style::SetAttribute(Attribute::Bold))?;
514-
queue!(self.output, style::Print(format!("{}\n", tool.display_name())))?;
515-
queue!(self.output, style::SetAttribute(Attribute::NormalIntensity))?;
516-
queue!(self.output, style::Print(format!("{}\n\n", "▁".repeat(terminal_width))))?;
458+
459+
for (i, (_, tool)) in queued_tools.iter().enumerate() {
460+
queue!(
461+
self.output,
462+
style::SetForegroundColor(Color::Cyan),
463+
style::Print(format!("{}. {}\n", i + 1, tool.display_name())),
464+
style::SetForegroundColor(Color::Reset),
465+
style::SetForegroundColor(Color::DarkGrey),
466+
style::Print(format!("{}\n", "▔".repeat(terminal_width))),
467+
)?;
517468
tool.queue_description(&self.ctx, self.output)?;
518469
queue!(self.output, style::Print("\n"))?;
519470
}
520-
queue!(self.output, style::Print("▁".repeat(terminal_width)))?;
521-
queue!(self.output, style::Print("\n\n"))?;
471+
522472
execute!(
523473
self.output,
524-
style::Print("Enter "),
474+
style::SetForegroundColor(Color::DarkGrey),
475+
style::Print("▁".repeat(terminal_width)),
476+
style::ResetColor,
477+
style::Print("\n\nEnter "),
525478
style::SetForegroundColor(Color::Green),
526479
style::Print("y"),
527480
style::ResetColor,
528-
style::Print(" to consent to running these tools, or anything else to continue your conversation.\n\n")
481+
style::Print(format!(
482+
" to run {}, or otherwise continue your conversation.\n\n",
483+
match queued_tools.len() == 1 {
484+
true => "this tool",
485+
false => "these tools",
486+
}
487+
)),
529488
)?;
530489
}
531490

@@ -727,6 +686,63 @@ impl From<ToolUseEventBuilder> for fig_telemetry::EventType {
727686
}
728687
}
729688

689+
/// Testing helper
690+
fn split_tool_use_event(value: &Map<String, serde_json::Value>) -> Vec<ChatResponseStream> {
691+
let tool_use_id = value.get("tool_use_id").unwrap().as_str().unwrap().to_string();
692+
let name = value.get("name").unwrap().as_str().unwrap().to_string();
693+
let args_str = value.get("args").unwrap().to_string();
694+
let split_point = args_str.len() / 2;
695+
vec![
696+
ChatResponseStream::ToolUseEvent {
697+
tool_use_id: tool_use_id.clone(),
698+
name: name.clone(),
699+
input: None,
700+
stop: None,
701+
},
702+
ChatResponseStream::ToolUseEvent {
703+
tool_use_id: tool_use_id.clone(),
704+
name: name.clone(),
705+
input: Some(args_str.split_at(split_point).0.to_string()),
706+
stop: None,
707+
},
708+
ChatResponseStream::ToolUseEvent {
709+
tool_use_id: tool_use_id.clone(),
710+
name: name.clone(),
711+
input: Some(args_str.split_at(split_point).1.to_string()),
712+
stop: None,
713+
},
714+
ChatResponseStream::ToolUseEvent {
715+
tool_use_id: tool_use_id.clone(),
716+
name: name.clone(),
717+
input: None,
718+
stop: Some(true),
719+
},
720+
]
721+
}
722+
723+
/// Testing helper
724+
fn create_stream(model_responses: serde_json::Value) -> StreamingClient {
725+
let mut mock = Vec::new();
726+
for response in model_responses.as_array().unwrap() {
727+
let mut stream = Vec::new();
728+
for event in response.as_array().unwrap() {
729+
match event {
730+
serde_json::Value::String(assistant_text) => {
731+
stream.push(ChatResponseStream::AssistantResponseEvent {
732+
content: assistant_text.to_string(),
733+
});
734+
},
735+
serde_json::Value::Object(tool_use) => {
736+
stream.append(&mut split_tool_use_event(tool_use));
737+
},
738+
other => panic!("Unexpected value: {:?}", other),
739+
}
740+
}
741+
mock.push(stream);
742+
}
743+
StreamingClient::mock(mock)
744+
}
745+
730746
#[cfg(test)]
731747
mod tests {
732748
use fig_api_client::model::ChatResponseStream;

crates/q_cli/src/cli/chat/tools/execute_bash.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ use std::io::Write;
22
use std::process::Stdio;
33

44
use bstr::ByteSlice;
5-
use crossterm::queue;
65
use crossterm::style::{
76
self,
87
Color,
98
};
9+
use crossterm::{
10+
execute,
11+
queue,
12+
};
1013
use eyre::{
1114
Context as EyreContext,
1215
Result,
@@ -54,6 +57,11 @@ impl ExecuteBash {
5457
let status = output.status.code().unwrap_or(0).to_string();
5558
let stdout = output.stdout.to_str_lossy();
5659
let stderr = output.stderr.to_str_lossy();
60+
61+
if !self.interactive {
62+
execute!(updates, style::Print(&stdout))?;
63+
}
64+
5765
Ok(InvokeOutput {
5866
output: OutputKind::Json(serde_json::json!({
5967
"exit_status": status,
@@ -64,13 +72,19 @@ impl ExecuteBash {
6472
}
6573

6674
pub fn queue_description(&self, updates: &mut impl Write) -> Result<()> {
67-
Ok(queue!(
75+
queue!(
6876
updates,
69-
style::Print("I will run the following command using your bash environment:\n"),
77+
style::Print("I will run the following shell command: "),
7078
style::SetForegroundColor(Color::Green),
7179
style::Print(&self.command),
72-
style::ResetColor,
73-
)?)
80+
)?;
81+
82+
// TODO: Could use graphemes for a better heuristic
83+
if self.command.len() > 20 {
84+
queue!(updates, style::Print("\n"),)?;
85+
}
86+
87+
Ok(queue!(updates, style::Print(&self.command), style::ResetColor)?)
7488
}
7589

7690
pub async fn validate(&mut self, _ctx: &Context) -> Result<()> {

0 commit comments

Comments
 (0)