CI: Issue Change Requests 2
This commit is contained in:
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user