2026-05-08 22:37:09 +08:00
|
|
|
use crate::config::{AppSettings, LLMConfig};
|
2026-04-04 17:32:49 +08:00
|
|
|
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<ChatMessage>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct ChatChoice {
|
|
|
|
|
pub message: ChatMessage,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct ChatCompletionResponse {
|
|
|
|
|
pub choices: Vec<ChatChoice>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 22:37:09 +08:00
|
|
|
pub fn call_single_llm(config: &LLMConfig, user_input: String, selected_prompt: Option<String>, header_configs: &[crate::config::HeaderConfig]) -> Result<String, String> {
|
2026-04-05 20:27:05 +08:00
|
|
|
let full_content = if let Some(prompt) = selected_prompt {
|
2026-04-04 17:32:49 +08:00
|
|
|
if !prompt.is_empty() {
|
2026-04-05 20:27:05 +08:00
|
|
|
format!("{}{}", prompt, user_input)
|
|
|
|
|
} else {
|
|
|
|
|
user_input
|
2026-04-04 17:32:49 +08:00
|
|
|
}
|
2026-04-05 20:27:05 +08:00
|
|
|
} else {
|
|
|
|
|
user_input
|
|
|
|
|
};
|
2026-04-04 17:32:49 +08:00
|
|
|
|
2026-04-05 20:27:05 +08:00
|
|
|
let messages = vec![ChatMessage {
|
2026-04-04 17:32:49 +08:00
|
|
|
role: "user".to_string(),
|
2026-04-05 20:27:05 +08:00
|
|
|
content: full_content,
|
|
|
|
|
}];
|
2026-04-04 17:32:49 +08:00
|
|
|
|
|
|
|
|
let request = ChatCompletionRequest {
|
2026-05-08 22:37:09 +08:00
|
|
|
model: config.model.clone(),
|
2026-04-04 17:32:49 +08:00
|
|
|
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
|
2026-05-08 22:37:09 +08:00
|
|
|
.post(format!("{}/chat/completions", config.base_url))
|
2026-04-04 17:32:49 +08:00
|
|
|
.header("Content-Type", "application/json");
|
|
|
|
|
|
2026-05-08 22:37:09 +08:00
|
|
|
if !config.api_key.is_empty() {
|
|
|
|
|
req_builder = req_builder.header("Authorization", format!("Bearer {}", config.api_key));
|
2026-04-04 17:32:49 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 22:37:09 +08:00
|
|
|
for header in header_configs {
|
2026-04-04 17:32:49 +08:00
|
|
|
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())
|
|
|
|
|
}
|
2026-05-08 22:37:09 +08:00
|
|
|
|
|
|
|
|
pub fn call_llm(settings: &AppSettings, user_input: String, selected_prompt: Option<String>) -> Result<String, String> {
|
|
|
|
|
settings.llm_configs
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|c| c.enabled && !c.api_key.is_empty() && !c.base_url.is_empty())
|
|
|
|
|
.ok_or_else(|| "没有可用的模型配置".to_string())
|
|
|
|
|
.and_then(|c| call_single_llm(c, user_input, selected_prompt, &settings.header_configs))
|
|
|
|
|
}
|