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
91 changes: 76 additions & 15 deletions crates/terraphim_agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ mod repl;

use client::{ApiClient, SearchResponse};
use service::TuiService;
use terraphim_types::{Document, LogicalOperator, NormalizedTermValue, RoleName, SearchQuery};
use terraphim_types::{
Document, Layer, LogicalOperator, NormalizedTermValue, RoleName, SearchQuery,
extract_first_paragraph,
};
use terraphim_update::{check_for_updates, check_for_updates_startup, update_binary};

#[derive(clap::ValueEnum, Debug, Clone)]
Expand Down Expand Up @@ -475,6 +478,10 @@ struct SearchDocumentOutput {
title: String,
url: String,
rank: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
summary: Option<String>,
}

#[derive(Debug, Serialize)]
Expand All @@ -485,6 +492,38 @@ struct SearchOutput {
results: Vec<SearchDocumentOutput>,
}

/// Extension trait to convert Document to layered output
impl SearchDocumentOutput {
fn from_document(doc: &Document, layer: &Layer) -> Self {
match layer {
Layer::One => Self {
id: doc.id.clone(),
title: doc.title.clone(),
url: doc.url.clone(),
rank: doc.rank,
tags: doc.tags.clone(),
summary: None,
},
Layer::Two => Self {
id: doc.id.clone(),
title: doc.title.clone(),
url: doc.url.clone(),
rank: doc.rank,
tags: doc.tags.clone(),
summary: Some(extract_first_paragraph(&doc.body)),
},
Layer::Three => Self {
id: doc.id.clone(),
title: doc.title.clone(),
url: doc.url.clone(),
rank: doc.rank,
tags: doc.tags.clone(),
summary: None, // For full content, summary not needed
},
}
}
}

