feat(cli): 新增 AI 智能 Commit Message 生成与多语言支持

- 新增 `git air msg` 命令,支持根据暂存区或历史 Diff 自动生成 Conventional Commits 提交说明
- 支持中、英、日、韩多语言自适应及通过 CLI/配置文件/环境变量进行定制
- 完善全局配置体系,新增 `commit_lang` 选项及相关单元测试
This commit is contained in:
unclesam committed 2026-08-22 16:54:44 +08:00
1 parent 4d9674831b
commit 6eda653db6
9 files changed
+457 -41

No files matched your search

+21 -7
View File
@@ -12,13 +12,14 @@ import (
// AppConfig 全局配置结构体
type AppConfig struct {
Provider string `yaml:"provider"` // gemini, deepseek, ollama, openai, custom
APIKey string `yaml:"api_key"` // API Key
BaseURL string `yaml:"base_url"` // 自定义请求地址
Model string `yaml:"model"` // 模型名称
CustomPrompt string `yaml:"custom_prompt"` // 用户自定义的 Base System Prompt
PriceInput float64 `yaml:"price_input,omitempty"` // 自定义输入单价 (每 1M tokens 美元,可选)
PriceOutput float64 `yaml:"price_output,omitempty"` // 自定义输出单价 (每 1M tokens 美元,可选)
Provider string `yaml:"provider"` // gemini, deepseek, ollama, openai, custom
APIKey string `yaml:"api_key"` // API Key
BaseURL string `yaml:"base_url"` // 自定义请求地址
Model string `yaml:"model"` // 模型名称
CustomPrompt string `yaml:"custom_prompt"` // 用户自定义的 Base System Prompt
PriceInput float64 `yaml:"price_input,omitempty"` // 自定义输入单价 (每 1M tokens 美元,可选)
PriceOutput float64 `yaml:"price_output,omitempty"` // 自定义输出单价 (每 1M tokens 美元,可选)
CommitLang string `yaml:"commit_lang,omitempty"` // Commit Message 生成语言 (auto, zh, en, ja, ko 等)
}
func getConfigPath() (string, error) {
@@ -77,6 +78,9 @@ func LoadConfig() (*AppConfig, error) {
if envBaseURL := os.Getenv("GIT_AIR_BASE_URL"); envBaseURL != "" {
cfg.BaseURL = envBaseURL
}
if envLang := os.Getenv("GIT_AIR_COMMIT_LANG"); envLang != "" {
cfg.CommitLang = envLang
}
return cfg, nil
}
@@ -104,6 +108,7 @@ var configSetCmd = &cobra.Command{
Short: "设置全局配置项",
Example: ` git air config set --provider gemini --key YOUR_KEY
git air config set --provider deepseek --key YOUR_KEY --model deepseek-chat
git air config set --commit-lang ja
git air config set --provider ollama --model qwen2.5-coder`,
Run: func(cmd *cobra.Command, args []string) {
cfg, err := LoadConfig()
@@ -134,6 +139,9 @@ var configSetCmd = &cobra.Command{
if po, _ := cmd.Flags().GetFloat64("price-output"); po > 0 {
cfg.PriceOutput = po
}
if cl, _ := cmd.Flags().GetString("commit-lang"); cl != "" {
cfg.CommitLang = cl
}
if err := SaveConfig(cfg); err != nil {
ui.PrintError("保存配置失败: %v", err)
return
@@ -168,6 +176,11 @@ var configGetCmd = &cobra.Command{
hasPrompt = "是 (已自定义)"
}
fmt.Printf("CustomPrompt: %s\n", hasPrompt)
commitLang := "auto (自动识别系统语言)"
if cfg.CommitLang != "" {
commitLang = cfg.CommitLang
}
fmt.Printf("CommitLang: %s\n", commitLang)
if cfg.PriceInput > 0 || cfg.PriceOutput > 0 {
fmt.Printf("CustomPrice: 输入 $%.4f/1M, 输出 $%.4f/1M (已自定义覆盖)\n", cfg.PriceInput, cfg.PriceOutput)
} else {
@@ -184,6 +197,7 @@ func init() {
configSetCmd.Flags().String("prompt", "", "自定义 Base System Prompt")
configSetCmd.Flags().Float64("price-input", 0, "自定义每 1M 输入 Token 美元价格")
configSetCmd.Flags().Float64("price-output", 0, "自定义每 1M 输出 Token 美元价格")
configSetCmd.Flags().String("commit-lang", "", "Commit Message 语言 (auto, zh, en, ja, ko 等)")
configCmd.AddCommand(configSetCmd)
configCmd.AddCommand(configGetCmd)
+139
View File
@@ -0,0 +1,139 @@
package cmd
import (
"bufio"
"fmt"
"os"
"os/exec"
"strings"
"github.com/spf13/cobra"
"github.com/unclesam-ly/git-air/internal/git"
"github.com/unclesam-ly/git-air/internal/llm"
"github.com/unclesam-ly/git-air/internal/reviewer"
"github.com/unclesam-ly/git-air/internal/ui"
)
var msgCmd = &cobra.Command{
Use: "msg [git diff 参数,如 HEAD~1, main..feature, 文件名等]",
Aliases: []string{"commit-msg", "commit"},
Short: "根据代码变更智能生成规范的 Conventional Commits 提交信息",
Example: ` git air msg # 根据当前暂存区代码自动生成
git air msg -l ja # 指定生成日语提交说明
git air msg -l en # 指定生成英文提交说明
git air msg -c # 生成后直接执行 git commit`,
Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()
// 1. 检查并获取 Diff (默认优先获取暂存区)
diff, err := git.GetDiff(ctx, args...)
if err != nil {
ui.PrintError("%v", err)
return
}
// 2. 加载配置
cfg, err := LoadConfig()
if err != nil {
ui.PrintError("加载配置失败: %v", err)
return
}
if flagKey, _ := cmd.Flags().GetString("key"); flagKey != "" {
cfg.APIKey = flagKey
}
if flagModel, _ := cmd.Flags().GetString("model"); flagModel != "" {
cfg.Model = flagModel
}
if flagProvider, _ := cmd.Flags().GetString("provider"); flagProvider != "" {
cfg.Provider = flagProvider
}
// 3. 初始化大模型客户端
llmClient, err := llm.NewClient(llm.Config{
Provider: cfg.Provider,
APIKey: cfg.APIKey,
BaseURL: cfg.BaseURL,
Model: cfg.Model,
PriceInput: cfg.PriceInput,
PriceOutput: cfg.PriceOutput,
})
if err != nil {
ui.PrintError("初始化模型失败: %v", err)
return
}
// 4. 确定目标语言 (命令行 Flag > 配置文件 > 自动探测系统语言)
lang, _ := cmd.Flags().GetString("lang")
if lang == "" {
lang = cfg.CommitLang
}
repoRoot, _ := git.GetRepoRoot(ctx)
rev := reviewer.NewReviewer(llmClient, repoRoot, "")
targetLang := reviewer.DetectLanguage(lang)
langDesc := "English"
switch targetLang {
case "zh":
langDesc = "简体中文"
case "ja":
langDesc = "日本語"
case "ko":
langDesc = "한국어"
}
fmt.Printf("\033[1;36m[git-air]\033[0m 正在生成 Conventional Commit Message... \033[0;90m(Language: %s / Engine: %s)\033[0m\n", langDesc, cfg.Model)
fmt.Println("\033[0;90m" + strings.Repeat("─", 65) + "\033[0m")
commitMsg, err := rev.GenerateCommitMsg(ctx, diff, lang)
if err != nil {
ui.PrintError("生成 Commit Message 失败: %v", err)
return
}
// 5. 打印生成的提交信息
fmt.Printf("\033[1;32m%s\033[0m\n", commitMsg)
fmt.Println("\033[0;90m" + strings.Repeat("─", 65) + "\033[0m")
// 6. 是否自动执行提交
autoCommit, _ := cmd.Flags().GetBool("commit")
if autoCommit {
executeGitCommit(commitMsg)
return
}
// 交互式询问是否直接提交
fmt.Print("? 是否直接以此信息执行 git commit?[Y/n]: ")
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(strings.ToLower(input))
if input == "" || input == "y" || input == "yes" {
executeGitCommit(commitMsg)
} else {
fmt.Println("\033[0;90m已跳过提交。你可以复制上方内容手动提交。\033[0m")
}
},
}
func executeGitCommit(msg string) {
cmd := exec.Command("git", "commit", "-m", msg)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
ui.PrintError("执行 git commit 失败: %v", err)
return
}
ui.PrintSuccess("代码提交成功!🎉")
}
func init() {
msgCmd.Flags().StringP("lang", "l", "", "生成语言 (auto, zh, en, ja, ko)")
msgCmd.Flags().BoolP("commit", "c", false, "生成后无需确认直接执行 git commit")
msgCmd.Flags().StringP("key", "k", "", "临时指定 API Key")
msgCmd.Flags().StringP("model", "m", "", "临时指定模型名称")
msgCmd.Flags().StringP("provider", "p", "", "临时指定 Provider")
rootCmd.AddCommand(msgCmd)
}