use std::io::Write;
use chat_rs::{
ChatBuilder, StreamEvent,
openai::OpenAIBuilder,
types::messages::{Messages, content},
};
use futures::StreamExt;
use tokio::io::{AsyncBufReadExt, BufReader};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let model = std::env::var("OPENAI_MODEL").unwrap_or_else(|_| "gpt-4o".to_string());
let client = OpenAIBuilder::new().with_model(model.clone()).build();
let mut chat = ChatBuilder::new()
.with_model(client)
.with_input_stream()
.with_max_steps(8)
.build();
let (lines_tx, mut lines_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
tokio::spawn(async move {
let mut reader = BufReader::new(tokio::io::stdin()).lines();
while let Ok(Some(line)) = reader.next_line().await {
if lines_tx.send(line).is_err() {
break;
}
}
});
println!("Chatting with {model}. Type while it replies to barge in. /quit to exit.");
let mut messages = Messages::default();
'session: loop {
print!("\n\x1b[1;32m> \x1b[0m");
std::io::stdout().flush()?;
let prompt = match lines_rx.recv().await {
Some(line) => line,
None => break,
};
if prompt.trim() == "/quit" {
break;
}
messages.push(content::from_user([prompt]));
let (input, mut output) = chat.stream(&mut messages).await.map_err(|f| f.err)?.split();
let mut stdout = std::io::stdout().lock();
loop {
tokio::select! {
event = output.next() => {
match event {
Some(Ok(StreamEvent::TextChunk(text))) => {
write!(stdout, "{text}")?;
stdout.flush()?;
}
Some(Ok(StreamEvent::ReasoningChunk(thought))) => {
write!(stdout, "\x1b[90m{thought}\x1b[0m")?;
stdout.flush()?;
}
Some(Ok(StreamEvent::Done(_))) | None => {
writeln!(stdout)?;
break;
}
Some(Ok(_)) => {}
Some(Err(failure)) => {
writeln!(stdout, "\n[error] {}", failure.err)?;
break;
}
}
}
line = lines_rx.recv() => {
match line {
Some(l) if l.trim() == "/quit" => break 'session,
Some(l) => {
writeln!(stdout, "\n\x1b[33m[+ {}]\x1b[0m", l.trim())?;
let _ = input.send(l);
}
None => break 'session,
}
}
}
}
}
println!("bye");
Ok(())
}