feat: 持久化 AI 审查结果,文件级内联展示与页面集成

This commit is contained in:
2026-06-19 23:24:06 +08:00
parent a89d057716
commit 0528eee4de
7 changed files with 518 additions and 23 deletions
+33 -1
View File
@@ -63,7 +63,39 @@ func parseReportData(data ReportData) ParsedReview {
noteMap[key] = append(noteMap[key], n.Content)
}
// Try to parse structured analysis result
// Try to parse structured analysis result (new ReviewResult format)
var reviewResult ReviewResult
if err := json.Unmarshal([]byte(data.Result), &reviewResult); err == nil && len(reviewResult.FileReviews) > 0 {
// New format: { file_reviews: [...], summary: {...}, top_n: N }
result.Score = reviewResult.Summary.Score
result.Overall = reviewResult.Summary.Overall
result.Findings = reviewResult.Summary.Findings
result.Recommendations = reviewResult.Summary.Recommendations
for _, fr := range reviewResult.FileReviews {
fileReport := FileReviewForReport{
FileName: fr.FileName,
ChangeLines: fr.ChangeLines,
}
// Collect file-level notes
fileReport.Notes = noteMap["file:"+fr.FileName]
for _, s := range fr.Suggestions {
sug := SuggestionForReport{
Severity: s.Severity,
SeverityCN: severityCN(s.Severity),
Description: s.Description,
Suggestion: s.Suggestion,
CodeExample: s.CodeExample,
}
fileReport.Suggestions = append(fileReport.Suggestions, sug)
}
result.FileReviews = append(result.FileReviews, fileReport)
}
return result
}
// Fallback: try legacy flat format { score, overall, findings, recommendations }
var analysisMap map[string]interface{}
if err := json.Unmarshal([]byte(data.Result), &analysisMap); err == nil {
if score, ok := analysisMap["score"].(float64); ok {
+20 -8
View File
@@ -34,6 +34,13 @@ type ReviewSummary struct {
Recommendations string `json:"recommendations"`
}
// ReviewResult holds the complete review output for persistence.
type ReviewResult struct {
FileReviews []FileReview `json:"file_reviews"`
Summary ReviewSummary `json:"summary"`
TopN int `json:"top_n"`
}
// countDiffLines counts the number of added/removed lines in a diff patch.
func countDiffLines(patch string) int {
count := 0
@@ -48,24 +55,25 @@ func countDiffLines(patch string) int {
}
// GenerateReview performs AI code review on diff files with Top-N strategy.
// It streams events (file_start, suggestion, file_end, summary, done) via callback.
func GenerateReview(db *sql.DB, repoPath, base, head string, topN int, callback StreamCallback) error {
// It streams events (file_start, suggestion, file_end, summary, done) via callback
// and returns the complete ReviewResult for persistence.
func GenerateReview(db *sql.DB, repoPath, base, head string, topN int, callback StreamCallback) (*ReviewResult, error) {
// Read LLM config
config, err := GetLLMConfig(db)
if err != nil {
return err
return nil, err
}
// Open repo
repo, err := OpenRepo(repoPath)
if err != nil {
return fmt.Errorf("open repo: %w", err)
return nil, fmt.Errorf("open repo: %w", err)
}
// Get diff files
files, err := GetDiffFiles(repo, base, head)
if err != nil {
return fmt.Errorf("get diff files: %w", err)
return nil, fmt.Errorf("get diff files: %w", err)
}
if len(files) == 0 {
@@ -73,7 +81,7 @@ func GenerateReview(db *sql.DB, repoPath, base, head string, topN int, callback
callback("summary", map[string]interface{}{"content": "没有检测到代码变更。"})
callback("done", map[string]interface{}{"content": ""})
}
return nil
return &ReviewResult{TopN: topN}, nil
}
// Sort by change size (descending)
@@ -226,6 +234,7 @@ func GenerateReview(db *sql.DB, repoPath, base, head string, topN int, callback
}
// Generate summary
var summary ReviewSummary
if len(fileReviews) > 0 {
if callback != nil {
callback("progress", map[string]interface{}{"step": "generating_summary"})
@@ -265,7 +274,6 @@ func GenerateReview(db *sql.DB, repoPath, base, head string, topN int, callback
summaryResponse, err := ChatStream(config, messages, callback)
if err == nil {
jsonStr := extractJSON(summaryResponse)
var summary ReviewSummary
if json.Unmarshal([]byte(jsonStr), &summary) == nil {
summaryText := fmt.Sprintf("整体评分: %d/10\n\n%s", summary.Score, summary.Overall)
if summary.Findings != "" {
@@ -294,5 +302,9 @@ func GenerateReview(db *sql.DB, repoPath, base, head string, topN int, callback
callback("done", map[string]interface{}{"content": ""})
}
return nil
return &ReviewResult{
FileReviews: fileReviews,
Summary: summary,
TopN: topN,
}, nil
}