stemedb/applications/disputed/app/src-tauri/src/commands/claims.rs
jordan 157dbbb9eb feat: Complete Aphoria Phase 8-9 + UAT suite (90/90 tests passing)
## Phase 8: Enterprise Extractor Improvements 
- 14 security extractors (TLS, JWT, SQL injection, XSS, etc.)
- 10 framework-specific extractors (Spring, Django, Rails, etc.)
- Config file security detection (YAML, TOML)

## Phase 9: Autonomous Extractor Generation 
- Shadow mode executor with TP/FP tracking
- Graduation pipeline with confidence thresholds
- Auto-rollback on regression detection
- Cross-project pattern syncing

## UAT Suite Complete (14 scripts, 90 tests)
- test-core-detection.sh (6 tests)
- test-declarative-extractors.sh (5 tests)
- test-domain-frameworks.sh (5 tests)
- test-domain-unreal.sh (3 tests)
- test-llm-extraction.sh (6 tests)
- test-eval-harness.sh (5 tests)
- test-cross-language.sh (3 tests)
- test-precommit-performance.sh (4 tests)
- test-output-formats.sh (8 tests)
- test-drift-detection.sh (6 tests)
- test-exit-codes.sh (12 tests)
+ 3 more scripts

## Other Changes
- Updated roadmap to mark Phase 8-9 complete
- Added .gitignore entries for build artifacts
- Updated pre-commit: 800 line limit, exclude tests/data/cmd

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 22:50:55 -07:00

118 lines
4.4 KiB
Rust

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<Vec<Claim>, 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<String, String> {
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<Claim>) -> Result<Vec<ClaimCheck>, 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<Claim>) -> Result<usize, String> {
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<usize, String> {
// TODO: Week 3 - Query Episteme
Ok(0)
}