use axum::{
extract::State,
http::StatusCode,
response::IntoResponse,
Json,
};
use tracing::{debug, error};
use serde_json;
use crate::{
server::AppState,
models::{AnalysisRequest, ChatRequest, ChatResponse},
handlers::{error_response, success_response},
};
pub async fn analyze_content(
State(state): State<AppState>,
Json(request): Json<AnalysisRequest>,
) -> impl IntoResponse {
debug!("Analyzing content for: {}", request.path);
let ai_service = match &state.ai_service {
Some(service) => service,
None => return error_response(StatusCode::SERVICE_UNAVAILABLE, "AI service not available"),
};
let content = match state.file_service.read_file(&request.path).await {
Ok(data) => String::from_utf8_lossy(&data).to_string(),
Err(e) => {
error!("Failed to read file for analysis: {}", e);
return error_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to read file");
}
};
match ai_service.analyze_content(&content, &request.path).await {
Ok(analysis) => success_response(StatusCode::OK, &serde_json::to_string(&analysis).unwrap_or_default()),
Err(e) => {
error!("Content analysis failed: {}", e);
error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string())
}
}
}
pub async fn summarize(
State(state): State<AppState>,
Json(request): Json<AnalysisRequest>,
) -> impl IntoResponse {
debug!("Summarizing content for: {}", request.path);
let ai_service = match &state.ai_service {
Some(service) => service,
None => return error_response(StatusCode::SERVICE_UNAVAILABLE, "AI service not available"),
};
let content = match state.file_service.read_file(&request.path).await {
Ok(data) => String::from_utf8_lossy(&data).to_string(),
Err(e) => {
error!("Failed to read file for summarization: {}", e);
return error_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to read file");
}
};
match ai_service.analyze_content(&content, &request.path).await {
Ok(analysis) => success_response(StatusCode::OK, &analysis.summary),
Err(e) => {
error!("Summarization failed: {}", e);
error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string())
}
}
}
pub async fn suggest_organization(
State(state): State<AppState>,
) -> impl IntoResponse {
debug!("Getting organization suggestions");
let ai_service = match &state.ai_service {
Some(service) => service,
None => return error_response(StatusCode::SERVICE_UNAVAILABLE, "AI service not available"),
};
let directory = match state.file_service.list_directory("/").await {
Ok(dir) => dir,
Err(e) => {
error!("Failed to list files for organization: {}", e);
return error_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to list files");
}
};
let file_infos: Vec<crate::ai::FileInfo> = directory.entries.iter()
.filter_map(|entry| {
if let crate::services::file::DirectoryEntry::File(file) = entry {
Some(crate::ai::FileInfo {
name: file.name.clone(),
size: file.size,
size_human: format_bytes(file.size),
modified: file.modified,
})
} else {
None
}
})
.collect();
match ai_service.suggest_organization(&file_infos).await {
Ok(suggestion) => success_response(StatusCode::OK, &serde_json::to_string(&suggestion).unwrap_or_default()),
Err(e) => {
error!("Organization suggestion failed: {}", e);
error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string())
}
}
}
pub async fn chat(
State(state): State<AppState>,
Json(request): Json<ChatRequest>,
) -> impl IntoResponse {
debug!("Processing chat request: {}", request.message);
let ai_service = match &state.ai_service {
Some(service) => service,
None => return error_response(StatusCode::SERVICE_UNAVAILABLE, "AI service not available"),
};
let mut context = String::new();
let mut context_used = Vec::new();
if let Some(paths) = &request.context_paths {
for path in paths {
if let Ok(content_bytes) = state.file_service.read_file(path).await {
let content = String::from_utf8_lossy(&content_bytes);
let max_length = request.max_context_length.unwrap_or(2000);
let truncated_content = if content.len() > max_length {
format!("{}...", &content[..max_length])
} else {
content.to_string()
};
context.push_str(&format!("File: {}\n{}\n\n", path, truncated_content));
context_used.push(path.clone());
}
}
}
match ai_service.chat(&request.message, &context).await {
Ok(response) => success_response(StatusCode::OK, &serde_json::to_string(&ChatResponse {
response,
context_used,
tokens_used: None,
}).unwrap_or_default()),
Err(e) => {
error!("Chat failed: {}", e);
error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string())
}
}
}
fn format_bytes(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = bytes as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
format!("{:.2} {}", size, UNITS[unit_index])
}