fn print_json_output<T: Serialize>(value: &T, mode: CommandOutputMode) -> Result<()> {
let out = match mode {
CommandOutputMode::Human => serde_json::to_string_pretty(value)?,
Expand Down Expand Up @@ -540,6 +579,9 @@ enum Command {
role: Option<String>,
#[arg(long, default_value_t = 10)]
limit: usize,
/// Output layer: 1=minimal (title+tags), 2=summary, 3=full (default)
#[arg(long, default_value_t = 3, value_name = "1|2|3")]
layer: u8,
},
/// Manage roles (list, select)
Roles {
Expand Down Expand Up @@ -1130,13 +1172,18 @@ async fn run_offline_command(
operator,
role,
limit,
layer,
} => {
let role_name = if let Some(role) = role {
RoleName::new(&role)
} else {
service.get_selected_role().await
};

// Parse and validate layer
let layer =
terraphim_types::Layer::from_u8(layer).unwrap_or(terraphim_types::Layer::Three);

let results = if let Some(additional_terms) = terms {
// Multi-term query with logical operators
let mut all_terms = vec![query.clone()];
Expand Down Expand Up @@ -1170,6 +1217,7 @@ async fn run_offline_command(
skip: Some(0),
limit: Some(limit),
role: Some(role_name.clone()),
layer,
};

service.search_with_query(&search_query).await?
Expand All @@ -1182,23 +1230,33 @@ async fn run_offline_command(

if output.is_machine_readable() {
let payload = SearchOutput {
query,
query: query.clone(),
role: role_name.to_string(),
count: results.len(),
results: results
.iter()
.map(|doc| SearchDocumentOutput {
id: doc.id.clone(),
title: doc.title.clone(),
url: doc.url.clone(),
rank: doc.rank,
})
.map(|doc| SearchDocumentOutput::from_document(doc, &layer))
.collect(),
};
print_json_output(&payload, output.mode)?;
} else {
for doc in results.iter() {
println!("- {}\t{}", doc.rank.unwrap_or_default(), doc.title);
match layer {
Layer::One => {
println!("- {}\t{}", doc.rank.unwrap_or_default(), doc.title);
}
Layer::Two => {
let summary = extract_first_paragraph(&doc.body);
println!("- {}\t{}", doc.rank.unwrap_or_default(), doc.title);
println!(" {}", summary);
}
Layer::Three => {
println!("- {}\t{}", doc.rank.unwrap_or_default(), doc.title);
if let Some(ref tags) = doc.tags {
println!(" Tags: {}", tags.join(", "));
}
}
}
}
}
Ok(())
Expand Down Expand Up @@ -2172,6 +2230,7 @@ async fn run_server_command(
operator,
role,
limit,
layer,
} => {
// Get selected role from server if not specified
let role_name = if let Some(role) = role {
Expand All @@ -2181,6 +2240,10 @@ async fn run_server_command(
config_res.config.selected_role
};

// Parse and validate layer
let layer =
terraphim_types::Layer::from_u8(layer).unwrap_or(terraphim_types::Layer::Three);

let q = if let Some(additional_terms) = terms {
// Multi-term query with logical operators
let search_terms: Vec<NormalizedTermValue> = additional_terms
Expand All @@ -2195,6 +2258,7 @@ async fn run_server_command(
skip: Some(0),
limit: Some(limit),
role: Some(role_name),
layer,
}
} else {
// Single term query (backward compatibility)
Expand All @@ -2205,6 +2269,7 @@ async fn run_server_command(
skip: Some(0),
limit: Some(limit),
role: Some(role_name),
layer,
}
};

Expand Down Expand Up @@ -2239,12 +2304,7 @@ async fn run_server_command(
results: res
.results
.iter()
.map(|doc| SearchDocumentOutput {
id: doc.id.clone(),
title: doc.title.clone(),
url: doc.url.clone(),
rank: doc.rank,
})
.map(|doc| SearchDocumentOutput::from_document(doc, &layer))
.collect(),
};
print_json_output(&payload, output.mode)?;
Expand Down Expand Up @@ -2955,6 +3015,7 @@ fn ui_loop(
skip: Some(0),
limit: Some(10),
role: Some(RoleName::new(&role)),
layer: Layer::default(),
};
let resp = api.search(&q).await?;
let lines: Vec<String> = resp
Expand Down
3 changes: 2 additions & 1 deletion crates/terraphim_agent/src/repl/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ impl ReplHandler {
}
} else if let Some(api_client) = &self.api_client {
// Server mode - use current role if no role specified
use terraphim_types::{NormalizedTermValue, RoleName, SearchQuery};
use terraphim_types::{Layer, NormalizedTermValue, RoleName, SearchQuery};

let effective_role = role.unwrap_or_else(|| self.current_role.clone());
let role_name = Some(RoleName::new(&effective_role));
Expand All @@ -420,6 +420,7 @@ impl ReplHandler {
skip: Some(0),
limit,
role: role_name,
layer: Layer::default(),
};

match api_client.search(&search_query).await {
Expand Down
3 changes: 2 additions & 1 deletion crates/terraphim_agent/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use terraphim_persistence::Persistable;
use terraphim_service::TerraphimService;
use terraphim_service::llm::{ChatOptions, build_llm_from_role};
use terraphim_settings::{DeviceSettings, Error as DeviceSettingsError};
use terraphim_types::{Document, NormalizedTermValue, RoleName, SearchQuery, Thesaurus};
use terraphim_types::{Document, Layer, NormalizedTermValue, RoleName, SearchQuery, Thesaurus};
use tokio::sync::Mutex;

#[derive(Clone)]
Expand Down Expand Up @@ -254,6 +254,7 @@ impl TuiService {
skip: Some(0),
limit,
role: Some(role.clone()),
layer: Layer::default(),
};

let mut service = self.service.lock().await;
Expand Down
3 changes: 2 additions & 1 deletion crates/terraphim_agent/tests/cross_mode_consistency_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::time::Duration;
use anyhow::Result;
use serial_test::serial;
use terraphim_agent::client::ApiClient;
use terraphim_types::{NormalizedTermValue, RoleName, SearchQuery};
use terraphim_types::{Layer, NormalizedTermValue, RoleName, SearchQuery};

/// Result structure normalized across all modes
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -244,6 +244,7 @@ async fn search_via_server(
skip: Some(0),
limit: Some(10),
role: Some(RoleName::new(role)),
layer: Layer::default(),
};

let response = client.search(&search_query).await?;
Expand Down
7 changes: 6 additions & 1 deletion crates/terraphim_agent/tests/error_handling_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::time::Duration;

use serial_test::serial;
use terraphim_agent::client::ApiClient;
use terraphim_types::{Document, DocumentType, NormalizedTermValue, RoleName, SearchQuery};
use terraphim_types::{Document, DocumentType, Layer, NormalizedTermValue, RoleName, SearchQuery};
use tokio::time::timeout;

const TEST_SERVER_URL: &str = "http://localhost:8000";
Expand Down Expand Up @@ -92,6 +92,7 @@ async fn test_malformed_server_response() {
skip: Some(0),
limit: Some(100000), // Extremely large limit
role: Some(RoleName::new("Default")),
layer: Layer::default(),
};

let result = client.search(&extreme_query).await;
Expand Down Expand Up @@ -137,6 +138,7 @@ async fn test_invalid_role_handling() {
skip: Some(0),
limit: Some(5),
role: Some(RoleName::new("CompleteLyInvalidRoleName12345")),
layer: Layer::default(),
};

let result = client.search(&invalid_query).await;
Expand Down Expand Up @@ -200,6 +202,7 @@ async fn test_empty_and_special_character_queries() {
skip: Some(0),
limit: Some(5),
role: Some(RoleName::new("Default")),
layer: Layer::default(),
};

let result = client.search(&search_query).await;
Expand Down Expand Up @@ -253,6 +256,7 @@ async fn test_concurrent_request_handling() {
skip: Some(0),
limit: Some(3),
role: Some(RoleName::new("Default")),
layer: Layer::default(),
};
client_clone.search(&query).await
});
Expand Down Expand Up @@ -517,6 +521,7 @@ async fn test_graceful_degradation() {
skip: Some(0),
limit: Some(1),
role: Some(RoleName::new("Default")),
layer: Layer::default(),
};
client
.search(&query)
Expand Down
7 changes: 6 additions & 1 deletion crates/terraphim_agent/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::time::Duration;
use anyhow::Result;
use serial_test::serial;
use terraphim_agent::client::{ApiClient, ChatResponse, ConfigResponse, SearchResponse};
use terraphim_types::{NormalizedTermValue, RoleName, SearchQuery};
use terraphim_types::{Layer, NormalizedTermValue, RoleName, SearchQuery};

const TEST_SERVER_URL: &str = "http://localhost:8000";
#[allow(dead_code)]
Expand Down Expand Up @@ -58,6 +58,7 @@ async fn test_api_client_search() {
skip: Some(0),
limit: Some(5),
role: Some(RoleName::new("Terraphim Engineer")),
layer: Layer::default(),
};

let result = client.search(&query).await;
Expand Down Expand Up @@ -203,6 +204,7 @@ async fn test_search_with_different_roles() {
skip: Some(0),
limit: Some(3),
role: Some(RoleName::new(role_name)),
layer: Layer::default(),
};

let result = client.search(&query).await;
Expand Down Expand Up @@ -244,6 +246,7 @@ async fn test_search_pagination() {
skip: Some(0),
limit: Some(2),
role: Some(RoleName::new("Default")),
layer: Layer::default(),
};

let result1 = client.search(&query1).await;
Expand All @@ -257,6 +260,7 @@ async fn test_search_pagination() {
skip: Some(2),
limit: Some(2),
role: Some(RoleName::new("Default")),
layer: Layer::default(),
};

let result2 = client.search(&query2).await;
Expand Down Expand Up @@ -449,6 +453,7 @@ async fn test_api_error_handling() {
skip: Some(0),
limit: Some(0), // Invalid limit
role: Some(RoleName::new("NonExistentRole")),
layer: Layer::default(),
};

let result = client.search(&query).await;
Expand Down
3 changes: 2 additions & 1 deletion crates/terraphim_agent/tests/kg_ranking_integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use anyhow::Result;

use serial_test::serial;
use terraphim_agent::client::ApiClient;
use terraphim_types::{Document, NormalizedTermValue, RoleName, SearchQuery};
use terraphim_types::{Document, Layer, NormalizedTermValue, RoleName, SearchQuery};

/// Get workspace root directory
fn get_workspace_root() -> Result<PathBuf> {
Expand Down Expand Up @@ -276,6 +276,7 @@ async fn search_via_server(
skip: Some(0),
limit: Some(20),
role: Some(RoleName::new(role)),
layer: Layer::default(),
};

let response = client.search(&search_query).await?;
Expand Down
3 changes: 2 additions & 1 deletion crates/terraphim_cli/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use terraphim_persistence::Persistable;
use terraphim_service::TerraphimService;
use terraphim_settings::{DeviceSettings, Error as DeviceSettingsError};
use terraphim_types::{
CoverageSignal, Document, ExtractedEntity, GroundingMetadata, NormalizationMethod,
CoverageSignal, Document, ExtractedEntity, GroundingMetadata, Layer, NormalizationMethod,
NormalizedTerm, NormalizedTermValue, OntologySchema, RoleName, SchemaSignal, SearchQuery,
Thesaurus,
};
Expand Down Expand Up @@ -265,6 +265,7 @@ impl CliService {
skip: Some(0),
limit,
role: Some(role.clone()),
layer: Layer::default(),
};

let mut service = self.service.lock().await;
Expand Down
2 changes: 2 additions & 0 deletions crates/terraphim_cli/tests/service_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ mod search_query_tests {
skip: Some(0),
limit: Some(10),
role: Some(RoleName::new("Default")),
layer: Default::default(),
};

assert_eq!(query.search_term.to_string(), "rust async");
Expand All @@ -221,6 +222,7 @@ mod search_query_tests {
skip: None,
limit: None,
role: None,
layer: Default::default(),
};

assert!(query.role.is_none());
Expand Down
Loading
Loading