feat: 添加 Token 费用估算与自定义忽略规则

- 在 LLM 客户端中捕获流式响应的 Token 使用量
- 内置多模型官方定价表,支持自定义输入/输出单价覆盖
- 新增 `--price-input` 和 `--price-output` 配置项
- 支持通过 `.airignore` 文件自定义排除评审文件
- 更新中英日文文档,补充费用估算与忽略规则说明
This commit is contained in:
unclesam committed 2026-08-21 15:15:12 +08:00
1 parent 02e2e4edd3
commit 28c29e69df
13 files changed
+691 -82

No files matched your search

+72 -14
View File
@@ -1,13 +1,14 @@
package git
import (
"bufio"
"os"
"path/filepath"
"regexp"
"strings"
)
// 默认需要忽略的文件名与后缀列表
var ignoredPatterns = []string{
var defaultIgnoredPatterns = []string{
// 依赖与锁文件
"go.sum",
"package-lock.json",
@@ -36,19 +37,45 @@ var ignoredPatterns = []string{
"*.pdf",
}
// isIgnoredFile 判断文件路径是否应该在 Code Review 中被忽略
func isIgnoredFile(filePath string) bool {
cleanPath := filepath.ToSlash(filePath)
// LoadAirIgnore 从仓库根目录加载 .airignore 文件中的自定义忽略规则
func LoadAirIgnore(repoRoot string) []string {
if repoRoot == "" {
return nil
}
ignoreFile := filepath.Join(repoRoot, ".airignore")
file, err := os.Open(ignoreFile)
if err != nil {
return nil
}
defer file.Close()
var patterns []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
trimmed := strings.TrimSpace(scanner.Text())
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
continue
}
patterns = append(patterns, trimmed)
}
return patterns
}
// isIgnoredFile 判断文件路径是否命中忽略规则(内置规则 + 用户自定义规则)
func isIgnoredFile(filePath string, customPatterns []string) bool {
cleanPath := filepath.ToSlash(strings.TrimSpace(filePath))
baseName := filepath.Base(cleanPath)
for _, pattern := range ignoredPatterns {
// 精确匹配文件名(如 go.sum)
if pattern == baseName {
// 1. 先匹配用户自定义规则 (.airignore)
for _, pattern := range customPatterns {
if matchPattern(pattern, cleanPath, baseName) {
return true
}
}
// 通配符匹配(如 *.pb.go)
if matched, _ := filepath.Match(pattern, baseName); matched {
// 2. 再匹配内置默认规则
for _, pattern := range defaultIgnoredPatterns {
if matchPattern(pattern, cleanPath, baseName) {
return true
}
}
@@ -56,10 +83,41 @@ func isIgnoredFile(filePath string) bool {
return false
}
var diffHeaderRegex = regexp.MustCompile(`(?m)^diff --git a/(.*?) b/(.*?)$`)
// matchPattern 单条规则智能匹配(支持目录/、通配符*、精确匹配)
func matchPattern(pattern, cleanPath, baseName string) bool {
pattern = filepath.ToSlash(strings.TrimSpace(pattern))
if pattern == "" {
return false
}
// FilterDiff 过滤掉 diff 中无意义的锁文件和自动生成文件的区块
func FilterDiff(rawDiff string) string {
// 目录规则:如 "docs/" 或 "vendor/"
if strings.HasSuffix(pattern, "/") {
dirPrefix := strings.TrimSuffix(pattern, "/")
if strings.HasPrefix(cleanPath, dirPrefix+"/") || cleanPath == dirPrefix {
return true
}
}
// 精确匹配文件名或相对路径
if pattern == baseName || pattern == cleanPath {
return true
}
// 通配符匹配文件名(如 *.pb.go)
if matched, _ := filepath.Match(pattern, baseName); matched {
return true
}
// 通配符匹配全路径(如 mock/*.go)
if matched, _ := filepath.Match(pattern, cleanPath); matched {
return true
}
return false
}
// FilterDiff 过滤掉 diff 中命中忽略规则(如锁文件、生成的代码、.airignore 排除项)的文件区块
func FilterDiff(rawDiff string, customPatterns ...string) string {
if strings.TrimSpace(rawDiff) == "" {
return ""
}
@@ -85,7 +143,7 @@ func FilterDiff(rawDiff string) string {
parts := strings.Fields(firstLine)
if len(parts) >= 2 {
filePath := strings.TrimPrefix(parts[1], "b/")
if isIgnoredFile(filePath) {
if isIgnoredFile(filePath, customPatterns) {
continue // 命中忽略规则,跳过这个文件的 diff
}
}