CI: Issue Change Requests 2

This commit is contained in:
2026-02-28 11:18:31 +01:00
committed by Konstantin Hintermayer
parent df3e4ef53c
commit 2a886bcd8d

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io" "io"
"log" "log"
@@ -14,6 +15,12 @@ import (
"google.golang.org/api/option" "google.golang.org/api/option"
) )
type Suggestion struct {
LineNumber uint64 `json:"line_number"`
Comment string `json:"comment"`
Snippet string `json:"snippet,omitempty"`
}
func main() { func main() {
apiKey := os.Getenv("GEMINI_API_KEY") apiKey := os.Getenv("GEMINI_API_KEY")
token := os.Getenv("GITEA_TOKEN") token := os.Getenv("GITEA_TOKEN")
@@ -55,7 +62,7 @@ func main() {
} }
defer geminiClient.Close() defer geminiClient.Close()
model := geminiClient.GenerativeModel("gemini-3-flash-preview") model := geminiClient.GenerativeModel("gemini-1.5-flash")
// Get PR files // Get PR files
files, _, err := client.ListPullRequestFiles(owner, repo, prNumber, gitea.ListPullRequestFilesOptions{}) files, _, err := client.ListPullRequestFiles(owner, repo, prNumber, gitea.ListPullRequestFilesOptions{})
@@ -63,7 +70,7 @@ func main() {
log.Fatalf("Failed to get PR files: %v", err) log.Fatalf("Failed to get PR files: %v", err)
} }
var reviews []string var giteaComments []gitea.CreatePullReviewComment
for _, file := range files { for _, file := range files {
if !strings.HasSuffix(file.Filename, ".md") { if !strings.HasSuffix(file.Filename, ".md") {
continue continue
@@ -76,25 +83,35 @@ func main() {
continue continue
} }
review, err := getGeminiReview(ctx, model, content) suggestions, err := getGeminiReview(ctx, model, content)
if err != nil { if err != nil {
fmt.Printf("Error getting review for %s: %v\n", file.Filename, err) fmt.Printf("Error getting review for %s: %v\n", file.Filename, err)
continue continue
} }
reviews = append(reviews, fmt.Sprintf("#### Review for `%s`\n\n%s", file.Filename, review)) for _, s := range suggestions {
body := s.Comment
if s.Snippet != "" {
body += fmt.Sprintf("\n\n```suggestion\n%s\n```", s.Snippet)
}
giteaComments = append(giteaComments, gitea.CreatePullReviewComment{
Path: file.Filename,
Body: body,
NewLineNum: int64(s.LineNumber),
})
}
} }
if len(reviews) > 0 { if len(giteaComments) > 0 {
reviewBody := "### 🤖 Gemini Writing Review\n\n" + strings.Join(reviews, "\n\n---\n\n")
_, _, err = client.CreatePullReview(owner, repo, prNumber, gitea.CreatePullReviewOptions{ _, _, err = client.CreatePullReview(owner, repo, prNumber, gitea.CreatePullReviewOptions{
State: gitea.ReviewStateRequestChanges, State: gitea.ReviewStateRequestChanges,
Body: reviewBody, Body: "### 🤖 Gemini Writing Review\n\nI've found some areas for improvement in the documentation. Please see the inline comments below.",
Comments: giteaComments,
}) })
if err != nil { if err != nil {
log.Fatalf("Failed to create PR review: %v", err) log.Fatalf("Failed to create PR review: %v", err)
} }
fmt.Println("Successfully created PR change request review.") fmt.Printf("Successfully created PR review with %d inline comments.\n", len(giteaComments))
} else { } else {
fmt.Println("No Markdown files to review or no suggestions found.") fmt.Println("No Markdown files to review or no suggestions found.")
} }
@@ -114,35 +131,56 @@ func readFile(path string) (string, error) {
return string(content), nil return string(content), nil
} }
func getGeminiReview(ctx context.Context, model *genai.GenerativeModel, content string) (string, error) { func getGeminiReview(ctx context.Context, model *genai.GenerativeModel, content string) ([]Suggestion, error) {
prompt := fmt.Sprintf(` prompt := fmt.Sprintf(`
Review the following Markdown content for spelling errors, grammar mistakes, and style improvements. Review the following Markdown content for spelling errors, grammar mistakes, and style improvements.
This review will be posted as a "Request Changes" on a Pull Request, so please be specific and actionable. Analyze the text line by line.
Provide your feedback as a list of bullet points. For each point:
1. Identify the issue.
2. Provide the original text snippet.
3. Suggest a clear alternative or fix.
4. Briefly explain why the change is necessary if not obvious.
5. Check the content for completeness and technical correctness.
Content: For each issue found, provide a suggestion in JSON format with:
- "line_number": The 1-indexed line number where the issue occurs.
- "comment": A brief explanation of the problem and the suggested fix.
- "snippet": The corrected text for that line (optional, but highly recommended for spell/grammar fixes).
Return ONLY a JSON array of suggestions. If no issues are found, return "[]".
Content with line numbers for reference:
%s %s
`, content) `, addLineNumbers(content))
resp, err := model.GenerateContent(ctx, genai.Text(prompt)) resp, err := model.GenerateContent(ctx, genai.Text(prompt))
if err != nil { if err != nil {
return "", err return nil, err
} }
if len(resp.Candidates) == 0 || len(resp.Candidates[0].Content.Parts) == 0 { if len(resp.Candidates) == 0 || len(resp.Candidates[0].Content.Parts) == 0 {
return "No suggestions found.", nil return nil, nil
} }
var result strings.Builder var rawJS strings.Builder
for _, part := range resp.Candidates[0].Content.Parts { for _, part := range resp.Candidates[0].Content.Parts {
if text, ok := part.(genai.Text); ok { if text, ok := part.(genai.Text); ok {
result.WriteString(string(text)) rawJS.WriteString(string(text))
} }
} }
return result.String(), nil
// Clean up markdown code blocks if present
js := strings.TrimSpace(rawJS.String())
js = strings.TrimPrefix(js, "```json")
js = strings.TrimSuffix(js, "```")
js = strings.TrimSpace(js)
var suggestions []Suggestion
if err := json.Unmarshal([]byte(js), &suggestions); err != nil {
return nil, fmt.Errorf("failed to unmarshal suggestions: %v (raw response: %s)", err, js)
}
return suggestions, nil
}
func addLineNumbers(text string) string {
lines := strings.Split(text, "\n")
for i, line := range lines {
lines[i] = fmt.Sprintf("%d: %s", i+1, line)
}
return strings.Join(lines, "\n")
} }