feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
use tauri::State;
|
|
|
|
|
|
|
|
|
|
use crate::ai::provider::create_provider;
|
|
|
|
|
use crate::ai::{AnalysisResult, ChatResponse, Message, ProviderInfo};
|
|
|
|
|
use crate::db::models::{AiConversation, AiMessage, AuditEntry};
|
|
|
|
|
use crate::state::{AppState, ProviderConfig};
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn analyze_logs(
|
|
|
|
|
issue_id: String,
|
|
|
|
|
log_file_ids: Vec<String>,
|
|
|
|
|
provider_config: ProviderConfig,
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
) -> Result<AnalysisResult, String> {
|
|
|
|
|
// Load log file contents
|
|
|
|
|
let mut log_contents = String::new();
|
|
|
|
|
{
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
for file_id in &log_file_ids {
|
|
|
|
|
let mut stmt = db
|
|
|
|
|
.prepare("SELECT file_name, file_path FROM log_files WHERE id = ?1")
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
if let Ok((name, path)) = stmt.query_row([file_id], |row| {
|
|
|
|
|
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
|
|
|
|
}) {
|
2026-03-15 18:28:59 +00:00
|
|
|
log_contents.push_str(&format!("--- {name} ---\n"));
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
if let Ok(content) = std::fs::read_to_string(&path) {
|
|
|
|
|
log_contents.push_str(&content);
|
|
|
|
|
} else {
|
|
|
|
|
log_contents.push_str("[Could not read file]\n");
|
|
|
|
|
}
|
|
|
|
|
log_contents.push('\n');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let provider = create_provider(&provider_config);
|
|
|
|
|
|
|
|
|
|
let messages = vec![
|
|
|
|
|
Message {
|
|
|
|
|
role: "system".into(),
|
|
|
|
|
content: "You are an expert IT engineer. Analyze the following log content and \
|
|
|
|
|
identify key issues, errors, and anomalies. Structure your response as: \
|
|
|
|
|
SUMMARY: (one paragraph), KEY_FINDINGS: (bullet list), \
|
|
|
|
|
FIRST_WHY: (initial why question for 5-whys analysis), \
|
|
|
|
|
SEVERITY: (critical/high/medium/low)"
|
|
|
|
|
.into(),
|
|
|
|
|
},
|
|
|
|
|
Message {
|
|
|
|
|
role: "user".into(),
|
2026-03-15 18:28:59 +00:00
|
|
|
content: format!("Analyze logs for issue {issue_id}:\n\n{log_contents}"),
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let response = provider
|
|
|
|
|
.chat(messages, &provider_config)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
let content = &response.content;
|
2026-03-15 17:43:46 +00:00
|
|
|
let summary = extract_section(content, "SUMMARY:").unwrap_or_else(|| {
|
|
|
|
|
content
|
|
|
|
|
.lines()
|
|
|
|
|
.next()
|
|
|
|
|
.unwrap_or("Analysis complete")
|
|
|
|
|
.to_string()
|
|
|
|
|
});
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
let key_findings = extract_list(content, "KEY_FINDINGS:");
|
|
|
|
|
let suggested_why1 = extract_section(content, "FIRST_WHY:")
|
|
|
|
|
.unwrap_or_else(|| "Why did this issue occur?".to_string());
|
|
|
|
|
let severity_assessment =
|
|
|
|
|
extract_section(content, "SEVERITY:").unwrap_or_else(|| "medium".to_string());
|
|
|
|
|
|
|
|
|
|
// Write audit entry
|
|
|
|
|
{
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
let entry = AuditEntry::new(
|
|
|
|
|
"ai_analyze_logs".to_string(),
|
|
|
|
|
"issue".to_string(),
|
|
|
|
|
issue_id.clone(),
|
2026-03-15 17:43:46 +00:00
|
|
|
serde_json::json!({ "log_file_ids": log_file_ids, "provider": provider_config.name })
|
|
|
|
|
.to_string(),
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
);
|
|
|
|
|
db.execute(
|
|
|
|
|
"INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \
|
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
|
|
|
|
rusqlite::params![
|
|
|
|
|
entry.id, entry.timestamp, entry.action,
|
|
|
|
|
entry.entity_type, entry.entity_id, entry.user_id, entry.details
|
|
|
|
|
],
|
|
|
|
|
).map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(AnalysisResult {
|
|
|
|
|
summary,
|
|
|
|
|
key_findings,
|
|
|
|
|
suggested_why1,
|
|
|
|
|
severity_assessment,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn extract_section(text: &str, header: &str) -> Option<String> {
|
|
|
|
|
let start = text.find(header)?;
|
|
|
|
|
let after = &text[start + header.len()..];
|
|
|
|
|
let end = after.find('\n').unwrap_or(after.len());
|
|
|
|
|
let section = after[..end].trim().to_string();
|
|
|
|
|
if section.is_empty() {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(section)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn extract_list(text: &str, header: &str) -> Vec<String> {
|
|
|
|
|
let start = match text.find(header) {
|
|
|
|
|
Some(s) => s + header.len(),
|
|
|
|
|
None => return vec![],
|
|
|
|
|
};
|
|
|
|
|
let section = &text[start..];
|
|
|
|
|
section
|
|
|
|
|
.lines()
|
|
|
|
|
.skip(1)
|
|
|
|
|
.take_while(|l| {
|
|
|
|
|
!l.chars()
|
|
|
|
|
.next()
|
|
|
|
|
.map(|c| c.is_uppercase() && l.contains(':'))
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
})
|
|
|
|
|
.filter(|l| l.trim_start().starts_with('-') || l.trim_start().starts_with('*'))
|
|
|
|
|
.map(|l| {
|
|
|
|
|
l.trim_start_matches(|c: char| c == '-' || c == '*' || c.is_whitespace())
|
|
|
|
|
.to_string()
|
|
|
|
|
})
|
|
|
|
|
.filter(|s| !s.is_empty())
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn chat_message(
|
|
|
|
|
issue_id: String,
|
|
|
|
|
message: String,
|
|
|
|
|
provider_config: ProviderConfig,
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
) -> Result<ChatResponse, String> {
|
|
|
|
|
// Find or create a conversation for this issue + provider
|
|
|
|
|
let conversation_id = {
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
let existing: Option<String> = db
|
|
|
|
|
.prepare("SELECT id FROM ai_conversations WHERE issue_id = ?1 AND provider = ?2 AND model = ?3 ORDER BY created_at DESC LIMIT 1")
|
|
|
|
|
.and_then(|mut stmt| {
|
|
|
|
|
stmt.query_row(
|
|
|
|
|
rusqlite::params![issue_id, provider_config.name, provider_config.model],
|
|
|
|
|
|row| row.get(0),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.ok();
|
|
|
|
|
|
|
|
|
|
match existing {
|
|
|
|
|
Some(id) => id,
|
|
|
|
|
None => {
|
|
|
|
|
let conv = AiConversation::new(
|
|
|
|
|
issue_id.clone(),
|
|
|
|
|
provider_config.name.clone(),
|
|
|
|
|
provider_config.model.clone(),
|
|
|
|
|
);
|
|
|
|
|
db.execute(
|
|
|
|
|
"INSERT INTO ai_conversations (id, issue_id, provider, model, created_at, title) \
|
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
|
|
|
|
rusqlite::params![
|
|
|
|
|
conv.id, conv.issue_id, conv.provider, conv.model, conv.created_at, conv.title
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
conv.id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Load conversation history (use and_then to keep stmt lifetime within closure)
|
|
|
|
|
let history: Vec<Message> = {
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
let raw: Vec<(String, String)> = db
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT role, content FROM ai_messages WHERE conversation_id = ?1 ORDER BY created_at ASC",
|
|
|
|
|
)
|
|
|
|
|
.and_then(|mut stmt| {
|
|
|
|
|
stmt.query_map([&conversation_id], |row| {
|
|
|
|
|
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
|
|
|
|
})
|
|
|
|
|
.map(|rows| rows.filter_map(|r| r.ok()).collect::<Vec<_>>())
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
drop(db);
|
|
|
|
|
raw.into_iter()
|
|
|
|
|
.map(|(role, content)| Message { role, content })
|
|
|
|
|
.collect()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let provider = create_provider(&provider_config);
|
|
|
|
|
|
|
|
|
|
let mut messages = history;
|
|
|
|
|
messages.push(Message {
|
|
|
|
|
role: "user".into(),
|
|
|
|
|
content: message.clone(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let response = provider
|
|
|
|
|
.chat(messages, &provider_config)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
// Save both user message and response to DB
|
|
|
|
|
{
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
let user_msg = AiMessage::new(conversation_id.clone(), "user".to_string(), message);
|
|
|
|
|
let asst_msg = AiMessage::new(
|
|
|
|
|
conversation_id,
|
|
|
|
|
"assistant".to_string(),
|
|
|
|
|
response.content.clone(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
db.execute(
|
|
|
|
|
"INSERT INTO ai_messages (id, conversation_id, role, content, token_count, created_at) \
|
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
|
|
|
|
rusqlite::params![
|
|
|
|
|
user_msg.id, user_msg.conversation_id, user_msg.role,
|
|
|
|
|
user_msg.content, user_msg.token_count, user_msg.created_at
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
db.execute(
|
|
|
|
|
"INSERT INTO ai_messages (id, conversation_id, role, content, token_count, created_at) \
|
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
|
|
|
|
rusqlite::params![
|
|
|
|
|
asst_msg.id, asst_msg.conversation_id, asst_msg.role,
|
|
|
|
|
asst_msg.content, asst_msg.token_count, asst_msg.created_at
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
2026-04-03 13:37:47 +00:00
|
|
|
// Audit - capture full transmission details
|
|
|
|
|
let audit_details = serde_json::json!({
|
|
|
|
|
"provider": provider_config.name,
|
|
|
|
|
"model": provider_config.model,
|
|
|
|
|
"api_url": provider_config.api_url,
|
|
|
|
|
"user_message": user_msg.content,
|
|
|
|
|
"response_preview": if response.content.len() > 200 {
|
2026-04-04 20:05:13 +00:00
|
|
|
format!("{preview}...", preview = &response.content[..200])
|
2026-04-03 13:37:47 +00:00
|
|
|
} else {
|
|
|
|
|
response.content.clone()
|
|
|
|
|
},
|
|
|
|
|
"token_count": user_msg.token_count,
|
|
|
|
|
});
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
let entry = AuditEntry::new(
|
|
|
|
|
"ai_chat".to_string(),
|
|
|
|
|
"issue".to_string(),
|
|
|
|
|
issue_id,
|
2026-04-03 13:37:47 +00:00
|
|
|
audit_details.to_string(),
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
);
|
|
|
|
|
let _ = db.execute(
|
|
|
|
|
"INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \
|
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
|
|
|
|
rusqlite::params![
|
|
|
|
|
entry.id, entry.timestamp, entry.action,
|
|
|
|
|
entry.entity_type, entry.entity_id, entry.user_id, entry.details
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(response)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 12:46:36 +00:00
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn test_provider_connection(
|
|
|
|
|
provider_config: ProviderConfig,
|
|
|
|
|
) -> Result<ChatResponse, String> {
|
|
|
|
|
let provider = create_provider(&provider_config);
|
|
|
|
|
let messages = vec![Message {
|
|
|
|
|
role: "user".into(),
|
|
|
|
|
content: "Reply with exactly: TFTSR connection test successful.".into(),
|
|
|
|
|
}];
|
|
|
|
|
provider
|
|
|
|
|
.chat(messages, &provider_config)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| e.to_string())
|
|
|
|
|
}
|
|
|
|
|
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn list_providers() -> Result<Vec<ProviderInfo>, String> {
|
|
|
|
|
Ok(vec![
|
|
|
|
|
ProviderInfo {
|
|
|
|
|
name: "openai".to_string(),
|
|
|
|
|
supports_streaming: true,
|
|
|
|
|
models: vec!["gpt-4o".to_string(), "gpt-4o-mini".to_string()],
|
|
|
|
|
},
|
|
|
|
|
ProviderInfo {
|
|
|
|
|
name: "anthropic".to_string(),
|
|
|
|
|
supports_streaming: true,
|
|
|
|
|
models: vec![
|
|
|
|
|
"claude-3-5-sonnet-20241022".to_string(),
|
|
|
|
|
"claude-3-haiku-20240307".to_string(),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
ProviderInfo {
|
|
|
|
|
name: "gemini".to_string(),
|
|
|
|
|
supports_streaming: false,
|
2026-03-15 17:43:46 +00:00
|
|
|
models: vec!["gemini-1.5-pro".to_string(), "gemini-1.5-flash".to_string()],
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
},
|
|
|
|
|
ProviderInfo {
|
|
|
|
|
name: "mistral".to_string(),
|
|
|
|
|
supports_streaming: true,
|
|
|
|
|
models: vec![
|
|
|
|
|
"mistral-large-latest".to_string(),
|
|
|
|
|
"mistral-small-latest".to_string(),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
ProviderInfo {
|
|
|
|
|
name: "ollama".to_string(),
|
|
|
|
|
supports_streaming: false,
|
|
|
|
|
models: vec!["llama3.2:3b".to_string(), "llama3.1:8b".to_string()],
|
|
|
|
|
},
|
|
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_extract_section_basic() {
|
|
|
|
|
let text = "SUMMARY: The server crashed due to OOM.\nKEY_FINDINGS:\n- item";
|
|
|
|
|
assert_eq!(
|
|
|
|
|
extract_section(text, "SUMMARY:"),
|
|
|
|
|
Some("The server crashed due to OOM.".to_string())
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_extract_section_missing() {
|
|
|
|
|
let text = "No matching header here";
|
|
|
|
|
assert_eq!(extract_section(text, "SUMMARY:"), None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_extract_section_empty_value() {
|
|
|
|
|
let text = "SUMMARY:\nKEY_FINDINGS:";
|
|
|
|
|
assert_eq!(extract_section(text, "SUMMARY:"), None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_extract_section_last_line() {
|
|
|
|
|
let text = "SEVERITY: critical";
|
|
|
|
|
assert_eq!(
|
|
|
|
|
extract_section(text, "SEVERITY:"),
|
|
|
|
|
Some("critical".to_string())
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_extract_list_basic() {
|
|
|
|
|
let text = "KEY_FINDINGS:\n- First issue\n- Second issue\nSEVERITY: high";
|
|
|
|
|
let list = extract_list(text, "KEY_FINDINGS:");
|
|
|
|
|
assert_eq!(list, vec!["First issue", "Second issue"]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_extract_list_with_asterisks() {
|
|
|
|
|
let text = "KEY_FINDINGS:\n* Item one\n* Item two\nFIRST_WHY: Why?";
|
|
|
|
|
let list = extract_list(text, "KEY_FINDINGS:");
|
|
|
|
|
assert_eq!(list, vec!["Item one", "Item two"]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_extract_list_missing_header() {
|
|
|
|
|
let text = "No findings here";
|
|
|
|
|
let list = extract_list(text, "KEY_FINDINGS:");
|
|
|
|
|
assert!(list.is_empty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_extract_list_empty_items_filtered() {
|
|
|
|
|
let text = "KEY_FINDINGS:\n- \n- Actual item\n- \nNEXT:";
|
|
|
|
|
let list = extract_list(text, "KEY_FINDINGS:");
|
|
|
|
|
assert_eq!(list, vec!["Actual item"]);
|
|
|
|
|
}
|
|
|
|
|
}
|