Skip to main content

git_commit_helper_cli/
config.rs

1use anyhow::{Context, Result};
2use dialoguer::{Confirm, Input};
3use directories::ProjectDirs;
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6use std::fs;
7use copilot_client::CopilotClient;
8use copilot_client::get_github_token;
9use log::{debug, info, warn};
10use dialoguer::console::Term;
11use crate::ai_service;
12
13#[derive(Debug, Serialize, Deserialize, Clone)]
14pub struct Config {
15    pub default_service: AIService,
16    pub services: Vec<AIServiceConfig>,
17    #[serde(default = "default_ai_review")]
18    pub ai_review: bool,  // 添加 AI Review 开关
19    #[serde(default = "default_timeout")]
20    pub timeout_seconds: u64,  // 添加请求超时时间设置
21    #[serde(default = "default_max_tokens")]
22    pub max_tokens: u64,  // 添加响应的最大 token
23    #[serde(default)]
24    pub gerrit: Option<GerritConfig>,  // Gerrit 配置
25    #[serde(default = "default_only_chinese")]
26    pub only_chinese: bool,  // 是否默认只使用中文
27    #[serde(default = "default_only_english")]
28    pub only_english: bool,  // 是否默认只使用英文
29    #[serde(default = "default_translate_direction")]
30    pub translate_direction: TranslateDirection,  // 默认翻译方向
31}
32
33// 添加默认值函数
34fn default_only_chinese() -> bool {
35    false
36}
37
38fn default_only_english() -> bool {
39    false
40}
41
42fn default_translate_direction() -> TranslateDirection {
43    TranslateDirection::ChineseToEnglish
44}
45
46#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
47pub enum TranslateDirection {
48    ChineseToEnglish,  // 中译英(默认)
49    EnglishToChinese,  // 英译中
50}
51
52#[derive(Debug, Serialize, Deserialize, Clone, Default)]
53pub struct GerritConfig {
54    pub username: Option<String>,
55    pub password: Option<String>,
56    pub token: Option<String>,
57}
58
59// 添加默认值函数
60fn default_ai_review() -> bool {
61    true
62}
63
64// 添加默认超时时间函数
65fn default_timeout() -> u64 {
66    20
67}
68
69// 添加响应的最大token
70fn default_max_tokens() -> u64 {
71    2048
72}
73
74#[derive(Debug, Serialize, Deserialize, Clone)]
75pub struct AIServiceConfig {
76    pub service: AIService,
77    pub api_key: String,
78    pub api_endpoint: Option<String>,
79    pub model: Option<String>,  // 新增字段
80}
81
82#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
83pub enum AIService {
84    DeepSeek,
85    OpenAI,  // Changed from ChatGPT
86    Claude,
87    Copilot,
88    Gemini,  // 新增
89    Grok,    // 新增
90    Qwen,    // 新增
91}
92
93impl Config {
94    pub fn new() -> Self {
95        Self {
96            default_service: AIService::OpenAI, // Changed from ChatGPT
97            services: Vec::new(),
98            ai_review: true,  // 默认开启
99            timeout_seconds: default_timeout(),
100            max_tokens: default_max_tokens(),
101            gerrit: None,
102            only_chinese: false,  // 默认关闭
103            only_english: false,  // 默认关闭
104            translate_direction: default_translate_direction(),  // 默认中译英
105        }
106    }
107
108    pub fn load() -> Result<Self> {
109        let config_path = Self::config_path()?;
110        debug!("尝试加载配置文件: {}", config_path.display());
111
112        if !config_path.exists() {  // 移除多余的括号
113            warn!("配置文件不存在: {}", config_path.display());
114            return Err(anyhow::anyhow!("配置文件不存在,请先运行 'git-commit-helper config' 进行配置"));
115        }
116
117        let config_str = fs::read_to_string(&config_path)
118            .context("读取配置文件失败")?;
119        let config: Config = serde_json::from_str(&config_str)
120            .context("解析配置文件失败")?;
121
122        info!("已加载配置,使用 {:?} 服务", config.default_service);
123
124        Ok(config)
125    }
126
127    pub async fn interactive_config() -> Result<()> {
128        Box::pin(Self::interactive_config_impl()).await
129    }
130
131    pub async fn setup_gerrit(&mut self) -> Result<()> {
132        println!("\nGerrit 认证配置");
133        println!("选择认证方式:");
134        println!("1) 用户名密码");
135        println!("2) Token");
136        println!("3) 跳过 (不配置)");
137
138        let selection: usize = Input::new()
139            .with_prompt("请选择认证方式")
140            .default(3)
141            .validate_with(|input: &usize| -> Result<(), &str> {
142                if *input >= 1 && *input <= 3 {
143                    Ok(())
144                } else {
145                    Err("请输入 1-3 之间的数字")
146                }
147            })
148            .interact()?;
149
150        let mut gerrit_config = GerritConfig::default();
151
152        match selection {
153            1 => {
154                let username: String = Input::new()
155                    .with_prompt("请输入 Gerrit 用户名")
156                    .interact_text()?;
157
158                let password: String = Input::new()
159                    .with_prompt("请输入 Gerrit 密码")
160                    .interact_text()?;
161
162                gerrit_config.username = Some(username);
163                gerrit_config.password = Some(password);
164            }
165            2 => {
166                let token: String = Input::new()
167                    .with_prompt("请输入 Gerrit Token")
168                    .interact_text()?;
169
170                gerrit_config.token = Some(token);
171            }
172            _ => {
173                // 不配置
174                self.gerrit = None;
175                return Ok(());
176            }
177        }
178
179        self.gerrit = Some(gerrit_config);
180        self.save()?;
181
182        println!("✅ Gerrit 认证信息已保存");
183        Ok(())
184    }
185
186    pub async fn interactive_config_impl() -> Result<()> {
187        info!("开始交互式配置...");
188        // 询问配置文件存放位置
189        let default_path = Self::default_config_path()?;
190        println!("\n配置文件存放位置选项:");
191        println!("1) 系统默认位置: {}", default_path.display());
192        println!("2) 自定义路径");
193
194        let selection: usize = Input::new()
195            .with_prompt("请选择配置文件存放位置")
196            .validate_with(|input: &usize| -> Result<(), &str> {
197                if *input >= 1 && *input <= 2 {
198                    Ok(())
199                } else {
200                    Err("请输入 1-2 之间的数字")
201                }
202            })
203            .interact()?;
204
205        let config_path = if selection == 1 {
206            default_path
207        } else {
208            let custom_path: String = Input::new()
209                .with_prompt("请输入配置文件路径 (相对路径将基于可执行文件所在目录)")
210                .interact_text()?;
211
212            let path = PathBuf::from(&custom_path);
213            if path.is_relative() {
214                let exe_dir = std::env::current_exe()?
215                    .parent()
216                    .ok_or_else(|| anyhow::anyhow!("无法获取可执行文件目录"))?
217                    .to_path_buf();
218                exe_dir.join(path)
219            } else {
220                path
221            }
222        };
223
224        // 设置环境变量,用于后续加载配置
225        std::env::set_var("GIT_COMMIT_HELPER_CONFIG", config_path.to_string_lossy().to_string());
226
227        let mut services: Vec<AIServiceConfig> = Vec::new();
228
229        loop {
230            println!("\n当前已配置的 AI 服务:");
231            for (i, s) in services.iter().enumerate() {
232                println!("{}. {:?}", i + 1, s.service);
233            }
234
235            if !Confirm::with_theme(&dialoguer::theme::ColorfulTheme::default())
236                .with_prompt("是否继续添加 AI 服务?")
237                .default(services.is_empty())
238                .interact()?
239            {
240                break;
241            }
242
243            println!("\n请选择要添加的 AI 服务:");
244            println!("1) DeepSeek");
245            println!("2) OpenAI");
246            println!("3) Claude");
247            println!("4) Copilot");
248            println!("5) Gemini");
249            println!("6) Grok");
250            println!("7) Qwen");
251
252            let selection = Input::<String>::new()
253                .with_prompt("请输入对应的数字")
254                .report(true)
255                .validate_with(|input: &String| -> Result<(), &str> {
256                    match input.parse::<usize>() {
257                        Ok(n) if n >= 1 && n <= 7 => Ok(()),
258                        _ => Err("请输入 1-7 之间的数字")
259                    }
260                })
261                .interact()?
262                .parse::<usize>()?;
263
264            let service = match selection {
265                1 => AIService::DeepSeek,
266                2 => AIService::OpenAI,
267                3 => AIService::Claude,
268                4 => AIService::Copilot,
269                5 => AIService::Gemini,
270                6 => AIService::Grok,
271                7 => AIService::Qwen,
272                _ => unreachable!(),
273            };
274
275            let config = Config::input_service_config(service).await?;
276            services.push(config);
277        }
278
279        if services.is_empty() {
280            return Err(anyhow::anyhow!("至少需要配置一个 AI 服务"));
281        }
282
283        println!("\n请选择默认的 AI 服务:");
284        for (i, s) in services.iter().enumerate() {
285            println!("{}. {:?}", i + 1, s.service);
286        }
287
288        let services_len = services.len();
289        let default_index: usize = Input::new()
290            .with_prompt("请输入对应的数字")
291            .validate_with(|input: &usize| -> Result<(), &str> {
292                if *input >= 1 && *input <= services_len {
293                    Ok(())
294                } else {
295                    Err("输入的数字超出范围")
296                }
297            })
298            .interact()?;
299
300        let mut config = Config {
301            default_service: services[default_index - 1].service.clone(),
302            services,
303            ai_review: true,  // 默认开启
304            timeout_seconds: default_timeout(),
305            max_tokens: default_max_tokens(),
306            gerrit: None,
307            only_chinese: false,  // 默认关闭
308            only_english: false,  // 默认关闭
309            translate_direction: default_translate_direction(),  // 默认中译英
310        };
311
312        // 确保配置目录存在
313        if let Some(parent) = config_path.parent() {
314            fs::create_dir_all(parent)?;
315        }
316
317        // 保存配置
318        fs::write(&config_path, serde_json::to_string_pretty(&config)?)?;
319        info!("配置已保存: {}", config_path.display());
320        println!("配置已保存到: {}", config_path.display());
321
322        // 询问是否进行测试
323        if Confirm::new()
324            .with_prompt("是否要测试翻译功能?")
325            .default(true)
326            .interact()?
327        {
328            println!("正在测试翻译功能...");
329            // 创建一个临时的 Config 对象,确保只测试默认服务
330            let test_config = Config {
331                default_service: config.default_service.clone(),
332                services: vec![config.services[default_index - 1].clone()],
333                ai_review: true,
334                timeout_seconds: config.timeout_seconds,
335                max_tokens: config.max_tokens,
336                gerrit: None,
337                only_chinese: false,
338                only_english: false,
339                translate_direction: default_translate_direction(),
340            };
341            let translator = ai_service::create_translator(&test_config).await?;
342            match translator.translate("这是一个测试消息,用于验证翻译功能是否正常。", &TranslateDirection::ChineseToEnglish).await {
343                Ok(result) => {
344                    println!("\n测试结果:");
345                    println!("原文: 这是一个测试消息,用于验证翻译功能是否正常。");
346                    println!("译文: {}\n", result);
347                    println!("测试成功!配置已完成。");
348                },
349                Err(e) => {
350                    println!("\n测试失败!错误信息:");
351                    println!("{}", e);
352                    println!("\n请检查以下内容:");
353                    println!("1. API Key 是否正确");
354                    println!("2. API Endpoint 是否可访问");
355                    println!("3. 网络连接是否正常");
356
357                    println!("\n请选择操作:");
358                    println!("1. 重新修改配置");
359                    println!("2. 强制保存配置");
360                    println!("3. 退出");
361
362                    let selection: usize = Input::new()
363                        .with_prompt("请输入对应的数字")
364                        .validate_with(|input: &usize| -> Result<(), &str> {
365                            if *input >= 1 && *input <= 3 {
366                                Ok(())
367                            } else {
368                                Err("请输入 1-3 之间的数字")
369                            }
370                        })
371                        .interact()?;
372
373                    match selection {
374                        1 => {
375                            // 重新获取当前服务的配置
376                            let new_config = Config::input_service_config(config.default_service.clone()).await?;
377                            config.services.pop(); // 移除失败的配置
378                            config.services.push(new_config); // 添加新配置
379                            // 重新保存配置
380                            fs::write(&config_path, serde_json::to_string_pretty(&config)?)?;
381                            // 递归调用测试,使用 Box::pin
382                            return Box::pin(Config::interactive_config_impl()).await;
383                        },
384                        2 => {
385                            println!("配置已强制保存,但可能无法正常工作。");
386                            return Ok(());
387                        },
388                        _ => return Err(e),
389                    }
390                }
391            }
392        }
393
394        Ok(())
395    }
396
397    pub async fn add_service(&mut self, service: AIService) -> Result<()> {
398        Box::pin(self.add_service_impl(service)).await
399    }
400
401    async fn add_service_impl(&mut self, service: AIService) -> Result<()> {
402        // 获取服务配置
403        let config = match service {
404            AIService::Copilot => {
405                println!("Copilot 服务需要 GitHub 身份验证...");
406
407                // 尝试获取 GitHub token
408                match get_github_token() {
409                    Ok(token) => {
410                        println!("✅ 已成功获取 GitHub 令牌");
411                        // 尝试连接 Copilot API 验证令牌
412                        let editor_version = "1.0.0".to_string();
413                        let client = CopilotClient::new_with_models(token.clone(), editor_version).await;
414                        match client {
415                            Ok(client) => {
416                                println!("✅ GitHub Copilot 认证成功!");
417                                // 获取可用模型
418                                let models = client.get_models().await?;
419                                if !models.is_empty() {
420                                    println!("\n可用模型:");
421                                    for (i, model) in models.iter().enumerate() {
422                                        println!("  {}. {} ({})", i+1, model.name, model.id);
423                                    }
424
425                                    // 让用户选择模型
426                                    let model_count = models.len();
427                                    let selection = Input::<String>::new()
428                                        .with_prompt("请选择要使用的模型编号 (留空使用默认)")
429                                        .allow_empty(true)
430                                        .validate_with(|input: &String| -> Result<(), &str> {
431                                            if input.is_empty() {
432                                                return Ok(());
433                                            }
434                                            match input.parse::<usize>() {
435                                                Ok(n) if n >= 1 && n <= model_count => Ok(()),
436                                                _ => Err("请输入有效的模型编号或留空")
437                                            }
438                                        })
439                                        .interact()?;
440
441                                    // 处理用户选择
442                                    let model_id = if selection.is_empty() {
443                                        "copilot-chat".to_string()
444                                    } else {
445                                        let idx = selection.parse::<usize>().unwrap() - 1;
446                                        models[idx].id.clone()
447                                    };
448
449                                    // 返回配置,使用用户选择的模型
450                                    AIServiceConfig {
451                                        service: AIService::Copilot,
452                                        api_key: token,
453                                        api_endpoint: None,
454                                        model: Some(model_id),
455                                    }
456                                } else {
457                                    // 如果没有可用模型列表,使用默认模型
458                                    AIServiceConfig {
459                                        service: AIService::Copilot,
460                                        api_key: token,
461                                        api_endpoint: None,
462                                        model: Some("copilot-chat".to_string()),
463                                    }
464                                }
465                            },
466                            Err(e) => {
467                                println!("❌ Copilot API 连接失败: {}", e);
468                                println!("请确保您已订阅 GitHub Copilot 服务并拥有有效权限。");
469                                return Err(anyhow::anyhow!("Copilot 认证失败"));
470                            }
471                        }
472                    },
473                    Err(e) => {
474                        println!("❌ 无法获取 GitHub 令牌: {}", e);
475                        println!("\n请按照以下步骤获取 GitHub 令牌:");
476                        println!("可使用QtCreator中的Copilot插件获取到copilot的token,或直接使用copilot.nvim在nvim中获取token:https://github.com/github/copilot.vim");
477                        println!("\n按回车键继续...");
478                        Term::stdout().read_line()?;
479                        return Err(anyhow::anyhow!("无法获取 GitHub 令牌"));
480                    }
481                }
482            },
483            _ => Config::input_service_config_with_default(&AIServiceConfig {
484                service: service.clone(),
485                api_key: String::new(),
486                api_endpoint: None,
487                model: None,
488            }).await?,
489        };
490
491        // 添加服务
492        if self.services.is_empty() {
493            self.default_service = config.service.clone();
494        }
495        self.services.push(config.clone());
496
497        // 提供测试选项
498        if Confirm::new()
499            .with_prompt("是否要测试该服务?")
500            .default(true)
501            .interact()?
502        {
503            println!("正在测试 {:?} 服务...", config.service);
504            // 创建一个临时的 Config 对象,只包含要测试的新服务
505            let test_config = Config {
506                default_service: config.service.clone(),
507                services: vec![config.clone()],
508                ai_review: true,
509                timeout_seconds: self.timeout_seconds,
510                max_tokens: self.max_tokens,
511                gerrit: None,
512                only_chinese: false,
513                only_english: false,
514                translate_direction: default_translate_direction(),
515            };
516            let translator = ai_service::create_translator(&test_config).await?;
517            let text = "这是一个测试消息,用于验证翻译功能是否正常。";
518            debug!("开始发送翻译请求");
519            match translator.translate(text, &TranslateDirection::ChineseToEnglish).await {
520                Ok(result) => {
521                    debug!("收到翻译响应");
522                    println!("\n测试结果:");
523                    println!("原文: {}", text);
524                    if result.is_empty() {
525                        println!("警告: 收到空的翻译结果!");
526                    }
527                    println!("译文: {}", result);
528                    println!("\n✅ 测试成功!服务已添加并可正常使用。");
529                    self.save()?;
530                },
531                Err(e) => {
532                    println!("\n❌ 测试失败!错误信息:");
533                    println!("{}", e);
534                    println!("\n请检查:");
535                    println!("1. API Key 是否正确");
536                    println!("2. API Endpoint 是否可访问");
537                    println!("3. 网络连接是否正常");
538                    println!("4. 查看日志获取详细信息(设置 RUST_LOG=debug)");
539
540                    println!("\n请选择操作:");
541                    println!("1. 重新配置服务");
542                    println!("2. 强制保存配置");
543                    println!("3. 放弃添加");
544
545                    let selection: usize = Input::new()
546                        .with_prompt("请输入对应的数字")
547                        .validate_with(|input: &usize| -> Result<(), &str> {
548                            if *input >= 1 && *input <= 3 {
549                                Ok(())
550                            } else {
551                                Err("请输入 1-3 之间的数字")
552                            }
553                        })
554                        .interact()?;
555
556                    match selection {
557                        1 => {
558                            // 移除刚添加的服务
559                            self.services.pop();
560                            // 使用 Box::pin 包装递归调用
561                            return Box::pin(self.add_service_impl(service)).await;
562                        },
563                        2 => {
564                            println!("配置已强制保存,但服务可能无法正常工作。");
565                            self.save()?;
566                        },
567                        _ => {
568                            self.services.pop(); // 移除失败的服务
569                            return Err(anyhow::anyhow!("已取消添加服务"));
570                        }
571                    }
572                }
573            }
574        } else {
575            self.save()?;
576            println!("✅ {:?} 服务已添加(未测试)", service);
577        }
578
579        info!("AI 服务已添加");
580        Ok(())
581    }
582
583    pub async fn edit_service(&mut self) -> Result<()> {
584        if self.services.is_empty() {
585            return Err(anyhow::anyhow!("没有可编辑的 AI 服务"));
586        }
587
588        println!("\n已配置的 AI 服务:");
589        for (i, s) in self.services.iter().enumerate() {
590            println!("{}. {:?}", i + 1, s.service);
591        }
592
593        let selection = Input::<String>::with_theme(&dialoguer::theme::ColorfulTheme::default())
594            .with_prompt("请输入要编辑的服务编号")
595            .report(true)
596            .interact()?
597            .parse::<usize>()?;
598
599        // 验证选择的服务编号是否有效
600        if selection < 1 || selection > self.services.len() {
601            return Err(anyhow::anyhow!("无效的服务编号"));
602        }
603
604        let old_config = &self.services[selection - 1];
605        let new_config = Config::input_service_config_with_default(old_config).await?;
606
607        // 不进行测试,直接更新服务
608        self.services[selection - 1] = new_config;
609        self.save()?;
610
611        println!("✅ 服务配置已更新。请稍后使用 'git-commit-helper test' 命令测试该服务。");
612        info!("AI 服务已修改(未测试)");
613
614        Ok(())
615    }
616
617    pub async fn remove_service(&mut self) -> Result<()> {
618        if self.services.is_empty() {
619            return Err(anyhow::anyhow!("没有可删除的 AI 服务"));
620        }
621
622        println!("\n已配置的 AI 服务:");
623        for (i, s) in self.services.iter().enumerate() {
624            println!("{}. {:?}", i + 1, s.service);
625        }
626
627        let services_len = self.services.len();
628        let selection = Input::<String>::new()
629            .with_prompt("请输入要删除的服务编号")
630            .report(true)
631            .validate_with(|input: &String| -> Result<(), &str> {
632                match input.parse::<usize>() {
633                    Ok(n) if n >= 1 && n <= services_len => Ok(()),
634                    _ => Err("输入的数字超出范围")
635                }
636            })
637            .interact()?
638            .parse::<usize>()?;
639
640        let removed = self.services.remove(selection - 1);
641
642        if removed.service == self.default_service && !self.services.is_empty() {
643            self.default_service = self.services[0].service.clone();
644        }
645
646        self.save()?;
647        info!("AI 服务删除成功");
648        Ok(())
649    }
650
651    pub async fn set_default_service(&mut self) -> Result<()> {
652        if self.services.is_empty() {
653            return Err(anyhow::anyhow!("没有可选择的 AI 服务"));
654        }
655
656        println!("\n已配置的 AI 服务:");
657        for (i, s) in self.services.iter().enumerate() {
658            println!("{}. {:?}", i + 1, s.service);
659        }
660
661        let services_len = self.services.len();
662        let selection = Input::<String>::new()
663            .with_prompt("请输入要设为默认的服务编号")
664            .report(true)
665            .validate_with(|input: &String| -> Result<(), &str> {
666                match input.parse::<usize>() {
667                    Ok(n) if n >= 1 && n <= services_len => Ok(()),
668                    _ => Err("输入的数字超出范围")
669                }
670            })
671            .interact()?
672            .parse::<usize>()?;
673
674        self.default_service = self.services[selection - 1].service.clone();
675        self.save()?;
676        info!("默认 AI 服务设置成功");
677        Ok(())
678    }
679
680    pub async fn input_service_config(service: AIService) -> Result<AIServiceConfig> {
681        // 对于除 Copilot 以外的服务,使用默认逻辑
682        Config::input_service_config_with_default(&AIServiceConfig {
683            service,
684            api_key: String::new(),
685            api_endpoint: None,
686            model: None,
687        }).await
688    }
689
690    pub async fn input_service_config_with_default(default: &AIServiceConfig) -> Result<AIServiceConfig> {
691        // 如果是 Copilot 服务,使用特殊处理
692        if default.service == AIService::Copilot {
693            // 为已存在的 Copilot 配置,只询问模型
694            if !default.api_key.is_empty() {
695                // 尝试连接 Copilot API 获取可用模型
696                let editor_version = "1.0.0".to_string();
697                match CopilotClient::new_with_models(default.api_key.clone(), editor_version).await {
698                    Ok(client) => {
699                        let models = client.get_models().await?;
700                        if !models.is_empty() {
701                            println!("\n可用模型:");
702                            for (i, model) in models.iter().enumerate() {
703                                println!("  {}. {} ({})", i+1, model.name, model.id);
704                            }
705
706                            // 显示当前选择的模型
707                            let current_model = default.model.as_deref().unwrap_or("copilot-chat");
708                            println!("\n当前选择的模型: {}", current_model);
709
710                            // 让用户选择模型
711                            let model_count = models.len();
712                            let selection = Input::<String>::new()
713                                .with_prompt("请选择要使用的模型编号 (留空保持当前选择)")
714                                .allow_empty(true)
715                                .validate_with(|input: &String| -> Result<(), &str> {
716                                    if input.is_empty() {
717                                        return Ok(());
718                                    }
719                                    match input.parse::<usize>() {
720                                        Ok(n) if n >= 1 && n <= model_count => Ok(()),
721                                        _ => Err("请输入有效的模型编号或留空")
722                                    }
723                                })
724                                .interact()?;
725
726                            // 处理用户选择
727                            let model_id = if selection.is_empty() {
728                                default.model.clone().unwrap_or_else(|| "copilot-chat".to_string())
729                            } else {
730                                let idx = selection.parse::<usize>().unwrap() - 1;
731                                models[idx].id.clone()
732                            };
733
734                            return Ok(AIServiceConfig {
735                                service: default.service.clone(),
736                                api_key: default.api_key.clone(),
737                                api_endpoint: None,
738                                model: Some(model_id),
739                            });
740                        }
741                    },
742                    Err(e) => {
743                        println!("⚠️ 无法获取模型列表: {}", e);
744                        println!("将使用之前配置的模型或默认模型。");
745                    }
746                }
747
748                let model: String = Input::new()
749                    .with_prompt("请输入模型名称 (可选,直接回车使用默认值) [copilot-chat]")
750                    .with_initial_text(default.model.as_deref().unwrap_or("copilot-chat"))
751                    .allow_empty(true)
752                    .interact_text()?;
753
754                return Ok(AIServiceConfig {
755                    service: default.service.clone(),
756                    api_key: default.api_key.clone(),  // 保留原有 token
757                    api_endpoint: None,
758                    model: if model.is_empty() { Some("copilot-chat".to_string()) } else { Some(model) },
759                });
760            } else {
761                // 如果没有 API key,直接处理 Copilot 验证,而不是递归调用
762                println!("Copilot 服务需要 GitHub 身份验证...");
763
764                // 尝试获取 GitHub token
765                match get_github_token() {
766                    Ok(token) => {
767                        println!("✅ 已成功获取 GitHub 令牌");
768                        // 尝试连接 Copilot API 验证令牌
769                        let editor_version = "1.0.0".to_string();
770                        let client = CopilotClient::new_with_models(token.clone(), editor_version).await;
771                        match client {
772                            Ok(client) => {
773                                println!("✅ GitHub Copilot 认证成功!");
774                                // 获取可用模型
775                                let models = client.get_models().await?;
776                                if !models.is_empty() {
777                                    println!("\n可用模型:");
778                                    for (i, model) in models.iter().enumerate() {
779                                        println!("  {}. {} ({})", i+1, model.name, model.id);
780                                    }
781
782                                    // 让用户选择模型
783                                    let model_count = models.len();
784                                    let selection = Input::<String>::new()
785                                        .with_prompt("请选择要使用的模型编号 (留空使用默认)")
786                                        .allow_empty(true)
787                                        .validate_with(|input: &String| -> Result<(), &str> {
788                                            if input.is_empty() {
789                                                return Ok(());
790                                            }
791                                            match input.parse::<usize>() {
792                                                Ok(n) if n >= 1 && n <= model_count => Ok(()),
793                                                _ => Err("请输入有效的模型编号或留空")
794                                            }
795                                        })
796                                        .interact()?;
797
798                                    // 处理用户选择
799                                    let model_id = if selection.is_empty() {
800                                        "copilot-chat".to_string()
801                                    } else {
802                                        let idx = selection.parse::<usize>().unwrap() - 1;
803                                        models[idx].id.clone()
804                                    };
805
806                                    // 返回配置,使用用户选择的模型
807                                    return Ok(AIServiceConfig {
808                                        service: AIService::Copilot,
809                                        api_key: token,
810                                        api_endpoint: None,
811                                        model: Some(model_id),
812                                    });
813                                } else {
814                                    // 如果没有可用模型列表,使用默认模型
815                                    return Ok(AIServiceConfig {
816                                        service: AIService::Copilot,
817                                        api_key: token,
818                                        api_endpoint: None,
819                                        model: Some("copilot-chat".to_string()),
820                                    });
821                                }
822                            },
823                            Err(e) => {
824                                println!("❌ Copilot API 连接失败: {}", e);
825                                println!("请确保您已订阅 GitHub Copilot 服务并拥有有效权限。");
826                                return Err(anyhow::anyhow!("Copilot 认证失败"));
827                            }
828                        }
829                    },
830                    Err(e) => {
831                        println!("❌ 无法获取 GitHub 令牌: {}", e);
832                        println!("\n请按照以下步骤获取 GitHub 令牌:");
833                        println!("可使用QtCreator中的Copilot插件获取到copilot的token,或直接使用copilot.nvim在nvim中获取token:https://github.com/github/copilot.vim");
834                        println!("\n按回车键继续...");
835                        Term::stdout().read_line()?;
836                        return Err(anyhow::anyhow!("无法获取 GitHub 令牌"));
837                    }
838                }
839            }
840        }
841
842        // 非 Copilot 服务需要 API Key
843        let api_key: String = Input::new()
844            .with_prompt("请输入 API Key")
845            .with_initial_text(&default.api_key)
846            .interact_text()?;
847
848        let default_endpoint = match default.service {
849            AIService::DeepSeek => "https://api.deepseek.com/v1",
850            AIService::OpenAI => "https://api.openai.com/v1",
851            AIService::Claude => "https://api.anthropic.com/v1",
852            AIService::Copilot => "",  // Copilot 不需要 endpoint
853            AIService::Gemini => "https://generativelanguage.googleapis.com/v1beta",
854            AIService::Grok => "https://api.x.ai/v1",
855            AIService::Qwen => "https://dashscope.aliyuncs.com/compatible-mode/v1",
856        };
857        let api_endpoint: String = Input::new()
858            .with_prompt(format!("请输入 API Endpoint (可选,直接回车使用默认值) [{}]", default_endpoint))
859            .with_initial_text(default.api_endpoint.as_deref().unwrap_or(""))
860            .allow_empty(true)
861            .interact_text()?;
862
863        let default_model_name = match default.service {
864            AIService::DeepSeek => "deepseek-chat",
865            AIService::OpenAI => "gpt-3.5-turbo",
866            AIService::Claude => "claude-3-sonnet-20240229",
867            AIService::Copilot => "copilot-chat",
868            AIService::Gemini => "gemini-2.0-flash",
869            AIService::Grok => "grok-3-latest",
870            AIService::Qwen => "qwen-plus",
871        };
872        let model: String = Input::new()
873            .with_prompt(format!("请输入模型名称 (可选,直接回车使用默认值) [{}]", default_model_name))
874            .with_initial_text(default.model.as_deref().unwrap_or(""))
875            .allow_empty(true)
876            .interact_text()?;
877
878        Ok(AIServiceConfig {
879            service: default.service.clone(),
880            api_key,
881            api_endpoint: if api_endpoint.is_empty() { None } else { Some(api_endpoint) },
882            model: if model.is_empty() { None } else { Some(model) },
883        })
884    }
885
886    pub fn get_default_service(&self) -> Result<&AIServiceConfig> {
887        if self.services.is_empty() {
888            return Err(anyhow::anyhow!("没有配置任何 AI 服务"));
889        }
890
891        // 查找默认服务
892        if let Some(service) = self.services.iter().find(|s| s.service == self.default_service) {
893            return Ok(service);
894        }
895
896        // 如果没有设置默认服务或默认服务不存在,返回第一个服务
897        Ok(&self.services[0])
898    }
899
900    pub fn save(&self) -> Result<()> {
901        let config_path = Self::config_path()?;
902        if let Some(parent) = config_path.parent() {
903            fs::create_dir_all(parent)?;
904        }
905        fs::write(&config_path, serde_json::to_string_pretty(&self)?)?;
906        Ok(())
907    }
908
909    pub fn config_path() -> Result<PathBuf> {
910        if let Ok(path) = std::env::var("GIT_COMMIT_HELPER_CONFIG") {
911            return Ok(PathBuf::from(path));
912        }
913        Self::default_config_path()
914    }
915
916    fn default_config_path() -> Result<PathBuf> {
917        let proj_dirs = ProjectDirs::from("com", "githelper", "git-commit-helper")
918            .context("无法确定配置文件路径")?;
919        Ok(proj_dirs.config_dir().join("config.json"))
920    }
921}