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
203 changes: 202 additions & 1 deletion src-tauri/src/commands/file.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::dto::{ImportFileResult, ImportSessionsResponse};
use crate::{AppState, OpenedFiles};
use retrochat::services::ImportFileRequest;
use retrochat::models::provider::config::{ClaudeCodeConfig, CodexConfig, GeminiCliConfig};
use retrochat::models::Provider;
use retrochat::services::{BatchImportRequest, ImportFileRequest};
use std::path::PathBuf;
use std::sync::Arc;
use tauri::{AppHandle, Emitter, Manager, State};
Expand Down Expand Up @@ -144,3 +146,202 @@ pub async fn import_sessions(
results,
})
}

// Helper struct to track import stats
struct ImportStats {
results: Vec<ImportFileResult>,
total_sessions_imported: i32,
total_messages_imported: i32,
successful_imports: i32,
failed_imports: i32,
total_files: i32,
}

impl ImportStats {
fn new() -> Self {
Self {
results: Vec::new(),
total_sessions_imported: 0,
total_messages_imported: 0,
successful_imports: 0,
failed_imports: 0,
total_files: 0,
}
}
}

// Command to import sessions from preset providers
#[tauri::command]
pub async fn import_from_provider(
state: State<'_, Arc<Mutex<AppState>>>,
provider: String,
overwrite: bool,
) -> Result<ImportSessionsResponse, String> {
log::info!(
"import_from_provider called with provider: {}, overwrite: {}",
provider,
overwrite
);

let state_guard = state.lock().await;
let import_service = &state_guard.import_service;

let mut stats = ImportStats::new();

// Parse provider string to Provider enum
let providers = match provider.to_lowercase().as_str() {
"all" => vec![Provider::All],
"claude" => vec![Provider::ClaudeCode],
"gemini" => vec![Provider::GeminiCLI],
"codex" => vec![Provider::Codex],
_ => return Err(format!("Unknown provider: {}", provider)),
};

// Expand "All" to all specific providers
let expanded_providers = Provider::expand_all(providers);

for prov in expanded_providers {
match prov {
Provider::All => {
// Should not happen due to expansion above
unreachable!("Provider::All should have been expanded")
}
Provider::ClaudeCode => {
log::info!("Importing from Claude Code directories...");
if let Err(e) = import_provider_directories(
&ClaudeCodeConfig::create(),
import_service,
overwrite,
&mut stats,
)
.await
{
log::error!("Error importing Claude directories: {}", e);
}
}
Provider::GeminiCLI => {
log::info!("Importing from Gemini directories...");
if let Err(e) = import_provider_directories(
&GeminiCliConfig::create(),
import_service,
overwrite,
&mut stats,
)
.await
{
log::error!("Error importing Gemini directories: {}", e);
}
}
Provider::Codex => {
log::info!("Importing from Codex directories...");
if let Err(e) = import_provider_directories(
&CodexConfig::create(),
import_service,
overwrite,
&mut stats,
)
.await
{
log::error!("Error importing Codex directories: {}", e);
}
}
Provider::Other(name) => {
log::error!("Unknown provider: {}", name);
return Err(format!("Unknown provider: {}", name));
}
}
}

log::info!(
"Provider import completed - {} successful, {} failed, total: {} sessions, {} messages",
stats.successful_imports,
stats.failed_imports,
stats.total_sessions_imported,
stats.total_messages_imported
);

Ok(ImportSessionsResponse {
total_files: stats.total_files,
successful_imports: stats.successful_imports,
failed_imports: stats.failed_imports,
total_sessions_imported: stats.total_sessions_imported,
total_messages_imported: stats.total_messages_imported,
results: stats.results,
})
}

