use crate::config::AppSettings; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatMessage { pub role: String, pub content: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatCompletionRequest { pub model: String, pub messages: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatChoice { pub message: ChatMessage, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatCompletionResponse { pub choices: Vec, } pub fn call_llm(settings: &AppSettings, user_input: String, selected_prompt: Option) -> Result { let mut messages = Vec::new(); if let Some(prompt) = selected_prompt { if !prompt.is_empty() { messages.push(ChatMessage { role: "system".to_string(), content: prompt, }); } } messages.push(ChatMessage { role: "user".to_string(), content: user_input, }); let request = ChatCompletionRequest { model: settings.llm_config.model.clone(), messages, }; let client = reqwest::blocking::Client::builder() .timeout(std::time::Duration::from_secs(120)) .build() .map_err(|e| format!("Failed to create HTTP client: {}", e))?; let mut req_builder = client .post(format!("{}/chat/completions", settings.llm_config.base_url)) .header("Content-Type", "application/json"); if !settings.llm_config.api_key.is_empty() { req_builder = req_builder.header("Authorization", format!("Bearer {}", settings.llm_config.api_key)); } for header in &settings.header_configs { if !header.key.is_empty() { req_builder = req_builder.header(&header.key, &header.value); } } let body = serde_json::to_string(&request) .map_err(|e| format!("Failed to serialize request: {}", e))?; let response = req_builder .body(body) .send() .map_err(|e| format!("Request failed: {}", e))?; if !response.status().is_success() { let status = response.status(); let error_body = response.text().unwrap_or_default(); return Err(format!("API error {}: {}", status, error_body)); } let response_text = response .text() .map_err(|e| format!("Failed to read response: {}", e))?; let completion: ChatCompletionResponse = serde_json::from_str(&response_text) .map_err(|e| format!("Failed to parse response: {}", e))?; if completion.choices.is_empty() { return Err("No response from API".to_string()); } Ok(completion.choices[0].message.content.clone()) }