1use crate::cli::display_utils::{
2 TextAlignment, format_token_for_display, get_terminal_width, pad_text_to_width,
3 text_display_width,
4};
5use crate::config::EnvironmentConfig;
6use crate::config::types::{ConfigStorage, Configuration};
7use anyhow::{Context, Result};
8use colored::*;
9use crossterm::{
10 event::{self, Event, KeyCode, KeyEvent, KeyEventKind},
11 execute, terminal,
12};
13use std::io::{self, Write};
14use std::process::Command;
15use std::thread;
16use std::time::Duration;
17
18struct BorderDrawing {
20 pub unicode_supported: bool,
22}
23
24impl BorderDrawing {
25 fn new() -> Self {
27 let unicode_supported = Self::detect_unicode_support();
28 Self { unicode_supported }
29 }
30
31 fn detect_unicode_support() -> bool {
33 if let Ok(term) = std::env::var("TERM") {
35 if term.contains("xterm") || term.contains("screen") || term == "tmux-256color" {
37 return true;
38 }
39 }
40
41 if let Ok(lang) = std::env::var("LANG")
43 && (lang.contains("UTF-8") || lang.contains("utf8"))
44 {
45 return true;
46 }
47
48 true
51 }
52
53 fn draw_top_border(&self, title: &str, width: usize) -> String {
55 if self.unicode_supported {
56 let title_padded = format!(" {title} ");
57 let title_len = text_display_width(&title_padded);
58
59 if title_len >= width.saturating_sub(2) {
60 format!("╔{}╗", "═".repeat(width.saturating_sub(2)))
62 } else {
63 let inner_width = width.saturating_sub(2); let padding_total = inner_width.saturating_sub(title_len);
65 let padding_left = padding_total / 2;
66 let padding_right = padding_total - padding_left;
67 format!(
68 "╔{}{}{}╗",
69 "═".repeat(padding_left),
70 title_padded,
71 "═".repeat(padding_right)
72 )
73 }
74 } else {
75 let title_padded = format!(" {title} ");
77 let title_len = title_padded.len();
78
79 if title_len >= width.saturating_sub(2) {
80 format!("+{}+", "-".repeat(width.saturating_sub(2)))
81 } else {
82 let inner_width = width.saturating_sub(2);
83 let padding_total = inner_width.saturating_sub(title_len);
84 let padding_left = padding_total / 2;
85 let padding_right = padding_total - padding_left;
86 format!(
87 "+{}{}{}+",
88 "-".repeat(padding_left),
89 title_padded,
90 "-".repeat(padding_right)
91 )
92 }
93 }
94 }
95
96 fn draw_middle_line(&self, text: &str, width: usize) -> String {
98 if self.unicode_supported {
99 let text_len = text_display_width(text);
100 let available_width = width.saturating_sub(4);
103 if text_len > available_width {
104 let mut current_width = 0;
106 let truncated: String = text
107 .chars()
108 .take_while(|&c| {
109 let char_width = match c as u32 {
110 0x00..=0x7F => 1,
111 0x80..=0x2FF => 1,
112 0x2190..=0x21FF => 2,
113 0x3000..=0x303F => 2,
114 0x3040..=0x309F => 2,
115 0x30A0..=0x30FF => 2,
116 0x4E00..=0x9FFF => 2,
117 0xAC00..=0xD7AF => 2,
118 0x3400..=0x4DBF => 2,
119 0xFF01..=0xFF60 => 2,
120 _ => 1,
121 };
122 if current_width + char_width <= available_width {
123 current_width += char_width;
124 true
125 } else {
126 false
127 }
128 })
129 .collect();
130 let truncated_width = text_display_width(&truncated);
132 let padding_spaces = available_width.saturating_sub(truncated_width);
133 format!("║ {}{} ║", truncated, " ".repeat(padding_spaces))
134 } else {
135 let padded_text =
136 pad_text_to_width(text, available_width, TextAlignment::Left, ' ');
137 format!("║ {padded_text} ║")
138 }
139 } else {
140 let text_len = text_display_width(text);
142 let available_width = width.saturating_sub(4);
143 if text_len > available_width {
144 let mut current_width = 0;
146 let truncated: String = text
147 .chars()
148 .take_while(|&c| {
149 let char_width = if (c as u32) <= 0x7F { 1 } else { 2 };
150 if current_width + char_width <= available_width {
151 current_width += char_width;
152 true
153 } else {
154 false
155 }
156 })
157 .collect();
158 let truncated_width = text_display_width(&truncated);
160 let padding_spaces = available_width.saturating_sub(truncated_width);
161 format!("| {}{} |", truncated, " ".repeat(padding_spaces))
162 } else {
163 let padded_text =
164 pad_text_to_width(text, available_width, TextAlignment::Left, ' ');
165 format!("| {padded_text} |")
166 }
167 }
168 }
169
170 fn draw_bottom_border(&self, width: usize) -> String {
172 if self.unicode_supported {
173 format!("╚{}╝", "═".repeat(width - 2))
174 } else {
175 format!("+{}+", "-".repeat(width - 2))
176 }
177 }
178}
179
180pub fn handle_current_command() -> Result<()> {
190 let storage = ConfigStorage::load()?;
191
192 println!("\n{}", "Current Configuration:".green().bold());
193 println!("Environment variable mode: configurations are set per-command execution");
194 println!("Use 'cc-switch use <alias>' to launch Claude with specific configuration");
195 println!("Use 'cc-switch use cc' to launch Claude with default settings");
196
197 let raw_mode_enabled = terminal::enable_raw_mode().is_ok();
199
200 if raw_mode_enabled {
201 let mut stdout = io::stdout();
202 if execute!(
203 stdout,
204 terminal::EnterAlternateScreen,
205 terminal::Clear(terminal::ClearType::All)
206 )
207 .is_ok()
208 {
209 let result = handle_main_menu_interactive(&mut stdout, &storage);
211
212 let _ = execute!(stdout, terminal::LeaveAlternateScreen);
214 let _ = terminal::disable_raw_mode();
215
216 return result;
217 } else {
218 let _ = terminal::disable_raw_mode();
220 }
221 }
222
223 handle_main_menu_simple(&storage)
225}
226
227fn handle_main_menu_interactive(stdout: &mut io::Stdout, storage: &ConfigStorage) -> Result<()> {
229 let menu_items = [
230 "Execute claude --dangerously-skip-permissions",
231 "Switch configuration",
232 "Exit",
233 ];
234 let mut selected_index = 0;
235
236 loop {
237 execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
239 execute!(stdout, crossterm::cursor::MoveTo(0, 0))?;
240
241 let border = BorderDrawing::new();
243 const MAIN_MENU_WIDTH: usize = 68;
244
245 println!(
246 "\r{}",
247 border.draw_top_border("Main Menu", MAIN_MENU_WIDTH).green()
248 );
249 println!(
250 "\r{}",
251 border
252 .draw_middle_line(
253 "↑↓/jk导航,1-9快选,E-编辑,R-官方,Q-退出,Enter确认,Esc取消",
254 MAIN_MENU_WIDTH
255 )
256 .green()
257 );
258 println!("\r{}", border.draw_bottom_border(MAIN_MENU_WIDTH).green());
259 println!();
260
261 for (index, item) in menu_items.iter().enumerate() {
263 if index == selected_index {
264 println!("\r> {} {}", "●".blue().bold(), item.blue().bold());
265 } else {
266 println!("\r {} {}", "○".dimmed(), item.dimmed());
267 }
268 }
269
270 stdout.flush()?;
272
273 let event = match event::read() {
275 Ok(event) => event,
276 Err(e) => {
277 let _ = execute!(stdout, terminal::LeaveAlternateScreen);
279 let _ = terminal::disable_raw_mode();
280 return Err(e.into());
281 }
282 };
283
284 match event {
285 Event::Key(KeyEvent {
286 code,
287 kind: KeyEventKind::Press,
288 ..
289 }) => {
290 match code {
291 KeyCode::Up => {
292 selected_index = selected_index.saturating_sub(1);
293 }
294 KeyCode::Down => {
295 if selected_index < menu_items.len() - 1 {
296 selected_index += 1;
297 }
298 }
299 KeyCode::Enter => {
300 let _ = execute!(stdout, terminal::LeaveAlternateScreen);
302 let _ = terminal::disable_raw_mode();
303
304 return handle_main_menu_action(selected_index, storage);
305 }
306 KeyCode::Esc => {
307 let _ = execute!(stdout, terminal::LeaveAlternateScreen);
309 let _ = terminal::disable_raw_mode();
310
311 println!("\nExiting...");
312 return Ok(());
313 }
314 _ => {}
315 }
316 }
317 Event::Key(_) => {} _ => {}
319 }
320 }
321}
322
323fn handle_main_menu_simple(storage: &ConfigStorage) -> Result<()> {
325 loop {
326 println!("\n{}", "Available Actions:".blue().bold());
327 println!("1. Execute claude --dangerously-skip-permissions");
328 println!("2. Switch configuration");
329 println!("3. Exit");
330
331 print!("\nPlease select an option (1-3): ");
332 io::stdout().flush().context("Failed to flush stdout")?;
333
334 let mut input = String::new();
335 io::stdin()
336 .read_line(&mut input)
337 .context("Failed to read input")?;
338
339 let choice = input.trim();
340
341 match choice {
342 "1" => return handle_main_menu_action(0, storage),
343 "2" => return handle_main_menu_action(1, storage),
344 "3" => return handle_main_menu_action(2, storage),
345 _ => {
346 println!("Invalid option. Please select 1-3.");
347 }
348 }
349 }
350}
351
352fn handle_main_menu_action(selected_index: usize, storage: &ConfigStorage) -> Result<()> {
354 match selected_index {
355 0 => {
356 println!("\nExecuting: claude --dangerously-skip-permissions");
357 execute_claude_command(true)?;
358 }
359 1 => {
360 handle_interactive_selection(storage)?;
362 }
363 2 => {
364 println!("Exiting...");
365 }
366 _ => {
367 println!("Invalid selection");
368 }
369 }
370 Ok(())
371}
372
373pub fn handle_interactive_selection(storage: &ConfigStorage) -> Result<()> {
381 if storage.configurations.is_empty() {
382 println!("No configurations available. Use 'add' command to create configurations first.");
383 return Ok(());
384 }
385
386 let mut configs: Vec<&Configuration> = storage.configurations.values().collect();
387 configs.sort_by(|a, b| a.alias_name.cmp(&b.alias_name));
388
389 let mut selected_index = 0;
390
391 let raw_mode_enabled = terminal::enable_raw_mode().is_ok();
393
394 if raw_mode_enabled {
395 let mut stdout = io::stdout();
396 if execute!(
397 stdout,
398 terminal::EnterAlternateScreen,
399 terminal::Clear(terminal::ClearType::All)
400 )
401 .is_ok()
402 {
403 let result = handle_full_interactive_menu(&mut stdout, &configs, &mut selected_index);
405
406 let _ = execute!(stdout, terminal::LeaveAlternateScreen);
408 let _ = terminal::disable_raw_mode();
409
410 return result;
411 } else {
412 let _ = terminal::disable_raw_mode();
414 }
415 }
416
417 handle_simple_interactive_menu(&configs, storage)
419}
420
421fn handle_full_interactive_menu(
423 stdout: &mut io::Stdout,
424 configs: &[&Configuration],
425 selected_index: &mut usize,
426) -> Result<()> {
427 if configs.is_empty() {
429 println!("\r{}", "No configurations available".yellow());
430 println!(
431 "\r{}",
432 "Use 'cc-switch add <alias> <token> <url>' to add configurations first.".dimmed()
433 );
434 println!("\r{}", "Press any key to continue...".dimmed());
435 let _ = event::read(); return Ok(());
437 }
438
439 const PAGE_SIZE: usize = 9; let total_pages = if configs.len() <= PAGE_SIZE {
443 1
444 } else {
445 configs.len().div_ceil(PAGE_SIZE)
446 };
447 let mut current_page = 0;
448
449 loop {
450 let start_idx = current_page * PAGE_SIZE;
452 let end_idx = std::cmp::min(start_idx + PAGE_SIZE, configs.len());
453 let page_configs = &configs[start_idx..end_idx];
454
455 execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
457 execute!(stdout, crossterm::cursor::MoveTo(0, 0))?;
458
459 let border = BorderDrawing::new();
461 const CONFIG_MENU_WIDTH: usize = 80;
464
465 println!(
466 "\r{}",
467 border
468 .draw_top_border("Select Configuration", CONFIG_MENU_WIDTH)
469 .green()
470 );
471 if total_pages > 1 {
472 println!(
473 "\r{}",
474 border
475 .draw_middle_line(
476 &format!("第 {} 页,共 {} 页", current_page + 1, total_pages),
477 CONFIG_MENU_WIDTH
478 )
479 .green()
480 );
481 println!(
482 "\r{}",
483 border
484 .draw_middle_line(
485 "↑↓/jk导航,1-9快选,E-编辑,N/P翻页,R-官方,Q-退出,Enter确认",
486 CONFIG_MENU_WIDTH
487 )
488 .green()
489 );
490 } else {
491 println!(
492 "\r{}",
493 border
494 .draw_middle_line(
495 "↑↓/jk导航,1-9快选,E-编辑,R-官方,Q-退出,Enter确认,Esc取消",
496 CONFIG_MENU_WIDTH
497 )
498 .green()
499 );
500 }
501 println!("\r{}", border.draw_bottom_border(CONFIG_MENU_WIDTH).green());
502 println!();
503
504 let official_index = 0;
506 if *selected_index == official_index {
507 println!(
508 "\r> {} {} {}",
509 "●".red().bold(),
510 "[R]".red().bold(),
511 "official".red().bold()
512 );
513 println!("\r Use official Claude API (no custom configuration)");
514 println!();
515 } else {
516 println!("\r {} {} {}", "○".red(), "[R]".red(), "official".red());
517 }
518
519 for (page_index, config) in page_configs.iter().enumerate() {
521 let actual_config_index = start_idx + page_index;
522 let display_number = page_index + 1; let actual_index = actual_config_index + 1; let number_label = format!("[{display_number}]");
525
526 if *selected_index == actual_index {
527 println!(
528 "\r> {} {} {}",
529 "●".blue().bold(),
530 number_label.blue().bold(),
531 config.alias_name.blue().bold()
532 );
533
534 let details = format_config_details(config, "\r ", false);
536 for detail_line in details {
537 println!("{detail_line}");
538 }
539 println!();
540 } else {
541 println!(
542 "\r {} {} {}",
543 "○".dimmed(),
544 number_label.dimmed(),
545 config.alias_name.dimmed()
546 );
547 }
548 }
549
550 let exit_index = configs.len() + 1;
552 if *selected_index == exit_index {
553 println!(
554 "\r> {} {} {}",
555 "●".yellow().bold(),
556 "[Q]".yellow().bold(),
557 "Exit".yellow().bold()
558 );
559 println!("\r Exit without making changes");
560 println!();
561 } else {
562 println!(
563 "\r {} {} {}",
564 "○".dimmed(),
565 "[Q]".dimmed(),
566 "Exit".dimmed()
567 );
568 }
569
570 if total_pages > 1 {
572 println!(
573 "\r{}",
574 format!(
575 "Page Navigation: [N]ext, [P]revious (第 {} 页,共 {} 页)",
576 current_page + 1,
577 total_pages
578 )
579 .dimmed()
580 );
581 }
582
583 stdout.flush()?;
585
586 let event = match event::read() {
588 Ok(event) => event,
589 Err(e) => {
590 let _ = execute!(stdout, terminal::LeaveAlternateScreen);
592 let _ = terminal::disable_raw_mode();
593 return Err(e.into());
594 }
595 };
596
597 match event {
598 Event::Key(KeyEvent {
599 code,
600 kind: KeyEventKind::Press,
601 ..
602 }) => match code {
603 KeyCode::Up | KeyCode::Char('k') | KeyCode::Char('K') => {
604 *selected_index = selected_index.saturating_sub(1);
605 }
606 KeyCode::Down | KeyCode::Char('j') | KeyCode::Char('J') => {
607 if *selected_index < configs.len() + 1 {
608 *selected_index += 1;
609 }
610 }
611 KeyCode::PageDown | KeyCode::Char('n') | KeyCode::Char('N') => {
612 if total_pages > 1 && current_page < total_pages - 1 {
613 current_page += 1;
614 let new_page_start_idx = current_page * PAGE_SIZE;
616 *selected_index = new_page_start_idx + 1; }
618 }
619 KeyCode::PageUp | KeyCode::Char('p') | KeyCode::Char('P') => {
620 if total_pages > 1 && current_page > 0 {
621 current_page -= 1;
622 let new_page_start_idx = current_page * PAGE_SIZE;
624 *selected_index = new_page_start_idx + 1; }
626 }
627 KeyCode::Enter => {
628 let _ = execute!(stdout, terminal::LeaveAlternateScreen);
630 let _ = terminal::disable_raw_mode();
631
632 return handle_selection_action(configs, *selected_index);
633 }
634 KeyCode::Esc => {
635 let _ = execute!(stdout, terminal::LeaveAlternateScreen);
637 let _ = terminal::disable_raw_mode();
638
639 println!("\nSelection cancelled");
640 return Ok(());
641 }
642 KeyCode::Char(c) if c.is_ascii_digit() => {
643 let digit = c.to_digit(10).unwrap() as usize;
644 if digit >= 1 && digit <= page_configs.len() {
646 let actual_config_index = start_idx + (digit - 1);
647 let selection_index = actual_config_index + 1; let _ = execute!(stdout, terminal::LeaveAlternateScreen);
651 let _ = terminal::disable_raw_mode();
652
653 return handle_selection_action(configs, selection_index);
654 }
655 }
657 KeyCode::Char('r') | KeyCode::Char('R') => {
658 let _ = execute!(stdout, terminal::LeaveAlternateScreen);
660 let _ = terminal::disable_raw_mode();
661
662 return handle_selection_action(configs, 0);
663 }
664 KeyCode::Char('e') | KeyCode::Char('E') => {
665 if *selected_index > 0 && *selected_index <= configs.len() {
667 let _ = execute!(stdout, terminal::LeaveAlternateScreen);
669 let _ = terminal::disable_raw_mode();
670
671 let config_index = *selected_index - 1; if let Err(e) = handle_config_edit(configs[config_index]) {
675 if e.downcast_ref::<EditModeError>()
677 == Some(&EditModeError::ReturnToMenu)
678 {
679 if execute!(
681 stdout,
682 terminal::EnterAlternateScreen,
683 terminal::Clear(terminal::ClearType::All)
684 )
685 .is_ok()
686 && terminal::enable_raw_mode().is_ok()
687 {
688 continue;
690 }
691 }
692 return Err(e);
694 }
695 }
696 }
698 KeyCode::Char('q') | KeyCode::Char('Q') => {
699 let _ = execute!(stdout, terminal::LeaveAlternateScreen);
701 let _ = terminal::disable_raw_mode();
702
703 return handle_selection_action(configs, configs.len() + 1);
704 }
705 _ => {}
706 },
707 Event::Key(_) => {} _ => {}
709 }
710 }
711}
712
713fn handle_simple_interactive_menu(
715 configs: &[&Configuration],
716 _storage: &ConfigStorage,
717) -> Result<()> {
718 const PAGE_SIZE: usize = 9; if configs.len() <= PAGE_SIZE {
722 return handle_simple_single_page_menu(configs);
723 }
724
725 let total_pages = configs.len().div_ceil(PAGE_SIZE);
727 let mut current_page = 0;
728
729 loop {
730 let start_idx = current_page * PAGE_SIZE;
732 let end_idx = std::cmp::min(start_idx + PAGE_SIZE, configs.len());
733 let page_configs = &configs[start_idx..end_idx];
734
735 println!("\n{}", "Available Configurations:".blue().bold());
736 if total_pages > 1 {
737 println!("第 {} 页,共 {} 页", current_page + 1, total_pages);
738 println!("使用 'n' 下一页, 'p' 上一页, 'r' 官方配置, 'q' 退出");
739 }
740 println!();
741
742 println!("{} {}", "[r]".red().bold(), "official".red());
744 println!(" Use official Claude API (no custom configuration)");
745 println!();
746
747 for (page_index, config) in page_configs.iter().enumerate() {
749 let display_number = page_index + 1;
750
751 println!(
752 "{}. {}",
753 format!("[{display_number}]").green().bold(),
754 config.alias_name.green()
755 );
756
757 let details = format_config_details(config, " ", true);
759 for detail_line in details {
760 println!("{detail_line}");
761 }
762 println!();
763 }
764
765 println!("{} {}", "[q]".yellow().bold(), "Exit".yellow());
767
768 if total_pages > 1 {
769 println!(
770 "\n页面导航: [n]下页, [p]上页 | 配置选择: [1-{}] | [e]编辑 | [r]官方 | [q]退出",
771 page_configs.len()
772 );
773 }
774
775 print!("\n请输入选择: ");
776 io::stdout().flush()?;
777
778 let mut input = String::new();
779 io::stdin().read_line(&mut input)?;
780 let choice = input.trim().to_lowercase();
781
782 match choice.as_str() {
783 "r" => {
784 println!("Using official Claude configuration");
786 return launch_claude_with_env(EnvironmentConfig::empty());
787 }
788 "e" => {
789 println!("编辑功能在交互式菜单中可用");
792 }
793 "q" => {
794 println!("Exiting...");
795 return Ok(());
796 }
797 "n" if total_pages > 1 && current_page < total_pages - 1 => {
798 current_page += 1;
799 continue;
800 }
801 "p" if total_pages > 1 && current_page > 0 => {
802 current_page -= 1;
803 continue;
804 }
805 digit_str => {
806 if let Ok(digit) = digit_str.parse::<usize>()
807 && digit >= 1
808 && digit <= page_configs.len()
809 {
810 let actual_config_index = start_idx + (digit - 1);
811 let selection_index = actual_config_index + 1; return handle_selection_action(configs, selection_index);
813 }
814 println!("无效选择,请重新输入");
815 }
816 }
817 }
818}
819
820fn handle_simple_single_page_menu(configs: &[&Configuration]) -> Result<()> {
822 println!("\n{}", "Available Configurations:".blue().bold());
823
824 println!("1. {}", "official".red());
826 println!(" Use official Claude API (no custom configuration)");
827 println!();
828
829 for (index, config) in configs.iter().enumerate() {
830 println!(
831 "{}. {}",
832 index + 2, config.alias_name.green()
834 );
835
836 let details = format_config_details(config, " ", true);
838 for detail_line in details {
839 println!("{detail_line}");
840 }
841 println!();
842 }
843
844 println!("{}. {}", configs.len() + 2, "Exit".yellow());
845
846 print!("\nSelect configuration (1-{}): ", configs.len() + 2);
847 io::stdout().flush()?;
848
849 let mut input = String::new();
850 io::stdin().read_line(&mut input)?;
851
852 match input.trim().parse::<usize>() {
853 Ok(1) => {
854 println!("Using official Claude configuration");
856 launch_claude_with_env(EnvironmentConfig::empty())
857 }
858 Ok(num) if num >= 2 && num <= configs.len() + 1 => {
859 handle_selection_action(configs, num - 2) }
861 Ok(num) if num == configs.len() + 2 => {
862 println!("Exiting...");
863 Ok(())
864 }
865 _ => {
866 println!("Invalid selection");
867 Ok(())
868 }
869 }
870}
871
872fn handle_selection_action(configs: &[&Configuration], selected_index: usize) -> Result<()> {
874 if selected_index == 0 {
875 println!("\nUsing official Claude configuration");
877 launch_claude_with_env(EnvironmentConfig::empty())
878 } else if selected_index <= configs.len() {
879 let config_index = selected_index - 1; let selected_config = configs[config_index].clone();
882 let env_config = EnvironmentConfig::from_config(&selected_config);
883
884 println!(
885 "\nSwitched to configuration '{}'",
886 selected_config.alias_name.green().bold()
887 );
888
889 let details = format_config_details(&selected_config, "", false);
891 for detail_line in details {
892 println!("{detail_line}");
893 }
894
895 launch_claude_with_env(env_config)
896 } else {
897 println!("\nExiting...");
899 Ok(())
900 }
901}
902
903fn launch_claude_with_env(env_config: EnvironmentConfig) -> Result<()> {
905 println!("\nWaiting 0.5 seconds before launching Claude...");
906 thread::sleep(Duration::from_millis(500));
907
908 println!("Launching Claude CLI...");
909
910 for (key, value) in env_config.as_env_tuples() {
912 unsafe {
913 std::env::set_var(&key, &value);
914 }
915 }
916
917 #[cfg(unix)]
919 {
920 use std::os::unix::process::CommandExt;
921 let error = Command::new("claude")
922 .arg("--dangerously-skip-permissions")
923 .exec();
924 anyhow::bail!("Failed to exec claude: {}", error);
926 }
927
928 #[cfg(not(unix))]
930 {
931 use std::process::Stdio;
932 let mut child = Command::new("claude")
933 .arg("--dangerously-skip-permissions")
934 .stdin(Stdio::inherit())
935 .stdout(Stdio::inherit())
936 .stderr(Stdio::inherit())
937 .spawn()
938 .context(
939 "Failed to launch Claude CLI. Make sure 'claude' command is available in PATH",
940 )?;
941
942 let status = child.wait()?;
943
944 if !status.success() {
945 anyhow::bail!("Claude CLI exited with error status: {}", status);
946 }
947 }
948}
949
950fn execute_claude_command(skip_permissions: bool) -> Result<()> {
955 println!("Launching Claude CLI...");
956
957 #[cfg(unix)]
959 {
960 use std::os::unix::process::CommandExt;
961 let mut command = Command::new("claude");
962 if skip_permissions {
963 command.arg("--dangerously-skip-permissions");
964 }
965
966 let error = command.exec();
967 anyhow::bail!("Failed to exec claude: {}", error);
969 }
970
971 #[cfg(not(unix))]
973 {
974 use std::process::Stdio;
975 let mut command = Command::new("claude");
976 if skip_permissions {
977 command.arg("--dangerously-skip-permissions");
978 }
979
980 command
981 .stdin(Stdio::inherit())
982 .stdout(Stdio::inherit())
983 .stderr(Stdio::inherit());
984
985 let mut child = command.spawn().context(
986 "Failed to launch Claude CLI. Make sure 'claude' command is available in PATH",
987 )?;
988
989 let status = child
990 .wait()
991 .context("Failed to wait for Claude CLI process")?;
992
993 if !status.success() {
994 anyhow::bail!("Claude CLI exited with error status: {}", status);
995 }
996 }
997}
998
999pub fn read_input(prompt: &str) -> Result<String> {
1007 print!("{prompt}");
1008 io::stdout().flush().context("Failed to flush stdout")?;
1009 let mut input = String::new();
1010 io::stdin()
1011 .read_line(&mut input)
1012 .context("Failed to read input")?;
1013 Ok(input.trim().to_string())
1014}
1015
1016pub fn read_sensitive_input(prompt: &str) -> Result<String> {
1024 print!("{prompt}");
1025 io::stdout().flush().context("Failed to flush stdout")?;
1026 let mut input = String::new();
1027 io::stdin()
1028 .read_line(&mut input)
1029 .context("Failed to read input")?;
1030 Ok(input.trim().to_string())
1031}
1032
1033fn format_config_details(config: &Configuration, indent: &str, _compact: bool) -> Vec<String> {
1046 let mut lines = Vec::new();
1047
1048 let terminal_width = get_terminal_width();
1050 let _available_width = terminal_width.saturating_sub(text_display_width(indent) + 8);
1051
1052 let token_label = "Token:";
1054 let url_label = "URL:";
1055 let model_label = "Model:";
1056 let small_model_label = "Small Fast Model:";
1057 let max_thinking_tokens_label = "Max Thinking Tokens:";
1058 let api_timeout_ms_label = "API Timeout (ms):";
1059 let disable_nonessential_traffic_label = "Disable Nonessential Traffic:";
1060 let default_sonnet_model_label = "Default Sonnet Model:";
1061 let default_opus_model_label = "Default Opus Model:";
1062 let default_haiku_model_label = "Default Haiku Model:";
1063
1064 let max_label_width = [
1066 token_label,
1067 url_label,
1068 model_label,
1069 small_model_label,
1070 max_thinking_tokens_label,
1071 api_timeout_ms_label,
1072 disable_nonessential_traffic_label,
1073 default_sonnet_model_label,
1074 default_opus_model_label,
1075 default_haiku_model_label,
1076 ]
1077 .iter()
1078 .map(|label| text_display_width(label))
1079 .max()
1080 .unwrap_or(0);
1081
1082 let token_line = format!(
1084 "{}{} {}",
1085 indent,
1086 pad_text_to_width(token_label, max_label_width, TextAlignment::Left, ' '),
1087 format_token_for_display(&config.token).dimmed()
1088 );
1089 lines.push(token_line);
1090
1091 let url_line = format!(
1093 "{}{} {}",
1094 indent,
1095 pad_text_to_width(url_label, max_label_width, TextAlignment::Left, ' '),
1096 config.url.cyan()
1097 );
1098 lines.push(url_line);
1099
1100 if let Some(model) = &config.model {
1102 let model_line = format!(
1103 "{}{} {}",
1104 indent,
1105 pad_text_to_width(model_label, max_label_width, TextAlignment::Left, ' '),
1106 model.yellow()
1107 );
1108 lines.push(model_line);
1109 }
1110
1111 if let Some(small_fast_model) = &config.small_fast_model {
1113 let small_model_line = format!(
1114 "{}{} {}",
1115 indent,
1116 pad_text_to_width(small_model_label, max_label_width, TextAlignment::Left, ' '),
1117 small_fast_model.yellow()
1118 );
1119 lines.push(small_model_line);
1120 }
1121
1122 if let Some(max_thinking_tokens) = config.max_thinking_tokens {
1124 let tokens_line = format!(
1125 "{}{} {}",
1126 indent,
1127 pad_text_to_width(
1128 max_thinking_tokens_label,
1129 max_label_width,
1130 TextAlignment::Left,
1131 ' '
1132 ),
1133 format!("{}", max_thinking_tokens).yellow()
1134 );
1135 lines.push(tokens_line);
1136 }
1137
1138 if let Some(api_timeout_ms) = config.api_timeout_ms {
1140 let timeout_line = format!(
1141 "{}{} {}",
1142 indent,
1143 pad_text_to_width(
1144 api_timeout_ms_label,
1145 max_label_width,
1146 TextAlignment::Left,
1147 ' '
1148 ),
1149 format!("{}", api_timeout_ms).yellow()
1150 );
1151 lines.push(timeout_line);
1152 }
1153
1154 if let Some(disable_flag) = config.claude_code_disable_nonessential_traffic {
1156 let flag_line = format!(
1157 "{}{} {}",
1158 indent,
1159 pad_text_to_width(
1160 disable_nonessential_traffic_label,
1161 max_label_width,
1162 TextAlignment::Left,
1163 ' '
1164 ),
1165 format!("{}", disable_flag).yellow()
1166 );
1167 lines.push(flag_line);
1168 }
1169
1170 if let Some(sonnet_model) = &config.anthropic_default_sonnet_model {
1172 let sonnet_line = format!(
1173 "{}{} {}",
1174 indent,
1175 pad_text_to_width(
1176 default_sonnet_model_label,
1177 max_label_width,
1178 TextAlignment::Left,
1179 ' '
1180 ),
1181 sonnet_model.yellow()
1182 );
1183 lines.push(sonnet_line);
1184 }
1185
1186 if let Some(opus_model) = &config.anthropic_default_opus_model {
1188 let opus_line = format!(
1189 "{}{} {}",
1190 indent,
1191 pad_text_to_width(
1192 default_opus_model_label,
1193 max_label_width,
1194 TextAlignment::Left,
1195 ' '
1196 ),
1197 opus_model.yellow()
1198 );
1199 lines.push(opus_line);
1200 }
1201
1202 if let Some(haiku_model) = &config.anthropic_default_haiku_model {
1204 let haiku_line = format!(
1205 "{}{} {}",
1206 indent,
1207 pad_text_to_width(
1208 default_haiku_model_label,
1209 max_label_width,
1210 TextAlignment::Left,
1211 ' '
1212 ),
1213 haiku_model.yellow()
1214 );
1215 lines.push(haiku_line);
1216 }
1217
1218 lines
1219}
1220
1221#[cfg(test)]
1222mod border_drawing_tests {
1223 use super::*;
1224
1225 #[test]
1226 fn test_border_drawing_unicode_support() {
1227 let _border = BorderDrawing::new();
1228 }
1230
1231 #[test]
1232 fn test_border_drawing_top_border() {
1233 let border = BorderDrawing {
1234 unicode_supported: true,
1235 };
1236 let result = border.draw_top_border("Test", 20);
1237 assert!(!result.is_empty());
1238 assert!(result.contains("Test"));
1239 }
1240
1241 #[test]
1242 fn test_border_drawing_ascii_fallback() {
1243 let border = BorderDrawing {
1244 unicode_supported: false,
1245 };
1246 let result = border.draw_top_border("Test", 20);
1247 assert!(!result.is_empty());
1248 assert!(result.contains("Test"));
1249 assert!(result.contains("+"));
1250 assert!(result.contains("-"));
1251 }
1252
1253 #[test]
1254 fn test_border_drawing_middle_line() {
1255 let border = BorderDrawing {
1256 unicode_supported: true,
1257 };
1258 let result = border.draw_middle_line("Test message", 30);
1259 assert!(!result.is_empty());
1260 assert!(result.contains("Test message"));
1261 }
1262
1263 #[test]
1264 fn test_border_drawing_bottom_border() {
1265 let border = BorderDrawing {
1266 unicode_supported: true,
1267 };
1268 let result = border.draw_bottom_border(20);
1269 assert!(!result.is_empty());
1270 }
1271
1272 #[test]
1273 fn test_border_drawing_width_consistency() {
1274 let border = BorderDrawing {
1275 unicode_supported: true,
1276 };
1277 let width = 30;
1278 let top = border.draw_top_border("Title", width);
1279 let middle = border.draw_middle_line("Content", width);
1280 let bottom = border.draw_bottom_border(width);
1281
1282 assert!(top.chars().count() >= width - 2);
1284 assert!(middle.chars().count() >= width - 2);
1285 assert!(bottom.chars().count() >= width - 2);
1286 }
1287}
1288
1289#[cfg(test)]
1290mod pagination_tests {
1291
1292 #[test]
1294 fn test_pagination_calculation() {
1295 const PAGE_SIZE: usize = 9;
1296
1297 assert_eq!(1_usize.div_ceil(PAGE_SIZE), 1); assert_eq!(9_usize.div_ceil(PAGE_SIZE), 1); assert_eq!(10_usize.div_ceil(PAGE_SIZE), 2); assert_eq!(18_usize.div_ceil(PAGE_SIZE), 2); assert_eq!(19_usize.div_ceil(PAGE_SIZE), 3); assert_eq!(27_usize.div_ceil(PAGE_SIZE), 3); assert_eq!(28_usize.div_ceil(PAGE_SIZE), 4); }
1308
1309 #[test]
1311 fn test_page_range_calculation() {
1312 const PAGE_SIZE: usize = 9;
1313
1314 let current_page = 0;
1316 let start_idx = current_page * PAGE_SIZE; let end_idx = std::cmp::min(start_idx + PAGE_SIZE, 15); assert_eq!(start_idx, 0);
1319 assert_eq!(end_idx, 9);
1320 assert_eq!(end_idx - start_idx, 9); let current_page = 1;
1324 let start_idx = current_page * PAGE_SIZE; let end_idx = std::cmp::min(start_idx + PAGE_SIZE, 15); assert_eq!(start_idx, 9);
1327 assert_eq!(end_idx, 15);
1328 assert_eq!(end_idx - start_idx, 6); let current_page = 0;
1332 let start_idx = current_page * PAGE_SIZE; let end_idx = std::cmp::min(start_idx + PAGE_SIZE, PAGE_SIZE); assert_eq!(start_idx, 0);
1335 assert_eq!(end_idx, 9);
1336 assert_eq!(end_idx - start_idx, 9); }
1338
1339 #[test]
1341 fn test_digit_mapping_to_config_index() {
1342 const PAGE_SIZE: usize = 9;
1343
1344 let current_page = 0;
1346 let start_idx = current_page * PAGE_SIZE; let digit = 1;
1350 let actual_config_index = start_idx + (digit - 1); assert_eq!(actual_config_index, 0);
1352
1353 let digit = 9;
1355 let actual_config_index = start_idx + (digit - 1); assert_eq!(actual_config_index, 8);
1357
1358 let current_page = 1;
1360 let start_idx = current_page * PAGE_SIZE; let digit = 1;
1364 let actual_config_index = start_idx + (digit - 1); assert_eq!(actual_config_index, 9);
1366
1367 let digit = 5;
1369 let actual_config_index = start_idx + (digit - 1); assert_eq!(actual_config_index, 13);
1371 }
1372
1373 #[test]
1375 fn test_selection_index_conversion() {
1376 const PAGE_SIZE: usize = 9;
1383
1384 let current_page = 0;
1386 let start_idx = current_page * PAGE_SIZE; let digit = 1;
1388 let actual_config_index = start_idx + (digit - 1); let selection_index = actual_config_index + 1; assert_eq!(selection_index, 1);
1391
1392 let current_page = 1;
1394 let start_idx = current_page * PAGE_SIZE; let digit = 1;
1396 let actual_config_index = start_idx + (digit - 1); let selection_index = actual_config_index + 1; assert_eq!(selection_index, 10);
1399 }
1400
1401 #[test]
1403 fn test_page_navigation_bounds() {
1404 const PAGE_SIZE: usize = 9;
1405 let total_configs: usize = 25; let total_pages = total_configs.div_ceil(PAGE_SIZE); assert_eq!(total_pages, 3);
1408
1409 let mut current_page = 0;
1411 if current_page > 0 {
1412 current_page -= 1;
1413 }
1414 assert_eq!(current_page, 0); let mut current_page = total_pages - 1; if current_page < total_pages - 1 {
1419 current_page += 1;
1420 }
1421 assert_eq!(current_page, 2); let mut current_page = 1;
1425
1426 if current_page < total_pages - 1 {
1428 current_page += 1;
1429 }
1430 assert_eq!(current_page, 2);
1431
1432 if current_page > 0 {
1434 current_page = current_page.saturating_sub(1);
1435 }
1436 assert_eq!(current_page, 1);
1437 }
1438
1439 #[test]
1441 fn test_digit_key_boundary_conditions() {
1442 const PAGE_SIZE: usize = 9;
1443
1444 let digit = 0;
1446 assert!(digit < 1, "Digit 0 should be less than 1 and ignored");
1447
1448 let configs_len = 5; let page_configs_len = std::cmp::min(PAGE_SIZE, configs_len); let digit = 9; assert!(
1453 digit > page_configs_len,
1454 "Digit 9 should be beyond available configs (5) and ignored"
1455 );
1456
1457 for digit in 1..=page_configs_len {
1459 assert!(
1460 digit >= 1 && digit <= page_configs_len,
1461 "Digit {} should be valid",
1462 digit
1463 );
1464 }
1465 }
1466
1467 #[test]
1469 fn test_empty_configs_handling() {
1470 let empty_configs: Vec<String> = Vec::new();
1471 assert!(
1472 empty_configs.is_empty(),
1473 "Empty config list should be properly detected"
1474 );
1475
1476 let configs_len = empty_configs.len(); assert_eq!(configs_len, 0, "Empty configs should have length 0");
1479
1480 }
1483
1484 #[test]
1486 fn test_page_navigation_boundaries() {
1487 const PAGE_SIZE: usize = 9;
1488 let total_configs: usize = 20; let total_pages = total_configs.div_ceil(PAGE_SIZE); let mut current_page = 0;
1493 let original_page = current_page;
1494
1495 if current_page > 0 {
1497 current_page -= 1;
1498 }
1499 assert_eq!(
1500 current_page, original_page,
1501 "First page should not navigate to previous"
1502 );
1503
1504 let mut current_page = total_pages - 1; let original_page = current_page;
1507
1508 if current_page < total_pages - 1 {
1510 current_page += 1;
1511 }
1512 assert_eq!(
1513 current_page, original_page,
1514 "Last page should not navigate to next"
1515 );
1516
1517 let mut current_page = 1; if current_page < total_pages - 1 {
1522 current_page += 1;
1523 }
1524 assert_eq!(current_page, 2, "Should navigate to next page");
1525
1526 if current_page > 0 {
1528 current_page = current_page.saturating_sub(1);
1529 }
1530 assert_eq!(current_page, 1, "Should navigate to previous page");
1531 }
1532
1533 #[test]
1535 fn test_j_key_navigation() {
1536 let mut selected_index: usize = 0;
1537 let configs_len = 5; if selected_index < configs_len + 1 {
1542 selected_index += 1;
1543 }
1544 assert_eq!(selected_index, 1, "j key should move selection down by one");
1545
1546 selected_index = configs_len + 1;
1548 let original_index = selected_index;
1549 if selected_index < configs_len + 1 {
1550 selected_index += 1;
1551 }
1552 assert_eq!(
1553 selected_index, original_index,
1554 "j key should not move beyond bottom boundary"
1555 );
1556 }
1557
1558 #[test]
1560 fn test_k_key_navigation() {
1561 let mut selected_index: usize = 5;
1562
1563 selected_index = selected_index.saturating_sub(1);
1566 assert_eq!(selected_index, 4, "k key should move selection up by one");
1567
1568 selected_index = 0;
1570 let original_index = selected_index;
1571 selected_index = selected_index.saturating_sub(1);
1572 assert_eq!(
1573 selected_index, original_index,
1574 "k key should not move beyond top boundary"
1575 );
1576 }
1577
1578 #[test]
1580 fn test_jk_key_boundary_conditions() {
1581 const CONFIGS_LEN: usize = 5;
1582
1583 let mut selected_index: usize = CONFIGS_LEN + 1; let original_index = selected_index;
1586 if selected_index < CONFIGS_LEN + 1 {
1587 selected_index += 1; }
1589 assert_eq!(
1590 selected_index, original_index,
1591 "j key should respect bottom boundary like Down arrow"
1592 );
1593
1594 let mut selected_index: usize = 0; let original_index = selected_index;
1597 selected_index = selected_index.saturating_sub(1); assert_eq!(
1599 selected_index, original_index,
1600 "k key should respect top boundary like Up arrow"
1601 );
1602 }
1603}
1604
1605#[derive(Debug, PartialEq)]
1607enum EditModeError {
1608 ReturnToMenu,
1609}
1610
1611impl std::fmt::Display for EditModeError {
1612 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1613 match self {
1614 EditModeError::ReturnToMenu => write!(f, "return_to_menu"),
1615 }
1616 }
1617}
1618
1619impl std::error::Error for EditModeError {}
1620
1621fn handle_config_edit(config: &Configuration) -> Result<()> {
1623 println!("\n{}", "配置编辑模式".green().bold());
1624 println!("{}", "===================".green());
1625 println!("正在编辑配置: {}", config.alias_name.cyan().bold());
1626 println!();
1627
1628 let mut editing_config = config.clone();
1630 let original_alias = config.alias_name.clone();
1631
1632 loop {
1633 display_edit_menu(&editing_config);
1635
1636 println!("\n{}", "提示: 可使用大小写字母".dimmed());
1638 print!("请选择要编辑的字段 (1-9, A-B), 或输入 S 保存, Q 返回上一级菜单: ");
1639 io::stdout().flush()?;
1640
1641 let mut input = String::new();
1642 io::stdin().read_line(&mut input)?;
1643 let input = input.trim();
1644
1645 match input {
1647 "1" => edit_field_alias(&mut editing_config)?,
1648 "2" => edit_field_token(&mut editing_config)?,
1649 "3" => edit_field_url(&mut editing_config)?,
1650 "4" => edit_field_model(&mut editing_config)?,
1651 "5" => edit_field_small_fast_model(&mut editing_config)?,
1652 "6" => edit_field_max_thinking_tokens(&mut editing_config)?,
1653 "7" => edit_field_api_timeout_ms(&mut editing_config)?,
1654 "8" => edit_field_claude_code_disable_nonessential_traffic(&mut editing_config)?,
1655 "9" => edit_field_anthropic_default_sonnet_model(&mut editing_config)?,
1656 "10" | "a" | "A" => edit_field_anthropic_default_opus_model(&mut editing_config)?,
1657 "11" | "b" | "B" => edit_field_anthropic_default_haiku_model(&mut editing_config)?,
1658 "s" | "S" => {
1659 return save_configuration_changes(&original_alias, &editing_config);
1661 }
1662 "q" | "Q" => {
1663 println!("\n{}", "返回上一级菜单".blue());
1664 return Err(EditModeError::ReturnToMenu.into());
1665 }
1666 _ => {
1667 println!("{}", "无效选择,请重试".red());
1668 }
1669 }
1670 }
1671}
1672
1673fn display_edit_menu(config: &Configuration) {
1675 println!("\n{}", "当前配置值:".blue().bold());
1676 println!("{}", "─────────────────────────".blue());
1677
1678 println!("1. 别名 (alias_name): {}", config.alias_name.green());
1679
1680 println!(
1681 "2. 令牌 (ANTHROPIC_AUTH_TOKEN): {}",
1682 format_token_for_display(&config.token).green()
1683 );
1684
1685 println!("3. URL (ANTHROPIC_BASE_URL): {}", config.url.green());
1686
1687 println!(
1688 "4. 模型 (ANTHROPIC_MODEL): {}",
1689 config.model.as_deref().unwrap_or("[未设置]").green()
1690 );
1691
1692 println!(
1693 "5. 快速模型 (ANTHROPIC_SMALL_FAST_MODEL): {}",
1694 config
1695 .small_fast_model
1696 .as_deref()
1697 .unwrap_or("[未设置]")
1698 .green()
1699 );
1700
1701 println!(
1702 "6. 最大思考令牌数 (ANTHROPIC_MAX_THINKING_TOKENS): {}",
1703 config
1704 .max_thinking_tokens
1705 .map(|t| t.to_string())
1706 .unwrap_or("[未设置]".to_string())
1707 .green()
1708 );
1709
1710 println!(
1711 "7. API超时时间 (API_TIMEOUT_MS): {}",
1712 config
1713 .api_timeout_ms
1714 .map(|t| t.to_string())
1715 .unwrap_or("[未设置]".to_string())
1716 .green()
1717 );
1718
1719 println!(
1720 "8. 禁用非必要流量 (CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC): {}",
1721 config
1722 .claude_code_disable_nonessential_traffic
1723 .map(|t| t.to_string())
1724 .unwrap_or("[未设置]".to_string())
1725 .green()
1726 );
1727
1728 println!(
1729 "9. 默认 Sonnet 模型 (ANTHROPIC_DEFAULT_SONNET_MODEL): {}",
1730 config
1731 .anthropic_default_sonnet_model
1732 .as_deref()
1733 .unwrap_or("[未设置]")
1734 .green()
1735 );
1736
1737 println!(
1738 "A. 默认 Opus 模型 (ANTHROPIC_DEFAULT_OPUS_MODEL): {}",
1739 config
1740 .anthropic_default_opus_model
1741 .as_deref()
1742 .unwrap_or("[未设置]")
1743 .green()
1744 );
1745
1746 println!(
1747 "B. 默认 Haiku 模型 (ANTHROPIC_DEFAULT_HAIKU_MODEL): {}",
1748 config
1749 .anthropic_default_haiku_model
1750 .as_deref()
1751 .unwrap_or("[未设置]")
1752 .green()
1753 );
1754
1755 println!("{}", "─────────────────────────".blue());
1756 println!(
1757 "S. {} | Q. {}",
1758 "保存更改".green().bold(),
1759 "返回上一级菜单".blue()
1760 );
1761}
1762
1763fn edit_field_alias(config: &mut Configuration) -> Result<()> {
1765 println!("\n编辑别名:");
1766 println!("当前值: {}", config.alias_name.cyan());
1767 print!("新值 (回车保持不变): ");
1768 io::stdout().flush()?;
1769
1770 let mut input = String::new();
1771 io::stdin().read_line(&mut input)?;
1772 let input = input.trim();
1773
1774 if !input.is_empty() {
1775 if input.contains(char::is_whitespace) {
1777 println!("{}", "错误: 别名不能包含空白字符".red());
1778 return Ok(());
1779 }
1780 if input == "cc" {
1781 println!("{}", "错误: 'cc' 是保留名称".red());
1782 return Ok(());
1783 }
1784
1785 config.alias_name = input.to_string();
1786 println!("别名已更新为: {}", input.green());
1787 }
1788 Ok(())
1789}
1790
1791fn edit_field_token(config: &mut Configuration) -> Result<()> {
1793 println!("\n编辑令牌:");
1794 println!("当前值: {}", format_token_for_display(&config.token).cyan());
1795 print!("新值 (回车保持不变): ");
1796 io::stdout().flush()?;
1797
1798 let mut input = String::new();
1799 io::stdin().read_line(&mut input)?;
1800 let input = input.trim();
1801
1802 if !input.is_empty() {
1803 config.token = input.to_string();
1804 println!("{}", "令牌已更新".green());
1805 }
1806 Ok(())
1807}
1808
1809fn edit_field_url(config: &mut Configuration) -> Result<()> {
1811 println!("\n编辑 URL:");
1812 println!("当前值: {}", config.url.cyan());
1813 print!("新值 (回车保持不变): ");
1814 io::stdout().flush()?;
1815
1816 let mut input = String::new();
1817 io::stdin().read_line(&mut input)?;
1818 let input = input.trim();
1819
1820 if !input.is_empty() {
1821 config.url = input.to_string();
1822 println!("URL 已更新为: {}", input.green());
1823 }
1824 Ok(())
1825}
1826
1827fn edit_field_model(config: &mut Configuration) -> Result<()> {
1829 println!("\n编辑模型:");
1830 println!(
1831 "当前值: {}",
1832 config.model.as_deref().unwrap_or("[未设置]").cyan()
1833 );
1834 print!("新值 (回车保持不变,输入空格清除): ");
1835 io::stdout().flush()?;
1836
1837 let mut input = String::new();
1838 io::stdin().read_line(&mut input)?;
1839 let input = input.trim();
1840
1841 if !input.is_empty() {
1842 if input == " " {
1843 config.model = None;
1844 println!("{}", "模型已清除".green());
1845 } else {
1846 config.model = Some(input.to_string());
1847 println!("模型已更新为: {}", input.green());
1848 }
1849 }
1850 Ok(())
1851}
1852
1853fn edit_field_small_fast_model(config: &mut Configuration) -> Result<()> {
1855 println!("\n编辑快速模型:");
1856 println!(
1857 "当前值: {}",
1858 config
1859 .small_fast_model
1860 .as_deref()
1861 .unwrap_or("[未设置]")
1862 .cyan()
1863 );
1864 print!("新值 (回车保持不变,输入空格清除): ");
1865 io::stdout().flush()?;
1866
1867 let mut input = String::new();
1868 io::stdin().read_line(&mut input)?;
1869 let input = input.trim();
1870
1871 if !input.is_empty() {
1872 if input == " " {
1873 config.small_fast_model = None;
1874 println!("{}", "快速模型已清除".green());
1875 } else {
1876 config.small_fast_model = Some(input.to_string());
1877 println!("快速模型已更新为: {}", input.green());
1878 }
1879 }
1880 Ok(())
1881}
1882
1883fn edit_field_max_thinking_tokens(config: &mut Configuration) -> Result<()> {
1885 println!("\n编辑最大思考令牌数:");
1886 println!(
1887 "当前值: {}",
1888 config
1889 .max_thinking_tokens
1890 .map(|t| t.to_string())
1891 .unwrap_or("[未设置]".to_string())
1892 .cyan()
1893 );
1894 print!("新值 (回车保持不变,输入 0 清除): ");
1895 io::stdout().flush()?;
1896
1897 let mut input = String::new();
1898 io::stdin().read_line(&mut input)?;
1899 let input = input.trim();
1900
1901 if !input.is_empty() {
1902 if input == "0" {
1903 config.max_thinking_tokens = None;
1904 println!("{}", "最大思考令牌数已清除".green());
1905 } else if let Ok(tokens) = input.parse::<u32>() {
1906 config.max_thinking_tokens = Some(tokens);
1907 println!("最大思考令牌数已更新为: {}", tokens.to_string().green());
1908 } else {
1909 println!("{}", "错误: 请输入有效的数字".red());
1910 }
1911 }
1912 Ok(())
1913}
1914
1915fn edit_field_api_timeout_ms(config: &mut Configuration) -> Result<()> {
1917 println!("\n编辑 API 超时时间 (毫秒):");
1918 println!(
1919 "当前值: {}",
1920 config
1921 .api_timeout_ms
1922 .map(|t| t.to_string())
1923 .unwrap_or("[未设置]".to_string())
1924 .cyan()
1925 );
1926 print!("新值 (回车保持不变,输入 0 清除): ");
1927 io::stdout().flush()?;
1928
1929 let mut input = String::new();
1930 io::stdin().read_line(&mut input)?;
1931 let input = input.trim();
1932
1933 if !input.is_empty() {
1934 if input == "0" {
1935 config.api_timeout_ms = None;
1936 println!("{}", "API 超时时间已清除".green());
1937 } else if let Ok(timeout) = input.parse::<u32>() {
1938 config.api_timeout_ms = Some(timeout);
1939 println!("API 超时时间已更新为: {}", timeout.to_string().green());
1940 } else {
1941 println!("{}", "错误: 请输入有效的数字".red());
1942 }
1943 }
1944 Ok(())
1945}
1946
1947fn edit_field_claude_code_disable_nonessential_traffic(config: &mut Configuration) -> Result<()> {
1949 println!("\n编辑禁用非必要流量标志:");
1950 println!(
1951 "当前值: {}",
1952 config
1953 .claude_code_disable_nonessential_traffic
1954 .map(|t| t.to_string())
1955 .unwrap_or("[未设置]".to_string())
1956 .cyan()
1957 );
1958 print!("新值 (回车保持不变,输入 0 清除): ");
1959 io::stdout().flush()?;
1960
1961 let mut input = String::new();
1962 io::stdin().read_line(&mut input)?;
1963 let input = input.trim();
1964
1965 if !input.is_empty() {
1966 if input == "0" {
1967 config.claude_code_disable_nonessential_traffic = None;
1968 println!("{}", "禁用非必要流量标志已清除".green());
1969 } else if let Ok(flag) = input.parse::<u32>() {
1970 config.claude_code_disable_nonessential_traffic = Some(flag);
1971 println!("禁用非必要流量标志已更新为: {}", flag.to_string().green());
1972 } else {
1973 println!("{}", "错误: 请输入有效的数字".red());
1974 }
1975 }
1976 Ok(())
1977}
1978
1979fn edit_field_anthropic_default_sonnet_model(config: &mut Configuration) -> Result<()> {
1981 println!("\n编辑默认 Sonnet 模型:");
1982 println!(
1983 "当前值: {}",
1984 config
1985 .anthropic_default_sonnet_model
1986 .as_deref()
1987 .unwrap_or("[未设置]")
1988 .cyan()
1989 );
1990 print!("新值 (回车保持不变,输入空格清除): ");
1991 io::stdout().flush()?;
1992
1993 let mut input = String::new();
1994 io::stdin().read_line(&mut input)?;
1995 let input = input.trim();
1996
1997 if !input.is_empty() {
1998 if input == " " {
1999 config.anthropic_default_sonnet_model = None;
2000 println!("{}", "默认 Sonnet 模型已清除".green());
2001 } else {
2002 config.anthropic_default_sonnet_model = Some(input.to_string());
2003 println!("默认 Sonnet 模型已更新为: {}", input.green());
2004 }
2005 }
2006 Ok(())
2007}
2008
2009fn edit_field_anthropic_default_opus_model(config: &mut Configuration) -> Result<()> {
2011 println!("\n编辑默认 Opus 模型:");
2012 println!(
2013 "当前值: {}",
2014 config
2015 .anthropic_default_opus_model
2016 .as_deref()
2017 .unwrap_or("[未设置]")
2018 .cyan()
2019 );
2020 print!("新值 (回车保持不变,输入空格清除): ");
2021 io::stdout().flush()?;
2022
2023 let mut input = String::new();
2024 io::stdin().read_line(&mut input)?;
2025 let input = input.trim();
2026
2027 if !input.is_empty() {
2028 if input == " " {
2029 config.anthropic_default_opus_model = None;
2030 println!("{}", "默认 Opus 模型已清除".green());
2031 } else {
2032 config.anthropic_default_opus_model = Some(input.to_string());
2033 println!("默认 Opus 模型已更新为: {}", input.green());
2034 }
2035 }
2036 Ok(())
2037}
2038
2039fn edit_field_anthropic_default_haiku_model(config: &mut Configuration) -> Result<()> {
2041 println!("\n编辑默认 Haiku 模型:");
2042 println!(
2043 "当前值: {}",
2044 config
2045 .anthropic_default_haiku_model
2046 .as_deref()
2047 .unwrap_or("[未设置]")
2048 .cyan()
2049 );
2050 print!("新值 (回车保持不变,输入空格清除): ");
2051 io::stdout().flush()?;
2052
2053 let mut input = String::new();
2054 io::stdin().read_line(&mut input)?;
2055 let input = input.trim();
2056
2057 if !input.is_empty() {
2058 if input == " " {
2059 config.anthropic_default_haiku_model = None;
2060 println!("{}", "默认 Haiku 模型已清除".green());
2061 } else {
2062 config.anthropic_default_haiku_model = Some(input.to_string());
2063 println!("默认 Haiku 模型已更新为: {}", input.green());
2064 }
2065 }
2066 Ok(())
2067}
2068
2069fn save_configuration_changes(original_alias: &str, new_config: &Configuration) -> Result<()> {
2071 let mut storage = ConfigStorage::load()?;
2073
2074 if original_alias != new_config.alias_name
2076 && storage.get_configuration(&new_config.alias_name).is_some()
2077 {
2078 println!("\n{}", "别名冲突!".red().bold());
2079 println!("配置 '{}' 已存在", new_config.alias_name.yellow());
2080 print!("是否覆盖现有配置? (y/N): ");
2081 io::stdout().flush()?;
2082
2083 let mut input = String::new();
2084 io::stdin().read_line(&mut input)?;
2085 let input = input.trim().to_lowercase();
2086
2087 if input != "y" && input != "yes" {
2088 println!("{}", "编辑已取消".yellow());
2089 return Ok(());
2090 }
2091 }
2092
2093 storage.update_configuration(original_alias, new_config.clone())?;
2095 storage.save()?;
2096
2097 println!("\n{}", "配置已成功保存!".green().bold());
2098
2099 Ok(())
2100}