// Helper function to import from a provider's directories
async fn import_provider_directories(
config: &retrochat::models::provider::config::ProviderConfig,
import_service: &retrochat::services::ImportService,
overwrite: bool,
stats: &mut ImportStats,
) -> Result<(), String> {
let directories = config.get_import_directories();

if directories.is_empty() {
log::info!("No directories found for provider: {}", config.name);
return Ok(());
}

for dir_path in directories {
let path = std::path::Path::new(&dir_path);
if !path.exists() {
log::warn!("Directory not found: {}", path.display());
continue;
}

log::info!("Importing from directory: {}", path.display());

let batch_request = BatchImportRequest {
directory_path: dir_path.clone(),
providers: None,
project_name: None,
overwrite_existing: Some(overwrite),
recursive: Some(true),
};

match import_service.import_batch(batch_request).await {
Ok(response) => {
log::info!(
"Successfully imported from directory '{}': {} sessions, {} messages",
dir_path,
response.total_sessions_imported,
response.total_messages_imported
);

stats.total_files += response.total_files_processed;
stats.successful_imports += response.successful_imports;
stats.failed_imports += response.failed_imports;
stats.total_sessions_imported += response.total_sessions_imported;
stats.total_messages_imported += response.total_messages_imported;

// Add directory-level result
stats.results.push(ImportFileResult {
file_path: dir_path.clone(),
sessions_imported: response.total_sessions_imported,
messages_imported: response.total_messages_imported,
success: response.failed_imports == 0,
error: if response.errors.is_empty() {
None
} else {
Some(response.errors.join("; "))
},
});
}
Err(e) => {
log::error!("Failed to import from directory '{}': {}", dir_path, e);
stats.failed_imports += 1;
stats.results.push(ImportFileResult {
file_path: dir_path.clone(),
sessions_imported: 0,
messages_imported: 0,
success: false,
error: Some(e.to_string()),
});
}
}
}

Ok(())
}
6 changes: 2 additions & 4 deletions src-tauri/src/commands/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,8 @@ pub async fn get_session_detail(

// Create a map of tool_operation_id -> tool_operation for efficient lookup
log::debug!("Building tool operation lookup map");
let tool_op_by_id: std::collections::HashMap<_, _> = tool_operations
.into_iter()
.map(|op| (op.id, op))
.collect();
let tool_op_by_id: std::collections::HashMap<_, _> =
tool_operations.into_iter().map(|op| (op.id, op)).collect();

// Create a map of message_id -> tool_operation
log::debug!("Building message -> tool operation map");
Expand Down
10 changes: 5 additions & 5 deletions src-tauri/src/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,15 +370,15 @@ impl From<retrochat::services::analytics::ToolUsageMetrics> for ToolUsageMetrics

#[derive(Debug, Serialize, Deserialize)]
pub struct HistogramRequest {
pub start_time: String, // RFC3339 timestamp
pub end_time: String, // RFC3339 timestamp
pub interval_minutes: i32, // 5, 15, 60, 360
pub start_time: String, // RFC3339 timestamp
pub end_time: String, // RFC3339 timestamp
pub interval_minutes: i32, // 5, 15, 60, 360
}

#[derive(Debug, Serialize, Deserialize)]
pub struct HistogramBucket {
pub timestamp: String, // Bucket start time (RFC3339)
pub count: i32, // Count in this bucket
pub timestamp: String, // Bucket start time (RFC3339)
pub count: i32, // Count in this bucket
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down
6 changes: 5 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use commands::{
analyze_session, cancel_analysis, create_analysis, get_analysis_result,
get_analysis_status, list_analyses, run_analysis,
},
file::{clear_opened_files, get_opened_files, handle_file_drop, import_sessions},
file::{
clear_opened_files, get_opened_files, handle_file_drop, import_from_provider,
import_sessions,
},
histogram::{get_session_activity_histogram, get_user_message_histogram},
session::{get_providers, get_session_detail, get_sessions, search_messages},
};
Expand Down Expand Up @@ -170,6 +173,7 @@ pub async fn run() -> anyhow::Result<()> {
get_opened_files,
clear_opened_files,
import_sessions,
import_from_provider,
get_session_activity_histogram,
get_user_message_histogram,
])
Expand Down
Loading