use tauri::State; use tracing::instrument; use crate::llm::{chunk_text, create_client, deduplicate_claims, ChunkConfig, LlmConfig}; use crate::types::{Claim, ClaimCheck, ClaimStatus}; use super::settings::SettingsState; /// Extract claims from text using the configured LLM provider. #[tauri::command] #[instrument(skip(state), fields(text_len = text.len()))] pub async fn extract_claims( state: State<'_, SettingsState>, text: String, ) -> Result, String> { tracing::info!(text_len = text.len(), "Extracting claims from text"); let settings = state.0.lock().map_err(|e| format!("Failed to read settings: {}", e))?.clone(); let api_key = settings .api_key .ok_or_else(|| format!("Please add your {} API key in Settings", settings.llm_provider))?; let config = LlmConfig { api_key, timeout_secs: 60 }; let client = create_client(&settings.llm_provider, config).map_err(|e| e.to_string())?; // Handle batching for long text (>4000 chars) let claims = if text.len() > 4000 { let chunks = chunk_text(&text, &ChunkConfig::default()); tracing::info!(chunk_count = chunks.len(), "Text is long, processing in chunks"); let mut all_claims = Vec::new(); for (i, chunk) in chunks.iter().enumerate() { tracing::info!(chunk_index = i, chunk_len = chunk.len(), "Processing chunk"); match client.extract_claims(chunk).await { Ok(c) => all_claims.extend(c), Err(e) => return Err(map_llm_error_to_user_message(&e)), } } deduplicate_claims(all_claims) } else { client.extract_claims(&text).await.map_err(|e| map_llm_error_to_user_message(&e))? }; tracing::info!(claim_count = claims.len(), "Extraction complete"); Ok(claims) } /// Test the LLM connection with the current settings. #[tauri::command] #[instrument(skip(state))] pub async fn test_llm_connection(state: State<'_, SettingsState>) -> Result { let settings = state.0.lock().map_err(|e| format!("Failed to read settings: {}", e))?.clone(); let api_key = settings.api_key.ok_or_else(|| "API key not configured".to_string())?; let config = LlmConfig { api_key, timeout_secs: 10 }; let client = create_client(&settings.llm_provider, config).map_err(|e| e.to_string())?; client.test_connection().await.map_err(|e| map_llm_error_to_user_message(&e))?; Ok(format!("Connected to {}", settings.llm_provider)) } /// Map LLM errors to user-friendly messages. fn map_llm_error_to_user_message(e: &crate::llm::LlmError) -> String { use crate::llm::LlmError; match e { LlmError::MissingApiKey(provider) => { format!("Please add your {} API key in Settings", provider) } LlmError::AuthenticationFailed => { "Invalid API key. Check your key in Settings.".to_string() } LlmError::RateLimited { retry_after_secs } => { format!("Rate limit reached. Please wait {} seconds and try again.", retry_after_secs) } LlmError::ServiceUnavailable(_) => "AI service temporarily unavailable.".to_string(), LlmError::Timeout(_) => "Request timed out. Try shorter text.".to_string(), LlmError::Network(_) => "Network error. Check your connection.".to_string(), LlmError::InvalidResponse(msg) => format!("Unexpected response from AI: {}", msg), LlmError::JsonParse(_) => { "Failed to parse AI response. Try again or use simpler text.".to_string() } LlmError::UnsupportedProvider(p) => format!("Unsupported provider: {}", p), } } /// Check claims against the knowledge graph. #[tauri::command] pub async fn check_claims(claims: Vec) -> Result, String> { tracing::info!(count = claims.len(), "Checking claims"); // TODO: Week 3 - Check against Episteme Ok(claims .into_iter() .map(|claim| ClaimCheck { claim, status: ClaimStatus::New, related: vec![] }) .collect()) } /// Save claims to the knowledge graph. #[tauri::command] pub async fn save_claims(claims: Vec) -> Result { let count = claims.len(); tracing::info!(count, "Saving claims"); // TODO: Week 3 - Save to Episteme Ok(count) } /// Get the current claim count. #[tauri::command] pub async fn get_claim_count() -> Result { // TODO: Week 3 - Query Episteme Ok(0) }