gitea/services/ai/review.go
Lieslot de827e114d
PR AI 정적 분석 기능 API 구현 (#24)
* feat: PR 리뷰 ai 정적 분석 기능 추가

* fix: CreateAiPullComment의 반환값을 *AiPullComment로 변경

* fix: review_test 모킹 타입 오류 수정

* fix: ai interface server config 파일 설정 추가 및 ai interface server 요청 방식 변경
2024-08-02 10:41:38 +09:00

166 lines
4.3 KiB
Go

package ai
import (
"bytes"
"fmt"
"io"
"net/http"
"strconv"
"sync"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/context"
)
type AiService interface {
CreateAiPullComment(ctx *context.Context, form *api.CreateAiPullCommentForm, aiRequester AiRequester, adapter DbAdapter) error
DeleteAiPullComment(ctx *context.Context, id int64, adapter DbAdapter) error
}
type AiReviewRequest struct {
Branch string `json:"branch"`
TreePath string `json:"file_path"`
Content string `json:"code"`
}
type AiReviewResponse struct {
Branch string `json:"branch"`
TreePath string `json:"file_path"`
Content string `json:"code"`
}
type AiServiceImpl struct{}
type AiRequesterImpl struct{}
type AiRequester interface {
RequestReviewToAI(ctx *context.Context, request *AiReviewRequest) (*AiReviewResponse, error)
}
var _ AiService = &AiServiceImpl{}
var _ AiRequester = &AiRequesterImpl{}
var apiURL = setting.AiServer.Url
type AiReviewCommentResult struct {
PullID string
RepoID string
TreePath string
Content string
}
func (aiRequest *AiRequesterImpl) RequestReviewToAI(ctx *context.Context, request *AiReviewRequest) (*AiReviewResponse, error) {
requestBytes, _ := json.Marshal(request)
buffer := bytes.NewBuffer(requestBytes)
response, err := http.Post(apiURL, "application/json", buffer)
if err != nil {
return nil, err
}
defer response.Body.Close()
bodyJson, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
result := &AiReviewResponse{}
err = json.Unmarshal(bodyJson, result)
if err != nil {
return nil, err
}
return result, nil
}
// TODOC 잘못된 형식의 json이 돌아올 때 예외 반환하기(json 형식 표시하도록)
func (aiController *AiServiceImpl) CreateAiPullComment(ctx *context.Context, form *api.CreateAiPullCommentForm, aiRequester AiRequester, adapter DbAdapter) error {
branch := form.Branch
var wg *sync.WaitGroup = new(sync.WaitGroup)
requestCnt := len(*form.FileContents)
wg.Add(requestCnt)
resultQueue := make(chan *AiReviewResponse, requestCnt)
for _, fileContent := range *form.FileContents {
go func(fileContent *api.PathContentMap) {
defer wg.Done()
result, err := aiRequester.RequestReviewToAI(ctx, &AiReviewRequest{
Branch: branch,
TreePath: fileContent.TreePath,
Content: fileContent.Content,
})
if err != nil {
fmt.Errorf("request to ai server fail %s", result.TreePath)
resultQueue <- nil
return
}
resultQueue <- result
}(&fileContent)
}
wg.Wait()
close(resultQueue)
pullID, err := strconv.ParseInt(form.PullID, 10, 64)
if err != nil {
return fmt.Errorf("pullID is invalid")
}
return saveResults(ctx, resultQueue, pullID, adapter)
}
func (aiService *AiServiceImpl) DeleteAiPullComment(ctx *context.Context, id int64, adapter DbAdapter) error {
return adapter.DeleteAiPullCommentByID(ctx, id)
}
func saveResults(ctx *context.Context, reviewResults chan *AiReviewResponse, pullID int64, adapter DbAdapter) error {
pull, err := adapter.GetIssueByID(ctx, pullID)
if err != nil {
return fmt.Errorf("pr not found by id")
}
for result := range reviewResults {
_, err := adapter.CreateAiPullComment(ctx, &issues_model.CreateAiPullCommentOption{
Doer: ctx.Doer,
Repo: pull.Repo,
Pull: pull,
TreePath: result.TreePath,
Content: result.Content,
})
if err != nil {
return err
}
}
return nil
}
type DbAdapter interface {
GetIssueByID(ctx *context.Context, id int64) (*issues_model.Issue, error)
CreateAiPullComment(ctx *context.Context, opts *issues_model.CreateAiPullCommentOption) (*issues_model.AiPullComment, error)
DeleteAiPullCommentByID(ctx *context.Context, id int64) error
}
type DbAdapterImpl struct{}
func (is *DbAdapterImpl) GetIssueByID(ctx *context.Context, id int64) (*issues_model.Issue, error) {
return issues_model.GetIssueByID(ctx, id)
}
func (is *DbAdapterImpl) CreateAiPullComment(ctx *context.Context, opts *issues_model.CreateAiPullCommentOption) (*issues_model.AiPullComment, error) {
return issues_model.CreateAiPullComment(ctx, opts)
}
func (is *DbAdapterImpl) DeleteAiPullCommentByID(ctx *context.Context, id int64) error {
return issues_model.DeleteAiPullCommentByID(ctx, id)
}