LLM支持优化

This commit is contained in:
2026-05-26 17:43:42 +08:00
parent a08bc809bb
commit 4331b9306e
26 changed files with 2309 additions and 669 deletions

View File

@@ -55,6 +55,10 @@ pub struct ChangelogCommand {
#[arg(long)]
dry_run: bool,
/// Enable thinking mode for this changelog (override config)
#[arg(long)]
think: bool,
/// Skip interactive prompts
#[arg(short = 'y', long)]
yes: bool,
@@ -74,8 +78,7 @@ impl ChangelogCommand {
// Initialize changelog if requested
if self.init {
let path = self.output.as_ref()
.map(|p| p.clone())
let path = self.output.clone()
.unwrap_or_else(|| PathBuf::from(&config.changelog.path));
init_changelog(&path)?;
@@ -84,8 +87,7 @@ impl ChangelogCommand {
}
// Determine output path
let output_path = self.output.as_ref()
.map(|p| p.clone())
let output_path = self.output.clone()
.unwrap_or_else(|| PathBuf::from(&config.changelog.path));
// Determine format
@@ -148,7 +150,7 @@ impl ChangelogCommand {
println!("{}", "".repeat(60));
let confirm = Confirm::new()
.with_prompt(&messages.write_to_file(&format!("{:?}", output_path)))
.with_prompt(messages.write_to_file(&format!("{:?}", output_path)))
.default(true)
.interact()?;
@@ -208,7 +210,7 @@ impl ChangelogCommand {
println!("{}", messages.ai_generating_changelog());
let generator = ContentGenerator::new(&manager).await?;
let generator = ContentGenerator::new_with_think(&manager, self.think).await?;
generator.generate_changelog_entry(version, commits, language).await
}
@@ -237,7 +239,8 @@ impl ChangelogCommand {
}
fn translate_changelog_categories(&self, changelog: &str, language: Language) -> String {
let translated = changelog
changelog
.lines()
.map(|line| {
if line.starts_with("## ") || line.starts_with("### ") {
@@ -253,7 +256,6 @@ impl ChangelogCommand {
}
})
.collect::<Vec<_>>()
.join("\n");
translated
.join("\n")
}
}

View File

@@ -71,6 +71,10 @@ pub struct CommitCommand {
#[arg(long)]
no_verify: bool,
/// Enable thinking mode for this commit (override config)
#[arg(short = 't', long)]
think: bool,
/// Skip interactive prompts
#[arg(short = 'y', long)]
yes: bool,
@@ -198,7 +202,7 @@ impl CommitCommand {
if let Some(commit_oid) = result {
println!("{} {}", messages.commit_created().green().bold(), commit_oid.to_string()[..8].to_string().cyan());
} else {
println!("{} {}", messages.commit_amended().green().bold(), "successfully");
println!("{} successfully", messages.commit_amended().green().bold());
}
// Push after commit if requested or ask user
@@ -258,7 +262,7 @@ impl CommitCommand {
async fn generate_commit(&self, repo: &GitRepo, format: CommitFormat, messages: &Messages) -> Result<String> {
let manager = ConfigManager::new()?;
let generator = ContentGenerator::new(&manager).await
let generator = ContentGenerator::new_with_think(&manager, self.think).await
.context("Failed to initialize LLM. Use --manual for manual commit.")?;
println!("{}", messages.ai_analyzing());

View File

@@ -393,8 +393,15 @@ impl ConfigCommand {
let timeout: u64 = value.parse()?;
manager.config_mut().llm.timeout = timeout;
}
"llm.thinking_enabled" => {
manager.config_mut().llm.thinking_enabled = value == "true";
}
"llm.thinking_budget_tokens" => {
let budget: u32 = value.parse()?;
manager.config_mut().llm.thinking_budget_tokens = Some(budget);
}
"llm.api_key_storage" => {
let valid_values = vec!["keyring", "config", "environment"];
let valid_values = ["keyring", "config", "environment"];
if !valid_values.contains(&value) {
bail!("Invalid value: {}. Use: {}", value, valid_values.join(", "));
}
@@ -433,6 +440,8 @@ impl ConfigCommand {
"llm.max_tokens" => config.llm.max_tokens.to_string(),
"llm.temperature" => config.llm.temperature.to_string(),
"llm.timeout" => config.llm.timeout.to_string(),
"llm.thinking_enabled" => config.llm.thinking_enabled.to_string(),
"llm.thinking_budget_tokens" => config.llm.thinking_budget_tokens.map(|v| v.to_string()).unwrap_or_else(|| "none".to_string()),
"llm.api_key_storage" => config.llm.api_key_storage.clone(),
"commit.format" => config.commit.format.to_string(),
"commit.auto_generate" => config.commit.auto_generate.to_string(),
@@ -681,15 +690,13 @@ impl ConfigCommand {
l.to_string()
} else {
println!("{}", "Select Output Language:".bold());
let languages = vec![
("en", "English"),
let languages = [("en", "English"),
("zh", "中文"),
("ja", "日本語"),
("ko", "한국어"),
("es", "Español"),
("fr", "Français"),
("de", "Deutsch"),
];
("de", "Deutsch")];
let lang_names: Vec<&str> = languages.iter().map(|(_, n)| *n).collect();
let idx = Select::new()
@@ -879,8 +886,8 @@ impl ConfigCommand {
manager.import(&config_content)?;
manager.save()?;
if let (Some(pats), Some(pwd)) = (encrypted_pats, pwd) {
if !pats.is_empty() {
if let (Some(pats), Some(pwd)) = (encrypted_pats, pwd)
&& !pats.is_empty() {
println!();
println!("{}", "Importing Personal Access Tokens...".bold());
@@ -951,7 +958,6 @@ impl ConfigCommand {
println!("{} {} token(s) failed to import", "".yellow(), failed_count);
}
}
}
println!("{} Configuration imported from {}", "".green(), file);
Ok(())
@@ -969,31 +975,35 @@ impl ConfigCommand {
println!("Ollama models (local):");
println!(" llama2, llama2-uncensored, llama2:13b");
println!(" codellama, codellama:34b");
println!(" mistral, mixtral");
println!(" phi, gemma");
println!(" mistral, mixtral, phi, gemma");
println!("\nRun 'ollama list' to see installed models");
}
"openai" => {
println!("OpenAI models:");
println!(" gpt-4, gpt-4-turbo, gpt-4o");
println!(" gpt-3.5-turbo, gpt-3.5-turbo-16k");
println!(" o-series (reasoning): o4-mini, o3, o3-mini, o1, o1-mini, o1-pro");
println!(" GPT-4: gpt-4.1, gpt-4.1-mini, gpt-4.1-nano, gpt-4o, gpt-4o-mini");
println!(" Legacy: gpt-4-turbo, gpt-4, gpt-3.5-turbo");
println!("\nUse --think/-t with o-series models for reasoning mode.");
}
"anthropic" => {
println!("Anthropic Claude models:");
println!(" claude-3-opus-20240229");
println!(" claude-3-sonnet-20240229");
println!(" claude-3-haiku-20240307");
println!(" Claude 4 (thinking): claude-opus-4-7, claude-sonnet-4-6, claude-haiku-4-5");
println!(" Claude 3.5: claude-3-opus-20240229, claude-3-sonnet-20240229, claude-3-haiku-20240307");
println!(" Legacy: claude-2.1, claude-2.0, claude-instant-1.2");
println!("\nUse --think/-t with Claude 4 models for extended thinking.");
}
"kimi" => {
println!("Kimi (Moonshot AI) models:");
println!(" moonshot-v1-8k");
println!(" moonshot-v1-32k");
println!(" moonshot-v1-128k");
println!(" K2 (thinking): kimi-k2.6, kimi-k2.5, kimi-k2-thinking, kimi-k2-thinking-turbo");
println!(" K2 instruct: kimi-k2-instruct, kimi-k2-instruct-0905");
println!(" Legacy: moonshot-v1-8k, moonshot-v1-32k, moonshot-v1-128k");
println!("\nUse --think/-t with K2 models for thinking mode.");
}
"deepseek" => {
println!("DeepSeek models:");
println!(" deepseek-chat");
println!(" deepseek-reasoner");
println!(" V4: deepseek-v4-flash, deepseek-v4-pro");
println!(" Legacy: deepseek-chat, deepseek-reasoner (deprecated 2026-07-24)");
println!("\nUse --think/-t for reasoning mode.");
}
"openrouter" => {
println!("OpenRouter models (examples):");

View File

@@ -102,15 +102,13 @@ impl InitCommand {
println!("\n{}", messages.setup_profile().bold());
println!("\n{}", messages.select_output_language().bold());
let languages = vec![
Language::English,
let languages = [Language::English,
Language::Chinese,
Language::Japanese,
Language::Korean,
Language::Spanish,
Language::French,
Language::German,
];
Language::German];
let language_names: Vec<String> = languages.iter().map(|l| l.display_name().to_string()).collect();
let language_idx = Select::new()
.items(&language_names)
@@ -297,12 +295,11 @@ impl InitCommand {
manager.set_llm_model(model);
manager.set_llm_base_url(base_url);
if let Some(key) = api_key {
if provider_needs_api_key(&provider) {
if let Some(key) = api_key
&& provider_needs_api_key(&provider) {
manager.set_api_key(&key)?;
println!("\n{} {}", "".green(), "API key stored securely in system keyring.".green());
}
}
Ok(())
}

View File

@@ -260,7 +260,7 @@ impl ProfileCommand {
}
let confirm = Confirm::new()
.with_prompt(&format!("Are you sure you want to remove profile '{}'?", name))
.with_prompt(format!("Are you sure you want to remove profile '{}'?", name))
.default(false)
.interact()?;
@@ -564,7 +564,7 @@ impl ProfileCommand {
if manager.has_profile(&profile_name) {
let overwrite = Confirm::new()
.with_prompt(&format!("Profile '{}' already exists. Overwrite?", profile_name))
.with_prompt(format!("Profile '{}' already exists. Overwrite?", profile_name))
.default(false)
.interact()?;
if !overwrite {
@@ -575,7 +575,7 @@ impl ProfileCommand {
let description: String = Input::new()
.with_prompt("Description (optional)")
.default(format!("Imported from existing git config"))
.default("Imported from existing git config".to_string())
.allow_empty(true)
.interact_text()?;
@@ -849,7 +849,7 @@ impl ProfileCommand {
println!("{}", "".repeat(40));
let token_value: String = Input::new()
.with_prompt(&format!("Token for {}", service))
.with_prompt(format!("Token for {}", service))
.interact_text()?;
let token_type_options = vec!["Personal", "OAuth", "Deploy", "App"];
@@ -895,7 +895,7 @@ impl ProfileCommand {
}
let confirm = Confirm::new()
.with_prompt(&format!("Remove token '{}' from profile '{}'?", service, profile_name))
.with_prompt(format!("Remove token '{}' from profile '{}'?", service, profile_name))
.default(false)
.interact()?;

View File

@@ -56,6 +56,10 @@ pub struct TagCommand {
#[arg(long)]
dry_run: bool,
/// Enable thinking mode for this tag (override config)
#[arg(short = 't', long)]
think: bool,
/// Skip interactive prompts
#[arg(short = 'y', long)]
yes: bool,
@@ -285,7 +289,7 @@ impl TagCommand {
println!("{}", messages.ai_generating_tag(commits.len()));
let generator = ContentGenerator::new(&manager).await?;
let generator = ContentGenerator::new_with_think(&manager, self.think).await?;
generator.generate_tag_message(version, &commits, language).await
}