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
78 changes: 2 additions & 76 deletions apps/search/src/aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ impl Aggregator {
}

/// Merge results from multiple engines, deduplicate by URL, apply consensus ranking.
fn merge_and_rank(
pub fn merge_and_rank(
engine_results: Vec<(EngineId, Vec<SearchResult>)>,
max_results: usize,
) -> Vec<SearchResult> {
Expand Down Expand Up @@ -176,7 +176,7 @@ fn merge_and_rank(
}

/// Normalize a URL for deduplication.
fn normalize_url(raw: &str) -> String {
pub fn normalize_url(raw: &str) -> String {
let mut s = raw.trim().to_string();

// Strip trailing slash
Expand Down Expand Up @@ -207,77 +207,3 @@ fn normalize_url(raw: &str) -> String {

s
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn normalize_strips_tracking_params() {
assert_eq!(
normalize_url("https://example.com/page?utm_source=google&id=1"),
"https://example.com/page?id=1"
);
}

#[test]
fn normalize_strips_www() {
assert_eq!(
normalize_url("https://www.example.com/page"),
"https://example.com/page"
);
}

#[test]
fn normalize_strips_trailing_slash() {
assert_eq!(
normalize_url("https://example.com/page/"),
"https://example.com/page"
);
}

#[test]
fn merge_deduplicates_and_ranks() {
let engine_results = vec![
(
EngineId::Wikipedia,
vec![
SearchResult {
title: "Rust".into(),
url: "https://en.wikipedia.org/wiki/Rust".into(),
description: "A language".into(),
engines: vec!["wikipedia".into()],
score: 0.0,
},
SearchResult {
title: "Shared".into(),
url: "https://example.com/shared".into(),
description: "Short".into(),
engines: vec!["wikipedia".into()],
score: 0.0,
},
],
),
(
EngineId::DuckDuckGo,
vec![SearchResult {
title: "Shared Result".into(),
url: "https://example.com/shared".into(),
description: "A longer description".into(),
engines: vec!["duckduckgo".into()],
score: 0.0,
}],
),
];

let results = merge_and_rank(engine_results, 10);

// The shared URL should be first (appears in both engines)
assert_eq!(results[0].url, "https://example.com/shared");
assert_eq!(results[0].engines.len(), 2);
// Should keep the longer description
assert_eq!(results[0].description, "A longer description");
// Should have 2 total results
assert_eq!(results.len(), 2);
}
}
72 changes: 72 additions & 0 deletions apps/search/tests/aggregator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use walrus_search::aggregator::{merge_and_rank, normalize_url};
use walrus_search::engine::EngineId;
use walrus_search::result::SearchResult;

#[test]
fn normalize_strips_tracking_params() {
assert_eq!(
normalize_url("https://example.com/page?utm_source=google&id=1"),
"https://example.com/page?id=1"
);
}

#[test]
fn normalize_strips_www() {
assert_eq!(
normalize_url("https://www.example.com/page"),
"https://example.com/page"
);
}

#[test]
fn normalize_strips_trailing_slash() {
assert_eq!(
normalize_url("https://example.com/page/"),
"https://example.com/page"
);
}

#[test]
fn merge_deduplicates_and_ranks() {
let engine_results = vec![
(
EngineId::Wikipedia,
vec![
SearchResult {
title: "Rust".into(),
url: "https://en.wikipedia.org/wiki/Rust".into(),
description: "A language".into(),
engines: vec!["wikipedia".into()],
score: 0.0,
},
SearchResult {
title: "Shared".into(),
url: "https://example.com/shared".into(),
description: "Short".into(),
engines: vec!["wikipedia".into()],
score: 0.0,
},
],
),
(
EngineId::DuckDuckGo,
vec![SearchResult {
title: "Shared Result".into(),
url: "https://example.com/shared".into(),
description: "A longer description".into(),
engines: vec!["duckduckgo".into()],
score: 0.0,
}],
),
];

let results = merge_and_rank(engine_results, 10);

// The shared URL should be first (appears in both engines)
assert_eq!(results[0].url, "https://example.com/shared");
assert_eq!(results[0].engines.len(), 2);
// Should keep the longer description
assert_eq!(results[0].description, "A longer description");
// Should have 2 total results
assert_eq!(results.len(), 2);
}
2 changes: 1 addition & 1 deletion crates/cli/src/cmd/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub struct Auth;

impl Auth {
pub fn run(self) -> Result<()> {
tui::run_app(" Walrus Auth ", AuthState::load, render, handle_key)
tui::run_app(AuthState::load, render, handle_key)
}
}

Expand Down
84 changes: 16 additions & 68 deletions crates/cli/src/cmd/console/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use ratatui::{
widgets::{Block, Borders, Paragraph, Tabs},
};
use sessions::render_sessions;
use tasks::{handle_approve_input, render_tasks};
use tasks::render_tasks;
use wcore::protocol::message::{SessionInfo, TaskInfo};

mod sessions;
Expand All @@ -31,12 +31,9 @@ impl Console {
let mut terminal = tui::setup()?;
let mut state = ConsoleState {
tab: Tab::Sessions,
focus: Focus::List,
sessions,
tasks,
selected: 0,
cursor: 0,
edit_buf: String::new(),
status: String::from("Ready"),
runner,
};
Expand Down Expand Up @@ -65,22 +62,13 @@ pub(crate) enum Tab {

const TAB_TITLES: &[&str] = &["Sessions", "Tasks"];

#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Focus {
List,
Approve,
}

// ── State ────────────────────────────────────────────────────────────

pub(crate) struct ConsoleState {
pub(crate) tab: Tab,
pub(crate) focus: Focus,
pub(crate) sessions: Vec<SessionInfo>,
pub(crate) tasks: Vec<TaskInfo>,
pub(crate) selected: usize,
pub(crate) cursor: usize,
pub(crate) edit_buf: String,
pub(crate) status: String,
pub(crate) runner: Runner,
}
Expand Down Expand Up @@ -121,7 +109,7 @@ async fn handle_key(
return Ok(Some(Ok(())));
}

if key.code == KeyCode::Tab && state.focus == Focus::List {
if key.code == KeyCode::Tab {
state.tab = match state.tab {
Tab::Sessions => Tab::Tasks,
Tab::Tasks => Tab::Sessions,
Expand All @@ -131,13 +119,7 @@ async fn handle_key(
return Ok(None);
}

match state.focus {
Focus::List => handle_list(key, state).await,
Focus::Approve => {
handle_approve_input(key, state).await;
Ok(None)
}
}
handle_list(key, state).await
}

async fn handle_list(
Expand Down Expand Up @@ -184,20 +166,6 @@ async fn handle_list(
}
state.refresh().await;
}
KeyCode::Char('a') => {
// Approve a blocked task.
if state.tab == Tab::Tasks {
if let Some(t) = state.tasks.get(state.selected)
&& t.blocked_on.is_some()
{
state.focus = Focus::Approve;
state.edit_buf.clear();
state.cursor = 0;
} else {
state.status = String::from("Task is not blocked");
}
}
}
_ => {}
}
Ok(None)
Expand Down Expand Up @@ -246,37 +214,17 @@ fn render(frame: &mut Frame, state: &ConsoleState) {
}

fn render_status(frame: &mut Frame, state: &ConsoleState, area: ratatui::layout::Rect) {
let help = match state.focus {
Focus::Approve => Line::from(vec![
Span::styled(" Enter ", Style::default().fg(Color::Cyan)),
Span::raw("Send "),
Span::styled("Esc ", Style::default().fg(Color::Cyan)),
Span::raw("Cancel "),
Span::styled("| ", Style::default().fg(Color::DarkGray)),
Span::styled(&state.status, Style::default().fg(Color::Green)),
]),
Focus::List => {
let mut spans = vec![
Span::styled(" Tab ", Style::default().fg(Color::Cyan)),
Span::raw("Switch "),
Span::styled("r ", Style::default().fg(Color::Cyan)),
Span::raw("Refresh "),
Span::styled("d ", Style::default().fg(Color::Cyan)),
Span::raw("Kill "),
];
if state.tab == Tab::Tasks {
spans.push(Span::styled("a ", Style::default().fg(Color::Cyan)));
spans.push(Span::raw("Approve "));
}
spans.push(Span::styled("q ", Style::default().fg(Color::Cyan)));
spans.push(Span::raw("Quit "));
spans.push(Span::styled("| ", Style::default().fg(Color::DarkGray)));
spans.push(Span::styled(
&state.status,
Style::default().fg(Color::Green),
));
Line::from(spans)
}
};
frame.render_widget(Paragraph::new(help), area);
let spans = vec![
Span::styled(" Tab ", Style::default().fg(Color::Cyan)),
Span::raw("Switch "),
Span::styled("r ", Style::default().fg(Color::Cyan)),
Span::raw("Refresh "),
Span::styled("d ", Style::default().fg(Color::Cyan)),
Span::raw("Kill "),
Span::styled("q ", Style::default().fg(Color::Cyan)),
Span::raw("Quit "),
Span::styled("| ", Style::default().fg(Color::DarkGray)),
Span::styled(&state.status, Style::default().fg(Color::Green)),
];
frame.render_widget(Paragraph::new(Line::from(spans)), area);
}
Loading
Loading