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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "routecode-cli"
version = "0.1.8"
version = "0.1.9"
edition = "2021"
authors = ["SpeerX <anasrhnim07@gmail.com>"]
description = "CLI application for RouteCode"
Expand Down
9 changes: 1 addition & 8 deletions apps/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,7 @@ async fn main() -> anyhow::Result<()> {

let api_key = match api_key {
Some(key) => key,
None => {
if cli.debug {
"your-api-key-here".to_string()
} else {
anyhow::bail!("API Key for {} not found. Set {}_API_KEY environment variable or configure it in ~/.routecode/config.json",
provider_name, provider_name.to_uppercase());
}
}
None => "".to_string(),
};

let provider = if provider_name == "vertex" {
Expand Down
8 changes: 8 additions & 0 deletions apps/cli/src/ui/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,18 @@ pub async fn handle_command(app: &mut App, input: &str) {
}
app.history.push(Message::system(format!("Session resumed: {}", name)));
app.screen = Screen::Session;
} else {
app.history.push(Message::system(format!("Error: Session '{}' not found", name)));
}
} else {
app.history.push(Message::system("Usage: /resume <session_name>"));
}
}
"/export" => {
if app.history.is_empty() {
app.history.push(Message::system("No messages to export in current session."));
return;
}
let name = args.first().map(|s| s.to_string()).unwrap_or_else(|| app.session_id.clone());
let session = routecode_sdk::utils::storage::Session {
messages: app.history.clone(),
Expand Down
75 changes: 31 additions & 44 deletions apps/cli/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ pub struct App {
pub cached_thinking_hovered: bool,
pub cached_total_height: usize,
pub cached_text: Option<ratatui::text::Text<'static>>,
pub cached_layout: Vec<(usize, bool)>,
pub pending_command_confirmation: Option<(String, String, ConfirmationSender)>,
pub inputting_command_feedback: bool,
pub show_user_msg_modal: Option<usize>,
Expand Down Expand Up @@ -275,6 +276,7 @@ impl App {
cached_thinking_hovered: false,
cached_total_height: 0,
cached_text: None,
cached_layout: Vec::new(),
pending_command_confirmation: None,
inputting_command_feedback: false,
show_user_msg_modal: None,
Expand Down Expand Up @@ -378,27 +380,8 @@ pub fn compute_thinking_hover(app: &App, size: ratatui::layout::Rect) -> bool {
// The absolute visual row including scroll
let target_visual_row = viewport_row as usize + app.history_scroll as usize;

// Build the history text and compute wrapping to find which logical line the target row maps to
let is_collapsed = app.collapse_thinking && !app.temp_expand_thinking;
let history_text = render_history(&app.history, is_collapsed, app.thinking_hover_rendered, None, 0);
let available_width = size.width.max(1) as usize;
let calc_width = (available_width as f32 * 0.95).floor().max(1.0) as usize;

let mut cumulative_visual_row: usize = 0;
for line in &history_text.lines {
let line_width: usize = line.spans.iter().map(|s| unicode_width::UnicodeWidthStr::width(s.content.as_ref())).sum();
let wrapped_height = if line_width == 0 { 1 } else {
line_width.div_ceil(calc_width)
};

// Check if target_visual_row falls within this logical line's visual rows
if target_visual_row >= cumulative_visual_row && target_visual_row < cumulative_visual_row + wrapped_height {
// Found the line - check if it's a thinking line
return line.spans.iter().any(|span| {
span.content.contains('\u{2502}') || span.content.contains('\u{2503}') || span.content.contains("Thinking...")
});
}
cumulative_visual_row += wrapped_height;
if let Some(&(_, is_thinking)) = app.cached_layout.get(target_visual_row) {
return is_thinking;
}
false
}
Expand All @@ -421,29 +404,8 @@ pub fn compute_message_hover(app: &App, size: ratatui::layout::Rect) -> Option<u
let viewport_row = mouse_row - 1;
let target_visual_row = viewport_row as usize + app.history_scroll as usize;

let is_collapsed = app.collapse_thinking && !app.temp_expand_thinking;
let available_width = size.width.max(1) as usize;
let calc_width = (available_width as f32 * 0.95).floor().max(1.0) as usize;

let mut cumulative_visual_row: usize = 0;

for (msg_idx, m) in app.history.iter().enumerate() {
let msg_slice = std::slice::from_ref(m);
let msg_text = session::render_history(msg_slice, is_collapsed, app.thinking_hover_rendered, None, msg_idx);

let mut msg_height: usize = 0;
for line in &msg_text.lines {
let line_width: usize = line.spans.iter().map(|s| unicode_width::UnicodeWidthStr::width(s.content.as_ref())).sum();
let wrapped_height = if line_width == 0 { 1 } else {
line_width.div_ceil(calc_width)
};
msg_height += wrapped_height;
}

if target_visual_row >= cumulative_visual_row && target_visual_row < cumulative_visual_row + msg_height {
return Some(msg_idx);
}
cumulative_visual_row += msg_height;
if let Some(&(msg_idx, _)) = app.cached_layout.get(target_visual_row) {
return Some(msg_idx);
}

None
Expand Down Expand Up @@ -889,6 +851,31 @@ async fn handle_key_event(
app.history.push(Message::system(format!("Queued: {}", input_text)));
app.input = TextArea::default();
} else {
let provider_id = &app.current_provider_id;
let env_key = format!("{}_API_KEY", provider_id.to_uppercase().replace("-", "_"));
let mut api_key = std::env::var(&env_key).ok();
if api_key.is_none() && provider_id.starts_with("cloudflare") {
api_key = std::env::var("CLOUDFLARE_API_KEY").ok();
}
if api_key.is_none() {
let config = app.orchestrator.config.lock().await;
api_key = config.api_keys.get(provider_id).cloned();
}

let has_valid_key = api_key.map_or(false, |k| !k.trim().is_empty());

if !has_valid_key && provider_id != "opencode-zen" && provider_id != "opencode-go" {
app.history.push(Message::system(format!("No API key found for {}. Please enter it to continue.", provider_id)));
app.show_provider_menu = true;
if let Some(pos) = PROVIDERS.iter().position(|p| p.id == *provider_id) {
app.menu_state.select(Some(pos));
} else {
app.menu_state.select(Some(0));
}
app.input = TextArea::default();
return Ok(KeyEventResult::Continue);
}

app.history.push(Message::user(input_text.clone()));
app.prompt_history.push(input_text.clone());
app.prompt_history.truncate(100);
Expand Down
39 changes: 28 additions & 11 deletions apps/cli/src/ui/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,37 @@ pub fn ui_session(f: &mut Frame, app: &mut App, area: Rect) -> Rect {
if needs_rebuild && (throttle_ok || !app.is_generating) {
app.render_dirty = false;
app.last_cache_update = std::time::Instant::now();
let history = render_history(&app.history, is_collapsed, thinking_hovered, hovered_msg_idx, 0);

// 1. Auto-scroll logic
let mut lines = Vec::new();
let mut total_height: usize = 0;
let mut layout = Vec::new();
let available_width = chunks[0].width.max(1) as usize;
for line in &history.lines {
let line_width: usize = line.spans.iter().map(|s| s.content.width()).sum();
let wrapped_height = if line_width == 0 { 1 } else {
// Use a slightly smaller width for calculation to account for word wrapping
let calc_width = (available_width as f32 * 0.95).floor() as usize;
(line_width + calc_width - 1) / calc_width.max(1)
};
total_height += wrapped_height;
let calc_width = (available_width as f32 * 0.95).floor().max(1.0) as usize;

for (msg_idx, m) in app.history.iter().enumerate() {
let msg_slice = std::slice::from_ref(m);
let msg_text = render_history(msg_slice, is_collapsed, thinking_hovered, hovered_msg_idx, msg_idx);

for line in msg_text.lines {
let line_width: usize = line.spans.iter().map(|s| s.content.width()).sum();
let wrapped_height = if line_width == 0 { 1 } else {
(line_width + calc_width - 1) / calc_width.max(1)
};

let is_thinking = line.spans.iter().any(|span| {
span.content.contains('\u{2502}') || span.content.contains('\u{2503}') || span.content.contains("Thinking...")
});

for _ in 0..wrapped_height {
layout.push((msg_idx, is_thinking));
}

total_height += wrapped_height;
lines.push(line);
}
}

let history = Text::from(lines);
app.cached_layout = layout;
// Safety buffer
total_height += 2;

Expand Down
2 changes: 1 addition & 1 deletion libs/sdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "routecode-sdk"
version = "0.1.8"
version = "0.1.9"
edition = "2021"
authors = ["SpeerX <anasrhnim07@gmail.com>"]
description = "Core logic for RouteCode"
Expand Down
Loading