use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{mpsc, RwLock};
use tracing::{debug, info};
use crate::core::{CoderLib, CoderLibError, CodeRequest, CodeResponse};
use crate::integration::{
EditHost, ContextGatherer, ContextConfig, GatheredContext,
HostCommand
};
use crate::storage::{Message, MessageRole, MessageContent};
pub struct AIAssistant {
coderlib: Arc<CoderLib>,
edit_host: Arc<EditHost>,
context_gatherer: ContextGatherer,
sessions: Arc<RwLock<HashMap<String, AssistantSession>>>,
ui_state: Arc<RwLock<UIState>>,
command_sender: Option<mpsc::UnboundedSender<HostCommand>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssistantSession {
pub id: String,
pub session_type: SessionType,
pub messages: Vec<Message>,
pub context: Option<GatheredContext>,
pub state: SessionState,
pub created_at: std::time::SystemTime,
pub last_activity: std::time::SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum SessionType {
CodeAssistance,
CodeExplanation,
CodeRefactoring,
BugFix,
CodeReview,
Documentation,
TestGeneration,
Performance,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum SessionState {
Active,
Processing,
WaitingForUser,
Paused,
Completed,
Error(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UIState {
pub panel_visible: bool,
pub active_session: Option<String>,
pub panel_bounds: PanelBounds,
pub preferences: UIPreferences,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PanelBounds {
pub width_percent: f32,
pub height_percent: f32,
pub position: PanelPosition,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum PanelPosition {
Right,
Left,
Bottom,
Floating { x: i32, y: i32 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UIPreferences {
pub theme: String,
pub font_size: u32,
pub show_token_usage: bool,
pub auto_apply_simple: bool,
pub show_context_preview: bool,
pub max_visible_messages: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssistantRequest {
pub session_id: Option<String>,
pub session_type: SessionType,
pub message: String,
pub include_context: bool,
pub context_hints: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssistantResponse {
pub session_id: String,
pub message: String,
pub suggested_actions: Vec<SuggestedAction>,
pub token_usage: Option<TokenUsage>,
pub metadata: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SuggestedAction {
pub action_type: ActionType,
pub description: String,
pub data: serde_json::Value,
pub confidence: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ActionType {
ApplyChanges,
OpenFile,
CreateFile,
RunCommand,
ShowDocumentation,
NavigateToDefinition,
AddImport,
GenerateTests,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenUsage {
pub input_tokens: u32,
pub output_tokens: u32,
pub total_tokens: u32,
pub estimated_cost: Option<f64>,
}
impl Default for UIState {
fn default() -> Self {
Self {
panel_visible: false,
active_session: None,
panel_bounds: PanelBounds {
width_percent: 40.0,
height_percent: 60.0,
position: PanelPosition::Right,
},
preferences: UIPreferences {
theme: "dark".to_string(),
font_size: 14,
show_token_usage: true,
auto_apply_simple: false,
show_context_preview: true,
max_visible_messages: 50,
},
}
}
}
impl AIAssistant {
pub fn new(
coderlib: Arc<CoderLib>,
edit_host: Arc<EditHost>,
context_config: ContextConfig,
) -> Self {
let context_gatherer = ContextGatherer::new(context_config);
Self {
coderlib,
edit_host,
context_gatherer,
sessions: Arc::new(RwLock::new(HashMap::new())),
ui_state: Arc::new(RwLock::new(UIState::default())),
command_sender: None,
}
}
pub fn set_command_sender(&mut self, sender: mpsc::UnboundedSender<HostCommand>) {
self.command_sender = Some(sender);
}
pub async fn show_panel(&self) -> Result<(), CoderLibError> {
let mut ui_state = self.ui_state.write().await;
ui_state.panel_visible = true;
self.send_command(HostCommand::ShowDialog {
title: "AI Assistant".to_string(),
message: "AI Assistant is ready to help!".to_string(),
buttons: vec!["Ask Question".to_string(), "Explain Code".to_string(), "Refactor".to_string(), "Close".to_string()],
}).await?;
info!("AI Assistant panel shown");
Ok(())
}
pub async fn hide_panel(&self) -> Result<(), CoderLibError> {
let mut ui_state = self.ui_state.write().await;
ui_state.panel_visible = false;
info!("AI Assistant panel hidden");
Ok(())
}
pub async fn process_request(&self, request: AssistantRequest) -> Result<AssistantResponse, CoderLibError> {
info!("Processing AI assistant request: {:?}", request.session_type);
let session_id = if let Some(ref id) = request.session_id {
id.clone()
} else {
self.create_new_session(request.session_type.clone()).await?
};
self.update_session_state(&session_id, SessionState::Processing).await?;
let context = if request.include_context {
let state = self.edit_host.get_state();
Some(self.context_gatherer.gather_context(&state, self.edit_host.as_ref()).await?)
} else {
None
};
let code_request = self.build_code_request(&request, &context).await?;
let mut code_response_receiver = self.coderlib.process_request(code_request).await?;
let code_response = if let Ok(response) = code_response_receiver.recv().await {
response
} else {
return Err(CoderLibError::Integration(
crate::core::error::IntegrationError::OperationFailed("No response received".to_string())
));
};
let assistant_response = self.build_assistant_response(&session_id, &code_response, &context).await?;
self.update_session_messages(&session_id, &request.message, &assistant_response.message).await?;
self.update_session_state(&session_id, SessionState::WaitingForUser).await?;
info!("AI assistant request processed successfully");
Ok(assistant_response)
}
async fn create_new_session(&self, session_type: SessionType) -> Result<String, CoderLibError> {
let session_id = uuid::Uuid::new_v4().to_string();
let now = std::time::SystemTime::now();
let session = AssistantSession {
id: session_id.clone(),
session_type,
messages: Vec::new(),
context: None,
state: SessionState::Active,
created_at: now,
last_activity: now,
};
let mut sessions = self.sessions.write().await;
sessions.insert(session_id.clone(), session);
let mut ui_state = self.ui_state.write().await;
ui_state.active_session = Some(session_id.clone());
debug!("Created new AI assistant session: {}", session_id);
Ok(session_id)
}
async fn update_session_state(&self, session_id: &str, new_state: SessionState) -> Result<(), CoderLibError> {
let mut sessions = self.sessions.write().await;
if let Some(session) = sessions.get_mut(session_id) {
session.state = new_state;
session.last_activity = std::time::SystemTime::now();
}
Ok(())
}
async fn update_session_messages(&self, session_id: &str, user_message: &str, assistant_message: &str) -> Result<(), CoderLibError> {
let mut sessions = self.sessions.write().await;
if let Some(session) = sessions.get_mut(session_id) {
session.messages.push(Message {
id: uuid::Uuid::new_v4().to_string(),
session_id: session_id.to_string(),
role: MessageRole::User,
content: MessageContent::Text(user_message.to_string()),
timestamp: chrono::Utc::now(),
metadata: serde_json::Value::Null,
});
session.messages.push(Message {
id: uuid::Uuid::new_v4().to_string(),
session_id: session_id.to_string(),
role: MessageRole::Assistant,
content: MessageContent::Text(assistant_message.to_string()),
timestamp: chrono::Utc::now(),
metadata: serde_json::Value::Null,
});
session.last_activity = std::time::SystemTime::now();
}
Ok(())
}
async fn build_code_request(&self, request: &AssistantRequest, context: &Option<GatheredContext>) -> Result<CodeRequest, CoderLibError> {
let mut prompt = String::new();
match request.session_type {
SessionType::CodeExplanation => {
prompt.push_str("Please explain the following code:\n\n");
}
SessionType::CodeRefactoring => {
prompt.push_str("Please refactor the following code to improve its quality, readability, and performance:\n\n");
}
SessionType::BugFix => {
prompt.push_str("Please help me identify and fix bugs in the following code:\n\n");
}
SessionType::CodeReview => {
prompt.push_str("Please review the following code and provide feedback:\n\n");
}
SessionType::Documentation => {
prompt.push_str("Please generate documentation for the following code:\n\n");
}
SessionType::TestGeneration => {
prompt.push_str("Please generate comprehensive tests for the following code:\n\n");
}
SessionType::Performance => {
prompt.push_str("Please analyze and optimize the performance of the following code:\n\n");
}
SessionType::CodeAssistance => {
prompt.push_str("Please help me with the following code question:\n\n");
}
}
prompt.push_str(&request.message);
prompt.push_str("\n\n");
if let Some(ctx) = context {
prompt.push_str("## Context\n");
prompt.push_str(&self.context_gatherer.format_context_for_ai(ctx));
}
Ok(CodeRequest {
content: prompt,
attachments: Vec::new(),
model: None,
context: crate::core::RequestContext {
current_file: None,
cursor_position: None,
selection: None,
project_root: None,
open_files: Vec::new(),
},
session_id: uuid::Uuid::new_v4().to_string(),
})
}
async fn build_assistant_response(&self, session_id: &str, code_response: &CodeResponse, _context: &Option<GatheredContext>) -> Result<AssistantResponse, CoderLibError> {
let suggested_actions = self.extract_suggested_actions(&code_response.content).await;
Ok(AssistantResponse {
session_id: session_id.to_string(),
message: code_response.content.clone(),
suggested_actions,
token_usage: None, metadata: HashMap::new(),
})
}
async fn extract_suggested_actions(&self, _response_content: &str) -> Vec<SuggestedAction> {
vec![]
}
async fn send_command(&self, command: HostCommand) -> Result<(), CoderLibError> {
if let Some(sender) = &self.command_sender {
sender.send(command)
.map_err(|e| CoderLibError::Integration(
crate::core::error::IntegrationError::OperationFailed(
format!("Failed to send command: {}", e)
)
))?;
}
Ok(())
}
pub async fn get_ui_state(&self) -> UIState {
self.ui_state.read().await.clone()
}
pub async fn update_preferences(&self, preferences: UIPreferences) -> Result<(), CoderLibError> {
let mut ui_state = self.ui_state.write().await;
ui_state.preferences = preferences;
Ok(())
}
pub async fn get_session(&self, session_id: &str) -> Option<AssistantSession> {
let sessions = self.sessions.read().await;
sessions.get(session_id).cloned()
}
pub async fn list_sessions(&self) -> Vec<AssistantSession> {
let sessions = self.sessions.read().await;
sessions.values().cloned().collect()
}
pub async fn close_session(&self, session_id: &str) -> Result<(), CoderLibError> {
let mut sessions = self.sessions.write().await;
sessions.remove(session_id);
let mut ui_state = self.ui_state.write().await;
if ui_state.active_session.as_ref() == Some(&session_id.to_string()) {
ui_state.active_session = None;
}
Ok(())
}
}