Skip to content

Commit a95fa48

Browse files
author
Terraphim AI
committed
feat: Multi-agent system updates and frontend improvements
## Core Multi-Agent Updates - Enhanced genai_llm_client with improved error handling and async patterns - Updated agent pool management for better concurrency - Refined workflow parallelization in agent_evolution - Updated all multi-agent examples with latest API patterns ## Service Layer Improvements - Enhanced terraphim_service with better scoring functions - Updated name scoring and scored result handling - Improved settings management with terraphim_settings ## Frontend Updates - Updated desktop UI components (BackButton, SessionList, Search) - Enhanced modal components (ArticleModal, ContextEditModal, KGSearchModal) - Improved result item rendering and interaction - Updated Vite configuration for better HMR support - Refreshed frontend assets and dependencies ## Build System - Updated Cargo.lock with latest dependencies
1 parent 9e76b78 commit a95fa48

52 files changed

Lines changed: 2929 additions & 3218 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 486 additions & 51 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/terraphim_agent_evolution/src/workflows/parallelization.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,8 @@ impl WorkflowPattern for Parallelization {
688688

689689
// Parallel execution reduces total time but adds overhead
690690
let estimated_tasks: usize = if input.prompt.len() > 2000 { 4 } else { 3 };
691-
let batches = estimated_tasks.div_ceil(self.parallel_config.max_parallel_tasks);
691+
let batches = (estimated_tasks + self.parallel_config.max_parallel_tasks - 1)
692+
/ self.parallel_config.max_parallel_tasks;
692693

693694
base_time_per_task * batches as u32 + Duration::from_secs(10)
694695
// aggregation overhead

crates/terraphim_multi_agent/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ log = { workspace = true }
2424
# Direct HTTP client approach (like Goose) for LLM communication
2525
reqwest = { version = "0.12", features = ["json", "stream"] }
2626

27-
# Multi-provider generative AI client (using stable version due to let_chains issue in git version)
28-
genai = "0.3.5"
27+
# Multi-provider generative AI client (using terraphim fork with OpenRouter support)
28+
genai = { git = "https://github.com/terraphim/rust-genai.git", branch = "main" }
2929

3030
# Additional dependencies
3131
ahash = { version = "0.8.8", features = ["serde"] }

crates/terraphim_multi_agent/src/genai_llm_client.rs

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -138,28 +138,12 @@ impl GenAiLlmClient {
138138
let end_time = Utc::now();
139139
let duration_ms = (end_time - start_time).num_milliseconds() as u64;
140140

141-
// Extract content from response
142-
let content = match &chat_res.content {
143-
Some(genai::chat::MessageContent::Text(text)) => text.clone(),
144-
Some(genai::chat::MessageContent::Parts(parts)) => {
145-
// For multi-part content, concatenate text parts
146-
parts
147-
.iter()
148-
.filter_map(|part| match part {
149-
genai::chat::ContentPart::Text(text) => Some(text.clone()),
150-
_ => None,
151-
})
152-
.collect::<Vec<_>>()
153-
.join(" ")
154-
}
155-
Some(genai::chat::MessageContent::ToolCalls(_)) => {
156-
"Tool calls not supported".to_string()
157-
}
158-
Some(genai::chat::MessageContent::ToolResponses(_)) => {
159-
"Tool responses not supported".to_string()
160-
}
161-
None => "No response".to_string(),
162-
};
141+
// Extract content from response - MessageContent is now a struct with accessor methods
142+
let content = chat_res
143+
.content
144+
.joined_texts()
145+
.or_else(|| chat_res.content.first_text().map(|s| s.to_string()))
146+
.unwrap_or_else(|| "No text content in response".to_string());
163147

164148
// Extract token usage if available
165149
let (input_tokens, output_tokens) = (

crates/terraphim_service/src/lib.rs

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,43 +1368,61 @@ impl TerraphimService {
13681368
}
13691369
} else {
13701370
// Local document: Try direct persistence lookup first
1371-
let mut placeholder = Document {
1372-
id: document.id.clone(),
1373-
..Default::default()
1374-
};
1375-
if let Ok(persisted_doc) = placeholder.load().await {
1376-
if let Some(better_description) = persisted_doc.description {
1377-
log::debug!("Replaced ripgrep description for '{}' with persistence description", document.title);
1378-
document.description = Some(better_description);
1379-
}
1371+
let should_lookup_persistence = document
1372+
.get_source_haystack()
1373+
.and_then(|source| {
1374+
role.haystacks
1375+
.iter()
1376+
.find(|haystack| haystack.location == *source)
1377+
})
1378+
.map(|haystack| haystack.fetch_content)
1379+
.unwrap_or(true);
1380+
1381+
if !should_lookup_persistence {
1382+
log::trace!(
1383+
"Skipping persistence lookup for '{}' (haystack fetch_content=false)",
1384+
document.title
1385+
);
13801386
} else {
1381-
// Try normalized ID based on document title (filename)
1382-
// For KG files, the title might be "haystack" but persistence ID is "haystackmd"
1383-
let normalized_id = normalize_filename_to_id(&document.title);
1384-
1385-
let mut normalized_placeholder = Document {
1386-
id: normalized_id.clone(),
1387+
let mut placeholder = Document {
1388+
id: document.id.clone(),
13871389
..Default::default()
13881390
};
1389-
if let Ok(persisted_doc) = normalized_placeholder.load().await {
1391+
if let Ok(persisted_doc) = placeholder.load().await {
13901392
if let Some(better_description) = persisted_doc.description {
1391-
log::debug!("Replaced ripgrep description for '{}' with persistence description (normalized from title: {})", document.title, normalized_id);
1393+
log::debug!("Replaced ripgrep description for '{}' with persistence description", document.title);
13921394
document.description = Some(better_description);
13931395
}
13941396
} else {
1395-
// Try with "md" suffix for KG files (title "haystack" -> ID "haystackmd")
1396-
let normalized_id_with_md = format!("{}md", normalized_id);
1397-
let mut md_placeholder = Document {
1398-
id: normalized_id_with_md.clone(),
1397+
// Try normalized ID based on document title (filename)
1398+
// For KG files, the title might be "haystack" but persistence ID is "haystackmd"
1399+
let normalized_id = normalize_filename_to_id(&document.title);
1400+
1401+
let mut normalized_placeholder = Document {
1402+
id: normalized_id.clone(),
13991403
..Default::default()
14001404
};
1401-
if let Ok(persisted_doc) = md_placeholder.load().await {
1405+
if let Ok(persisted_doc) = normalized_placeholder.load().await {
14021406
if let Some(better_description) = persisted_doc.description {
1403-
log::debug!("Replaced ripgrep description for '{}' with persistence description (normalized with md: {})", document.title, normalized_id_with_md);
1407+
log::debug!("Replaced ripgrep description for '{}' with persistence description (normalized from title: {})", document.title, normalized_id);
14041408
document.description = Some(better_description);
14051409
}
14061410
} else {
1407-
log::debug!("No persistence document found for '{}' (tried ID: '{}', normalized: '{}', with md: '{}')", document.title, document.id, normalized_id, normalized_id_with_md);
1411+
// Try with "md" suffix for KG files (title "haystack" -> ID "haystackmd")
1412+
let normalized_id_with_md = format!("{}md", normalized_id);
1413+
let mut md_placeholder = Document {
1414+
id: normalized_id_with_md.clone(),
1415+
..Default::default()
1416+
};
1417+
if let Ok(persisted_doc) = md_placeholder.load().await {
1418+
if let Some(better_description) = persisted_doc.description
1419+
{
1420+
log::debug!("Replaced ripgrep description for '{}' with persistence description (normalized with md: {})", document.title, normalized_id_with_md);
1421+
document.description = Some(better_description);
1422+
}
1423+
} else {
1424+
log::debug!("No persistence document found for '{}' (tried ID: '{}', normalized: '{}', with md: '{}')", document.title, document.id, normalized_id, normalized_id_with_md);
1425+
}
14081426
}
14091427
}
14101428
}

crates/terraphim_service/src/score/names.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ impl fmt::Display for QueryScorer {
8181
/// For example, a 3-gram might contain up to 4 bytes, if it contains 3 Unicode
8282
/// codepoints that each require 4 UTF-8 code units.
8383
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
84+
#[allow(dead_code)]
8485
pub enum NgramType {
8586
/// A windowing ngram.
8687
///

crates/terraphim_service/src/score/scored.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ impl<T> SearchResults<T> {
1717
/// The score provided must be less than or equal to every other score in
1818
/// this collection, otherwise this method will panic.
1919
pub fn push(&mut self, scored: Scored<T>) {
20-
assert!(self.0.last().is_none_or(|smallest| &scored <= smallest));
20+
assert!(self.0.last().map_or(true, |smallest| &scored <= smallest));
2121
self.0.push(scored);
2222
}
2323

crates/terraphim_settings/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ impl DeviceSettings {
221221

222222
/// Save settings to a specified file
223223
fn save_to_file(&self, path: &PathBuf) -> Result<(), Error> {
224-
let serialized_settings =
225-
toml::to_string_pretty(self).map_err(|e| Error::IoError(std::io::Error::other(e)))?;
224+
let serialized_settings = toml::to_string_pretty(self)
225+
.map_err(|e| Error::IoError(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
226226

227227
std::fs::write(path, serialized_settings).map_err(Error::IoError)?;
228228

desktop/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
"@tiptap/core": "^3.7.2",
9191
"@tiptap/extension-mention": "^3.7.2",
9292
"@tiptap/starter-kit": "^3.7.2",
93+
"@tiptap/suggestion": "^2.22.1",
9394
"@tomic/lib": "^0.40.0",
9495
"@tomic/svelte": "^0.40.0",
9596
"bulma": "^0.9.4",

desktop/src/lib/BackButton.svelte

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ export let customClass: string = '';
77
// Hide button on these paths (home by default)
88
export let hideOnPaths: string[] = ['/'];
99
10-
let _isVisible = true;
10+
let isVisible = true;
1111
1212
function updateVisibility() {
1313
try {
1414
const path = window.location?.pathname || '/';
15-
_isVisible = !hideOnPaths.includes(path);
15+
isVisible = !hideOnPaths.includes(path);
1616
} catch (_) {
17-
_isVisible = true;
17+
isVisible = true;
1818
}
1919
}
2020
21-
function _goBack() {
21+
function goBack() {
2222
// Try to go back in browser history, fallback to specified path
2323
if (window.history.length > 1) {
2424
window.history.back();
@@ -27,37 +27,25 @@ function _goBack() {
2727
}
2828
}
2929
30-
// Initialize visibility immediately
31-
updateVisibility();
32-
3330
onMount(() => {
34-
// Update visibility again on mount in case window object is ready
3531
updateVisibility();
36-
37-
const handleVisibilityUpdate = () => {
38-
updateVisibility();
39-
// Force Svelte to re-render by updating a reactive variable
40-
_isVisible = _isVisible; // This triggers reactivity
41-
};
42-
43-
window.addEventListener('popstate', handleVisibilityUpdate);
44-
window.addEventListener('hashchange', handleVisibilityUpdate);
45-
32+
window.addEventListener('popstate', updateVisibility);
33+
window.addEventListener('hashchange', updateVisibility);
4634
return () => {
47-
window.removeEventListener('popstate', handleVisibilityUpdate);
48-
window.removeEventListener('hashchange', handleVisibilityUpdate);
35+
window.removeEventListener('popstate', updateVisibility);
36+
window.removeEventListener('hashchange', updateVisibility);
4937
};
5038
});
5139
</script>
5240

53-
{#if _isVisible}
41+
{#if isVisible}
5442
<button
5543
class="button is-light back-button {customClass}"
56-
on:click={_goBack}
44+
on:click={goBack}
5745
on:keydown={(e) => {
5846
if (e.key === 'Enter' || e.key === ' ') {
5947
e.preventDefault();
60-
_goBack();
48+
goBack();
6149
}
6250
}}
6351
title="Go back"

0 commit comments

Comments
 (0)