Skip to main content

git_commit_helper_cli/
ai_service.rs

1use async_trait::async_trait;
2use dialoguer::{Confirm, Select};
3use log::{debug, info, warn};
4use crate::config::{AIService, Config, AIServiceConfig};
5use crate::terminal_format::print_progress;
6
7#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
8pub struct Message {
9    pub role: String,
10    pub content: String,
11}
12use copilot_client::CopilotClient;
13
14#[async_trait]
15pub trait AiService: Send + Sync {
16    async fn translate(&self, text: &str, direction: &crate::config::TranslateDirection) -> anyhow::Result<String> {
17        // 使用翻译的 prompt
18        let system_prompt = get_translation_prompt(text, direction);
19        Ok(self.chat(&system_prompt, text).await?)
20    }
21
22    async fn chat(&self, system_prompt: &str, user_content: &str) -> anyhow::Result<String>;
23}
24
25pub use AiService as Translator; // 为了兼容性,保留原有的 Translator 类型
26
27pub struct DeepSeekTranslator {
28    api_key: String,
29    endpoint: String,
30    model: String,
31    timeout_seconds: u64,
32    max_tokens: u64,
33}
34
35pub struct OpenAITranslator {
36    api_key: String,
37    endpoint: String,
38    model: String,
39    timeout_seconds: u64,
40    max_tokens: u64,
41}
42
43pub struct ClaudeTranslator {
44    api_key: String,
45    endpoint: String,
46    model: String,
47    timeout_seconds: u64,
48    max_tokens: u64,
49}
50
51pub struct CopilotTranslator {
52    client: CopilotClient,
53    model: String,
54}
55
56pub struct GeminiTranslator {
57    api_key: String,
58    endpoint: String,
59    model: String,
60    timeout_seconds: u64,
61    max_tokens: u64,
62}
63
64pub struct GrokTranslator {
65    api_key: String,
66    endpoint: String,
67    model: String,
68    timeout_seconds: u64,
69    max_tokens: u64,
70}
71
72pub struct QwenTranslator {
73    api_key: String,
74    endpoint: String,
75    model: String,
76    timeout_seconds: u64,
77    max_tokens: u64,
78}
79
80impl DeepSeekTranslator {
81    pub fn new(config: &AIServiceConfig) -> Self {
82        Self {
83            api_key: config.api_key.clone(),
84            endpoint: config.api_endpoint.clone()
85                .unwrap_or_else(|| "https://api.deepseek.com/v1".into()),
86            model: config.model.clone()
87                .unwrap_or_else(|| "deepseek-chat".into()),
88            timeout_seconds: crate::config::Config::load()
89                .map(|c| c.timeout_seconds)
90                .unwrap_or(20),
91            max_tokens: crate::config::Config::load()
92                .map(|c| c.max_tokens)
93                .unwrap_or(2048),
94        }
95    }
96}
97
98impl OpenAITranslator {
99    pub fn new(config: &AIServiceConfig) -> Self {
100        Self {
101            api_key: config.api_key.clone(),
102            endpoint: config.api_endpoint.clone()
103                .unwrap_or_else(|| "https://api.openai.com/v1".into()),
104            model: config.model.clone()
105                .unwrap_or_else(|| "gpt-3.5-turbo".into()),
106            timeout_seconds: crate::config::Config::load()
107                .map(|c| c.timeout_seconds)
108                .unwrap_or(20),
109            max_tokens: crate::config::Config::load()
110                .map(|c| c.max_tokens)
111                .unwrap_or(2048),
112        }
113    }
114}
115
116impl ClaudeTranslator {
117    pub fn new(config: &AIServiceConfig) -> Self {
118        Self {
119            api_key: config.api_key.clone(),
120            endpoint: config.api_endpoint.clone()
121                .unwrap_or_else(|| "https://api.anthropic.com/v1".into()),
122            model: config.model.clone()
123                .unwrap_or_else(|| "claude-3-sonnet-20240229".into()),
124            timeout_seconds: crate::config::Config::load()
125                .map(|c| c.timeout_seconds)
126                .unwrap_or(20),
127            max_tokens: crate::config::Config::load()
128                .map(|c| c.max_tokens)
129                .unwrap_or(2048),
130        }
131    }
132}
133
134impl CopilotTranslator {
135    pub fn new(client: CopilotClient, model: String) -> Self {
136        Self { client, model }
137    }
138}
139
140impl GeminiTranslator {
141    pub fn new(config: &AIServiceConfig) -> Self {
142        Self {
143            api_key: config.api_key.clone(),
144            endpoint: config.api_endpoint.clone()
145                .unwrap_or_else(|| "https://generativelanguage.googleapis.com/v1beta".into()),
146            model: config.model.clone()
147                .unwrap_or_else(|| "gemini-2.0-flash".into()),
148            timeout_seconds: crate::config::Config::load()
149                .map(|c| c.timeout_seconds)
150                .unwrap_or(20),
151            max_tokens: crate::config::Config::load()
152                .map(|c| c.max_tokens)
153                .unwrap_or(2048),
154        }
155    }
156}
157
158impl GrokTranslator {
159    pub fn new(config: &AIServiceConfig) -> Self {
160        Self {
161            api_key: config.api_key.clone(),
162            endpoint: config.api_endpoint.clone()
163                .unwrap_or_else(|| "https://api.x.ai/v1".into()),
164            model: config.model.clone()
165                .unwrap_or_else(|| "grok-3-latest".into()),
166            timeout_seconds: crate::config::Config::load()
167                .map(|c| c.timeout_seconds)
168                .unwrap_or(20),
169            max_tokens: crate::config::Config::load()
170                .map(|c| c.max_tokens)
171                .unwrap_or(2048),
172        }
173    }
174}
175
176impl QwenTranslator {
177    pub fn new(config: &AIServiceConfig) -> Self {
178        Self {
179            api_key: config.api_key.clone(),
180            endpoint: config.api_endpoint.clone()
181                .unwrap_or_else(|| "https://dashscope.aliyuncs.com/compatible-mode/v1".into()),
182            model: config.model.clone()
183                .unwrap_or_else(|| "qwen-plus".into()),
184            timeout_seconds: crate::config::Config::load()
185                .map(|c| c.timeout_seconds)
186                .unwrap_or(20),
187            max_tokens: crate::config::Config::load()
188                .map(|c| c.max_tokens)
189                .unwrap_or(2048),
190        }
191    }
192}
193
194// 添加一个新的工具函数
195fn wrap_chinese_text(text: &str, max_width: usize) -> String {
196    let mut result = String::new();
197    let mut current_line = String::new();
198    let mut current_width = 0;
199
200    for c in text.chars() {
201        let char_width = if c.is_ascii() { 1 } else { 2 };
202
203        if current_width + char_width > max_width {
204            result.push_str(&current_line);
205            result.push('\n');
206            current_line.clear();
207            current_width = 0;
208        }
209
210        current_line.push(c);
211        current_width += char_width;
212    }
213
214    if !current_line.is_empty() {
215        result.push_str(&current_line);
216    }
217
218    result
219}
220
221fn get_translation_prompt(text: &str, direction: &crate::config::TranslateDirection) -> String {
222    use crate::config::TranslateDirection;
223    
224    let prompt = match direction {
225        TranslateDirection::ChineseToEnglish => format!(
226            r#"You are a professional translator. Please translate the following Chinese text to English.
227    Important rules:
228    1. Keep all English content, numbers, and English punctuation unchanged
229    2. Do not translate any content inside English double quotes
230    3. Preserve the case of all English words
231    4. Only return the English translation, DO NOT include the original Chinese text
232    5. Keep simple and concise, no need to rewrite or expand the content
233
234    Example response format:
235    feat: add support for external plugins
236
237    1. Implement plugin loading mechanism
238    2. Add plugin configuration interface
239    3. Setup plugin discovery path: "/插件"
240
241    Text to translate:
242    {}"#,
243            wrap_chinese_text(text, 72)
244        ),
245        TranslateDirection::EnglishToChinese => format!(
246            r#"You are a professional translator. Please translate the following English text to Chinese.
247    Important rules:
248    1. Keep all Chinese content, numbers, and Chinese punctuation unchanged
249    2. Do not translate any content inside quotes
250    3. Maintain the original text structure and formatting
251    4. Only return the Chinese translation, DO NOT include the original English text
252    5. Keep simple and concise, no need to rewrite or expand the content
253    6. Use appropriate Chinese punctuation marks
254
255    Example response format:
256    feat: 添加外部插件支持
257
258    1. 实现插件加载机制
259    2. 添加插件配置接口
260    3. 设置插件发现路径: "/plugins"
261
262    Text to translate:
263    {}"#,
264            text
265        ),
266    };
267
268    debug!("生成的提示词:\n{}", prompt);
269    prompt
270}
271
272#[async_trait]
273impl AiService for DeepSeekTranslator {
274    async fn chat(&self, system_prompt: &str, user_content: &str) -> anyhow::Result<String> {
275        debug!("使用 DeepSeek,API Endpoint: {}", self.endpoint);
276        let client = reqwest::Client::builder()
277            .timeout(std::time::Duration::from_secs(self.timeout_seconds))
278            .build()?;
279
280        let url = format!("{}/chat/completions", self.endpoint);
281        let messages = vec![
282            serde_json::json!({
283                "role": "system",
284                "content": system_prompt
285            }),
286            serde_json::json!({
287                "role": "user",
288                "content": user_content
289            })
290        ];
291        debug!("发送给 DeepSeek 的消息:\n{}", serde_json::to_string_pretty(&messages)?);
292        let body = serde_json::json!({
293            "model": self.model,
294            "messages": messages,
295            "max_tokens": self.max_tokens
296        });
297
298        let ai_host = match url.split('/').nth(2) {
299            Some(host) => host,
300            None => "api.deepseek.com",
301        };
302        print_progress(&format!("正在请求 {} 进行AI对话", ai_host), None);
303
304        loop {
305            match client
306                .post(&url)
307                .header("Authorization", format!("Bearer {}", self.api_key))
308                .json(&body)
309                .send()
310                .await
311            {
312                Ok(response) => {
313                    print_progress(&format!("正在请求 {} 进行AI对话", ai_host), Some(100));
314                    debug!("收到响应: {:#?}", response);
315
316                    if !response.status().is_success() {
317                        let error_json = response.json::<serde_json::Value>().await?;
318                        debug!("响应内容: {}", serde_json::to_string_pretty(&error_json)?);
319                        return Err(anyhow::anyhow!("API 调用失败: {}",
320                            error_json["error"]["message"].as_str().unwrap_or("未知错误")));
321                    }
322
323                    let result = response.json::<serde_json::Value>().await?;
324                    debug!("响应内容: {}", serde_json::to_string_pretty(&result)?);
325
326                    let response = result["choices"][0]["message"]["content"]
327                        .as_str()
328                        .unwrap_or_default();
329                    return Ok(response.to_string());
330                }
331                Err(e) if e.is_timeout() => {
332                    warn!("请求超时: {}", e);
333                    if !Confirm::with_theme(&dialoguer::theme::ColorfulTheme::default())
334                        .with_prompt("请求超时,是否重试?")
335                        .default(true)
336                        .interact()? {
337                        return Err(anyhow::anyhow!("请求超时"));
338                    }
339                    continue;
340                }
341                Err(e) => return Err(e.into()),
342            }
343        }
344    }
345}
346
347#[async_trait]
348impl AiService for OpenAITranslator {
349    async fn chat(&self, system_prompt: &str, user_content: &str) -> anyhow::Result<String> {
350        debug!("使用 OpenAI,API Endpoint: {}", self.endpoint);
351        let client = reqwest::Client::builder()
352            .timeout(std::time::Duration::from_secs(self.timeout_seconds))
353            .build()?;
354
355        let url = format!("{}/chat/completions", self.endpoint);
356        let messages = vec![
357            serde_json::json!({
358                "role": "system",
359                "content": system_prompt
360            }),
361            serde_json::json!({
362                "role": "user",
363                "content": user_content
364            })
365        ];
366        debug!("发送给 OpenAI 的消息:\n{}", serde_json::to_string_pretty(&messages)?);
367        let body = serde_json::json!({
368            "model": self.model,
369            "messages": messages,
370            "max_tokens": self.max_tokens
371        });
372
373        let ai_host = match url.split('/').nth(2) {
374            Some(host) => host,
375            None => "api.openai.com",
376        };
377        print_progress(&format!("正在请求 {} 进行AI对话", ai_host), None);
378
379        loop {
380            match client
381                .post(&url)
382                .header("Authorization", format!("Bearer {}", self.api_key))
383                .json(&body)
384                .send()
385                .await
386            {
387                Ok(response) => {
388                    print_progress(&format!("正在请求 {} 进行AI对话", ai_host), Some(100));
389                    debug!("收到响应: {:#?}", response);
390
391                    if !response.status().is_success() {
392                        let error_json = response.json::<serde_json::Value>().await?;
393                        debug!("响应内容: {}", serde_json::to_string_pretty(&error_json)?);
394                        return Err(anyhow::anyhow!("API 调用失败: {}",
395                            error_json["error"]["message"].as_str().unwrap_or("未知错误")));
396                    }
397
398                    let result = response.json::<serde_json::Value>().await?;
399                    debug!("响应内容: {}", serde_json::to_string_pretty(&result)?);
400
401                    let response = result["choices"][0]["message"]["content"]
402                        .as_str()
403                        .unwrap_or_default();
404                    return Ok(response.to_string());
405                }
406                Err(e) if e.is_timeout() => {
407                    warn!("请求超时: {}", e);
408                    if !Confirm::with_theme(&dialoguer::theme::ColorfulTheme::default())
409                        .with_prompt("请求超时,是否重试?")
410                        .default(true)
411                        .interact()? {
412                        return Err(anyhow::anyhow!("请求超时"));
413                    }
414                    continue;
415                }
416                Err(e) => return Err(e.into()),
417            }
418        }
419    }
420}
421
422#[async_trait]
423impl AiService for ClaudeTranslator {
424    async fn chat(&self, system_prompt: &str, user_content: &str) -> anyhow::Result<String> {
425        debug!("使用 Claude,API Endpoint: {}", self.endpoint);
426        let client = reqwest::Client::builder()
427            .timeout(std::time::Duration::from_secs(self.timeout_seconds))
428            .build()?;
429
430        let url = format!("{}/messages", self.endpoint);
431        let messages = vec![
432            serde_json::json!({
433                "role": "system",
434                "content": system_prompt
435            }),
436            serde_json::json!({
437                "role": "user",
438                "content": user_content
439            })
440        ];
441        debug!("发送给 Claude 的消息:\n{}", serde_json::to_string_pretty(&messages)?);
442        let body = serde_json::json!({
443            "model": self.model,
444            "messages": messages,
445            "max_tokens": self.max_tokens
446        });
447
448        let ai_host = match url.split('/').nth(2) {
449            Some(host) => host,
450            None => "api.anthropic.com",
451        };
452        print_progress(&format!("正在请求 {} 进行AI对话", ai_host), None);
453
454        loop {
455            match client
456                .post(&url)
457                .header("Authorization", format!("Bearer {}", self.api_key))
458                .header("anthropic-version", "2023-06-01")
459                .json(&body)
460                .send()
461                .await
462            {
463                Ok(response) => {
464                    print_progress(&format!("正在请求 {} 进行AI对话", ai_host), Some(100));
465                    debug!("收到响应: {:#?}", response);
466
467                    if !response.status().is_success() {
468                        let error_json = response.json::<serde_json::Value>().await?;
469                        debug!("响应内容: {}", serde_json::to_string_pretty(&error_json)?);
470                        return Err(anyhow::anyhow!("API 调用失败: {}",
471                            error_json["error"]["message"].as_str().unwrap_or("未知错误")));
472                    }
473
474                    let result = response.json::<serde_json::Value>().await?;
475                    debug!("响应内容: {}", serde_json::to_string_pretty(&result)?);
476
477                    let response = result["content"][0]["text"]
478                        .as_str()
479                        .unwrap_or_default();
480                    return Ok(response.to_string());
481                }
482                Err(e) if e.is_timeout() => {
483                    warn!("请求超时: {}", e);
484                    if !Confirm::with_theme(&dialoguer::theme::ColorfulTheme::default())
485                        .with_prompt("请求超时,是否重试?")
486                        .default(true)
487                        .interact()? {
488                        return Err(anyhow::anyhow!("请求超时"));
489                    }
490                    continue;
491                }
492                Err(e) => return Err(e.into()),
493            }
494        }
495    }
496}
497
498#[async_trait]
499impl AiService for CopilotTranslator {
500    async fn chat(&self, system_prompt: &str, user_content: &str) -> anyhow::Result<String> {
501        debug!("使用 Copilot");
502        let ai_host = "copilot.local";
503        print_progress(&format!("正在请求 {} 进行AI对话", ai_host), None);
504
505        let messages = vec![
506            copilot_client::Message {
507                role: "system".to_string(),
508                content: system_prompt.to_string(),
509            },
510            copilot_client::Message {
511                role: "user".to_string(),
512                content: user_content.to_string(),
513            },
514        ];
515        debug!("发送给 Copilot 的消息:\n{}", serde_json::to_string_pretty(&messages)?);
516        let response = self.client.chat_completion(messages, self.model.clone()).await?;
517        print_progress(&format!("正在请求 {} 进行AI对话", ai_host), Some(100));
518        let result = response.choices.get(0)
519            .map(|choice| choice.message.content.clone())
520            .unwrap_or_default();
521        Ok(result)
522    }
523}
524
525#[async_trait]
526impl AiService for GeminiTranslator {
527    async fn chat(&self, system_prompt: &str, user_content: &str) -> anyhow::Result<String> {
528        debug!("使用 Gemini,API Endpoint: {}", self.endpoint);
529        let client = reqwest::Client::builder()
530            .timeout(std::time::Duration::from_secs(self.timeout_seconds))
531            .build()?;
532
533        let url = format!("{}/models/{}:generateContent?key={}", self.endpoint, self.model, self.api_key);
534        let prompt = format!("{}\n\n{}", system_prompt, user_content);
535        debug!("发送给 Gemini 的内容:\n{}", prompt);
536        let body = serde_json::json!({
537            "contents": [{
538                "parts": [{
539                    "text": prompt
540                }]
541            }],
542            "generationConfig": {
543                "maxOutputTokens": self.max_tokens
544            }
545        });
546
547        let ai_host = match url.split('/').nth(2) {
548            Some(host) => host,
549            None => "generativelanguage.googleapis.com",
550        };
551        print_progress(&format!("正在请求 {} 进行AI对话", ai_host), None);
552
553        loop {
554            match client
555                .post(&url)
556                .json(&body)
557                .send()
558                .await
559            {
560                Ok(response) => {
561                    print_progress(&format!("正在请求 {} 进行AI对话", ai_host), Some(100));
562                    debug!("收到响应: {:#?}", response);
563
564                    if !response.status().is_success() {
565                        let error_json = response.json::<serde_json::Value>().await?;
566                        debug!("响应内容: {}", serde_json::to_string_pretty(&error_json)?);
567                        return Err(anyhow::anyhow!("API 调用失败: {}",
568                            error_json["error"]["message"].as_str().unwrap_or("未知错误")));
569                    }
570
571                    let result = response.json::<serde_json::Value>().await?;
572                    debug!("响应内容: {}", serde_json::to_string_pretty(&result)?);
573
574                    let response = result["candidates"][0]["content"]["parts"][0]["text"]
575                        .as_str()
576                        .unwrap_or_default();
577                    return Ok(response.to_string());
578                }
579                Err(e) if e.is_timeout() => {
580                    warn!("请求超时: {}", e);
581                    if !Confirm::with_theme(&dialoguer::theme::ColorfulTheme::default())
582                        .with_prompt("请求超时,是否重试?")
583                        .default(true)
584                        .interact()? {
585                        return Err(anyhow::anyhow!("请求超时"));
586                    }
587                    continue;
588                }
589                Err(e) => return Err(e.into()),
590            }
591        }
592    }
593}
594
595#[async_trait]
596impl AiService for GrokTranslator {
597    async fn chat(&self, system_prompt: &str, user_content: &str) -> anyhow::Result<String> {
598        debug!("使用 Grok,API Endpoint: {}", self.endpoint);
599        let client = reqwest::Client::builder()
600            .timeout(std::time::Duration::from_secs(self.timeout_seconds))
601            .build()?;
602
603        let url = format!("{}/chat/completions", self.endpoint);
604        let messages = vec![
605            serde_json::json!({
606                "role": "system",
607                "content": system_prompt
608            }),
609            serde_json::json!({
610                "role": "user",
611                "content": user_content
612            })
613        ];
614        debug!("发送给 Grok 的消息:\n{}", serde_json::to_string_pretty(&messages)?);
615        let body = serde_json::json!({
616            "model": self.model,
617            "messages": messages,
618            "max_tokens": self.max_tokens
619        });
620
621        let ai_host = match url.split('/').nth(2) {
622            Some(host) => host,
623            None => "api.x.ai",
624        };
625        print_progress(&format!("正在请求 {} 进行AI对话", ai_host), None);
626
627        loop {
628            match client
629                .post(&url)
630                .header("Authorization", format!("Bearer {}", self.api_key))
631                .json(&body)
632                .send()
633                .await
634            {
635                Ok(response) => {
636                    print_progress(&format!("正在请求 {} 进行AI对话", ai_host), Some(100));
637                    debug!("收到响应: {:#?}", response);
638
639                    if !response.status().is_success() {
640                        let error_json = response.json::<serde_json::Value>().await?;
641                        debug!("响应内容: {}", serde_json::to_string_pretty(&error_json)?);
642                        return Err(anyhow::anyhow!("API 调用失败: {}",
643                            error_json["error"]["message"].as_str().unwrap_or("未知错误")));
644                    }
645
646                    let result = response.json::<serde_json::Value>().await?;
647                    debug!("响应内容: {}", serde_json::to_string_pretty(&result)?);
648
649                    let response = result["choices"][0]["message"]["content"]
650                        .as_str()
651                        .unwrap_or_default();
652                    return Ok(response.to_string());
653                }
654                Err(e) if e.is_timeout() => {
655                    warn!("请求超时: {}", e);
656                    if !Confirm::with_theme(&dialoguer::theme::ColorfulTheme::default())
657                        .with_prompt("请求超时,是否重试?")
658                        .default(true)
659                        .interact()? {
660                        return Err(anyhow::anyhow!("请求超时"));
661                    }
662                    continue;
663                }
664                Err(e) => return Err(e.into()),
665            }
666        }
667    }
668}
669
670#[async_trait]
671impl AiService for QwenTranslator {
672    async fn chat(&self, system_prompt: &str, user_content: &str) -> anyhow::Result<String> {
673        debug!("使用 Qwen,API Endpoint: {}", self.endpoint);
674        let client = reqwest::Client::builder()
675            .timeout(std::time::Duration::from_secs(self.timeout_seconds))
676            .build()?;
677
678        let url = format!("{}/chat/completions", self.endpoint);
679        let messages = vec![
680            serde_json::json!({
681                "role": "system",
682                "content": system_prompt
683            }),
684            serde_json::json!({
685                "role": "user",
686                "content": user_content
687            })
688        ];
689        debug!("发送给 Qwen 的消息:\n{}", serde_json::to_string_pretty(&messages)?);
690        let body = serde_json::json!({
691            "model": self.model,
692            "messages": messages,
693            "temperature": 0.1,
694            "max_tokens": self.max_tokens
695        });
696
697        let ai_host = match url.split('/').nth(2) {
698            Some(host) => host,
699            None => "dashscope.aliyuncs.com",
700        };
701        print_progress(&format!("正在请求 {} 进行AI对话", ai_host), None);
702
703        loop {
704            match client
705                .post(&url)
706                .header("Authorization", format!("Bearer {}", self.api_key))
707                .json(&body)
708                .send()
709                .await
710            {
711                Ok(response) => {
712                    print_progress(&format!("正在请求 {} 进行AI对话", ai_host), Some(100));
713                    debug!("收到响应: {:#?}", response);
714
715                    if !response.status().is_success() {
716                        let error_json = response.json::<serde_json::Value>().await?;
717                        debug!("响应内容: {}", serde_json::to_string_pretty(&error_json)?);
718                        return Err(anyhow::anyhow!("API 调用失败: {}",
719                            error_json["error"]["message"].as_str().unwrap_or("未知错误")));
720                    }
721
722                    let result = response.json::<serde_json::Value>().await?;
723                    debug!("响应内容: {}", serde_json::to_string_pretty(&result)?);
724
725                    let response = result["choices"][0]["message"]["content"]
726                        .as_str()
727                        .unwrap_or_default();
728                    return Ok(response.to_string());
729                }
730                Err(e) if e.is_timeout() => {
731                    warn!("请求超时: {}", e);
732                    if !Confirm::with_theme(&dialoguer::theme::ColorfulTheme::default())
733                        .with_prompt("请求超时,是否重试?")
734                        .default(true)
735                        .interact()? {
736                        return Err(anyhow::anyhow!("请求超时"));
737                    }
738                    continue;
739                }
740                Err(e) => return Err(e.into()),
741            }
742        }
743    }
744}
745
746pub async fn create_translator(config: &Config) -> anyhow::Result<Box<dyn Translator>> {
747    info!("创建 {:?} AI服务", config.default_service);
748    let service_config = config.services.iter()
749        .find(|s| s.service == config.default_service)
750        .ok_or_else(|| anyhow::anyhow!("找不到默认服务的配置"))?;
751    create_translator_for_service(service_config).await
752}
753
754pub async fn translate_with_fallback(config: &Config, text: &str, direction: &crate::config::TranslateDirection) -> anyhow::Result<String> {
755    let mut tried_services = Vec::new();
756
757    // 如果已设置环境变量,直接返回原文
758    if std::env::var("GIT_COMMIT_HELPER_NO_TRANSLATE").is_ok() {
759        return Ok(text.trim().to_string());
760    }
761
762    debug!("尝试使用默认服务 {:?}", config.default_service);
763    if let Some(result) = try_translate(&config.default_service, config, text, direction).await {
764        return result;
765    }
766    tried_services.push(config.default_service.clone());
767
768    for service_config in &config.services {
769        if tried_services.contains(&service_config.service) {
770            continue;
771        }
772
773        debug!("尝试使用备选服务 {:?}", service_config.service);
774        if let Some(result) = try_translate(&service_config.service, config, text, direction).await {
775            return result;
776        }
777        tried_services.push(service_config.service.clone());
778    }
779
780    while let Some(service) = select_retry_service(config, &tried_services)? {
781        debug!("用户选择使用 {:?} 重试", service);
782        if let Some(result) = try_translate(&service, config, text, direction).await {
783            return result;
784        }
785        tried_services.push(service);
786    }
787
788    Err(anyhow::anyhow!("所有AI服务均失败"))
789}
790
791async fn try_translate(service: &AIService, config: &Config, text: &str, direction: &crate::config::TranslateDirection) -> Option<anyhow::Result<String>> {
792    let service_config = config.services.iter()
793        .find(|s| s.service == *service)?;
794
795    let translator = create_translator_for_service(service_config).await.ok()?;
796    match translator.translate(text, direction).await {
797        Ok(result) => Some(Ok(result)),
798        Err(e) => {
799            warn!("{:?} 服务翻译失败: {}", service, e);
800            None
801        }
802    }
803}
804
805fn select_retry_service(config: &Config, tried_services: &[AIService]) -> anyhow::Result<Option<AIService>> {
806    let available_services: Vec<_> = config.services.iter()
807        .filter(|s| !tried_services.contains(&s.service))
808        .collect();
809
810    if available_services.is_empty() {
811        return Ok(None);
812    }
813
814    let options: Vec<String> = available_services.iter()
815        .map(|s| format!("{:?}", s.service))
816        .collect();
817
818    println!("\n之前的翻译尝试都失败了,是否要使用其他服务重试?");
819    if !Confirm::with_theme(&dialoguer::theme::ColorfulTheme::default())
820        .default(true)
821        .interact()? {
822        return Ok(None);
823    }
824
825    let selection = Select::with_theme(&dialoguer::theme::ColorfulTheme::default())
826        .with_prompt("请选择要使用的服务")
827        .items(&options)
828        .default(0)
829        .interact()?;
830
831    Ok(Some(available_services[selection].service.clone()))
832}
833
834pub async fn create_translator_for_service(service_config: &AIServiceConfig) -> anyhow::Result<Box<dyn Translator>> {
835    // 获取全局配置中的超时时间
836    let config = crate::config::Config::load().ok();
837    let timeout = config.as_ref().map(|c| c.timeout_seconds).unwrap_or(20);
838
839    Ok(match service_config.service {
840        AIService::DeepSeek => {
841            let mut translator = DeepSeekTranslator::new(service_config);
842            translator.timeout_seconds = timeout;
843            Box::new(translator)
844        },
845        AIService::OpenAI => {
846            let mut translator = OpenAITranslator::new(service_config);
847            translator.timeout_seconds = timeout;
848            Box::new(translator)
849        },
850        AIService::Claude => {
851            let mut translator = ClaudeTranslator::new(service_config);
852            translator.timeout_seconds = timeout;
853            Box::new(translator)
854        },
855        AIService::Copilot => {
856            let editor_version = "1.0.0".to_string();
857            let client = CopilotClient::new_with_models(service_config.api_key.clone(), editor_version).await?;
858            let model_id = service_config.model.clone().unwrap_or_else(|| "copilot-chat".to_string());
859            Box::new(CopilotTranslator::new(client, model_id))
860        },
861        AIService::Gemini => {
862            let mut translator = GeminiTranslator::new(service_config);
863            translator.timeout_seconds = timeout;
864            Box::new(translator)
865        },
866        AIService::Grok => {
867            let mut translator = GrokTranslator::new(service_config);
868            translator.timeout_seconds = timeout;
869            Box::new(translator)
870        },
871        AIService::Qwen => {
872            let mut translator = QwenTranslator::new(service_config);
873            translator.timeout_seconds = timeout;
874            Box::new(translator)
875        },
876    })
877}