use envision::component::{
Component, ConversationMessage, ConversationRole, ConversationView, ConversationViewMessage,
ConversationViewState, MessageBlock,
};
use envision::prelude::*;
struct ConversationApp;
#[derive(Clone)]
struct State {
conversation: ConversationViewState,
}
#[derive(Clone, Debug)]
enum Msg {
Conversation(ConversationViewMessage),
Quit,
}
impl App for ConversationApp {
type State = State;
type Message = Msg;
fn init() -> (State, Command<Msg>) {
let mut conversation = ConversationViewState::new()
.with_title("AI Conversation")
.with_show_timestamps(true);
conversation.push_message(
ConversationMessage::new(
ConversationRole::System,
"You are a helpful coding assistant.",
)
.with_timestamp("14:00"),
);
conversation.push_message(
ConversationMessage::new(
ConversationRole::User,
"Can you write a function to compute the Fibonacci sequence in Rust?",
)
.with_timestamp("14:01"),
);
conversation.push_message(
ConversationMessage::with_blocks(
ConversationRole::Assistant,
vec![
MessageBlock::thinking(
"The user wants a Fibonacci function in Rust.\n\
I can provide both iterative and recursive approaches.",
),
MessageBlock::text("Here is an iterative Fibonacci function:"),
MessageBlock::code(
"fn fibonacci(n: u64) -> u64 {\n \
if n <= 1 {\n \
return n;\n \
}\n \
let mut a = 0;\n \
let mut b = 1;\n \
for _ in 2..=n {\n \
let temp = a + b;\n \
a = b;\n \
b = temp;\n \
}\n \
b\n\
}",
Some("rust"),
),
MessageBlock::text("This runs in O(n) time with O(1) space."),
],
)
.with_timestamp("14:01"),
);
conversation.push_message(
ConversationMessage::new(
ConversationRole::User,
"Can you test that with a few values?",
)
.with_timestamp("14:02"),
);
conversation.push_message(
ConversationMessage::with_blocks(
ConversationRole::Assistant,
vec![
MessageBlock::text("Let me run some test values."),
MessageBlock::tool_use("code_runner").with_input("fibonacci(0) = 0\nfibonacci(1) = 1\nfibonacci(10) = 55\nfibonacci(20) = 6765"),
],
)
.with_timestamp("14:02"),
);
conversation.push_message(
ConversationMessage::with_blocks(
ConversationRole::Tool,
vec![MessageBlock::text("All tests passed successfully.")],
)
.with_timestamp("14:02"),
);
conversation.push_message(
ConversationMessage::with_blocks(
ConversationRole::Tool,
vec![MessageBlock::error("Rate limit exceeded (example error)")],
)
.with_timestamp("14:03"),
);
conversation.push_message(
ConversationMessage::new(
ConversationRole::Assistant,
"All test cases passed! The function works correctly.",
)
.with_timestamp("14:03"),
);
let state = State { conversation };
(state, Command::none())
}
fn update(state: &mut State, msg: Msg) -> Command<Msg> {
match msg {
Msg::Conversation(m) => {
ConversationView::update(&mut state.conversation, m);
}
Msg::Quit => return Command::quit(),
}
Command::none()
}
fn view(state: &State, frame: &mut Frame) {
let theme = Theme::default();
ConversationView::view(
&state.conversation,
&mut RenderContext::new(frame, frame.area(), &theme),
);
}
fn handle_event_with_state(state: &Self::State, event: &Event) -> Option<Msg> {
if let Some(key) = event.as_key() {
if matches!(key.code, Key::Esc) {
return Some(Msg::Quit);
}
}
ConversationView::handle_event(
&state.conversation,
event,
&EventContext::new().focused(true),
)
.map(Msg::Conversation)
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut vt = Runtime::<ConversationApp, _>::virtual_builder(80, 30).build()?;
println!("=== Conversation View Example ===\n");
vt.tick()?;
println!("Initial state:");
println!("{}\n", vt.display());
vt.send(Event::char('k'));
vt.tick()?;
println!("After scrolling up:");
println!("{}\n", vt.display());
vt.send(Event::char('g'));
vt.tick()?;
println!("At the top:");
println!("{}\n", vt.display());
vt.send(Event::char('G'));
vt.tick()?;
println!("At the bottom:");
println!("{}\n", vt.display());
println!(
"Total messages: {}",
vt.state().conversation.message_count()
);
Ok(())
}