## 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>
118 lines
4.4 KiB
Rust
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)
|
|
}
|