Compare commits
10 Commits
a3b7dda440
...
cdc9fda92a
Author | SHA1 | Date | |
---|---|---|---|
|
cdc9fda92a | ||
|
de827e114d | ||
|
88f4754d06 | ||
|
54407a6d0b | ||
|
0390e49210 | ||
|
f9c73568c5 | ||
|
b15346f4a1 | ||
|
8f0af1d889 | ||
|
882144d133 | ||
|
385f737d20 |
15
client/base.go
Normal file
15
client/base.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
var client *resty.Client = nil
|
||||
|
||||
func Request() *resty.Request {
|
||||
if client == nil {
|
||||
client = resty.New().SetBaseURL(setting.DiscussionServer.Url)
|
||||
}
|
||||
return client.R()
|
||||
}
|
89
client/discussion/discussion_client.go
Normal file
89
client/discussion/discussion_client.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package discussion
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/client"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type DiscussionCode struct {
|
||||
Id sql.NullInt64 `json:"id"`
|
||||
FilePath string `json:"filePath"`
|
||||
StartLine int `json:"startLine"`
|
||||
EndLine int `json:"endLine"`
|
||||
}
|
||||
|
||||
type PostDiscussionRequest struct {
|
||||
RepoId int64 `json:"repoId"`
|
||||
PosterId int64 `json:"posterId"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
Codes []DiscussionCode `json:"codes"`
|
||||
}
|
||||
|
||||
type DiscussionAvailableRequest struct {
|
||||
RepoId int64 `json:"repoId"`
|
||||
Available bool `json:"available"`
|
||||
}
|
||||
|
||||
type CommentScopeEnum int
|
||||
|
||||
const (
|
||||
CommentScopeGlobal CommentScopeEnum = iota
|
||||
CommentScopeLocal
|
||||
)
|
||||
|
||||
type PostCommentRequest struct {
|
||||
DiscussionId int64 `json:"discussionId"`
|
||||
CodeId int64 `json:"codeId"`
|
||||
PosterId int64 `json:"posterId"`
|
||||
Scope CommentScopeEnum `json:"scope"`
|
||||
StartLine sql.NullInt32 `json:"startLine"`
|
||||
EndLine sql.NullInt32 `json:"endLine"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type ModifyDiscussionRequest struct {
|
||||
RepoId int64 `json:"repoId"`
|
||||
DiscussionId int64 `json:"discussionId"`
|
||||
PosterId int64 `json:"posterId"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
Codes []DiscussionCode `json:"codes"`
|
||||
}
|
||||
|
||||
func PostDiscussion(request PostDiscussionRequest) (*resty.Response, error) {
|
||||
return client.Request().SetBody(request).Post("/discussion")
|
||||
}
|
||||
|
||||
func GetDiscussionCount(repoId int64, isClosed bool) (*resty.Response, error) {
|
||||
var isClosedAsInt = map[bool]int{false: 0, true: 1}[isClosed]
|
||||
return client.Request().
|
||||
SetQueryParam("isClosed", string(isClosedAsInt)).
|
||||
Get(fmt.Sprintf("/discussion/%d/count", repoId))
|
||||
}
|
||||
|
||||
func GetDiscussionList(repoId int64, isClosed bool) (*resty.Response, error) {
|
||||
var isClosedAsInt = map[bool]int{false: 0, true: 1}[isClosed]
|
||||
return client.Request().
|
||||
SetQueryParam("isClosed", string(isClosedAsInt)).
|
||||
Get(fmt.Sprintf("/discussion/%d/list", repoId))
|
||||
}
|
||||
|
||||
func HandleDiscussionAvailable() (*resty.Response, error) {
|
||||
return client.Request().Post("/discussion/available")
|
||||
}
|
||||
|
||||
func GetDiscussionContents(discussionId int64) (*resty.Response, error) {
|
||||
return client.Request().Get(fmt.Sprintf("/discussion/%d/codes", discussionId))
|
||||
}
|
||||
|
||||
func PostComment(request PostCommentRequest) (*resty.Response, error) {
|
||||
return client.Request().SetBody(request).Post("/discussion/comment")
|
||||
}
|
||||
|
||||
func ModifyDiscussion(request ModifyDiscussionRequest) (*resty.Response, error) {
|
||||
return client.Request().SetBody(request).Put("/discussion")
|
||||
}
|
10
go.mod
10
go.mod
|
@ -104,12 +104,12 @@ require (
|
|||
github.com/yuin/goldmark v1.7.0
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
github.com/yuin/goldmark-meta v1.1.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/oauth2 v0.18.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/text v0.15.0
|
||||
golang.org/x/tools v0.19.0
|
||||
google.golang.org/grpc v1.62.1
|
||||
google.golang.org/protobuf v1.33.0
|
||||
|
@ -194,6 +194,7 @@ require (
|
|||
github.com/go-openapi/strfmt v0.23.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.13.1 // indirect
|
||||
github.com/go-webauthn/x v0.1.9 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
|
@ -267,6 +268,7 @@ require (
|
|||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/toqueteos/webbrowser v1.2.0 // indirect
|
||||
github.com/unknwon/com v1.0.1 // indirect
|
||||
|
|
14
go.sum
14
go.sum
|
@ -329,6 +329,8 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ
|
|||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
|
||||
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
|
||||
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
|
@ -736,6 +738,8 @@ github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
|
@ -854,8 +858,10 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
|
|||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
|
@ -888,8 +894,11 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -939,8 +948,10 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
|
@ -950,8 +961,10 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
|||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
@ -965,6 +978,7 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
6
models/fixtures/ai_pull_comment.yml
Normal file
6
models/fixtures/ai_pull_comment.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
-
|
||||
id: 2 # create 전용
|
||||
poster_id: 1
|
||||
pull_id: 1
|
||||
|
||||
|
140
models/issues/ai_comment.go
Normal file
140
models/issues/ai_comment.go
Normal file
|
@ -0,0 +1,140 @@
|
|||
package issues
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
)
|
||||
|
||||
// init 메소드가 있으면 자동적으로 xorm에서 이 메소드를 실행하는듯 하다.
|
||||
func init() {
|
||||
db.RegisterModel(new(AiPullComment))
|
||||
}
|
||||
|
||||
// TODOC AI 코멘트 테이블 만들기
|
||||
// TODOC outdated가 어떤 식으로 나타나는 것인지 알아보기
|
||||
// TODOC 먼저 영속성 계층부터-도메인 계층 순서로 만들어가기
|
||||
type AiPullComment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
PosterID int64 `xorm:"INDEX"`
|
||||
Poster *user_model.User `xorm:"-"`
|
||||
PullID int64
|
||||
Pull *Issue `xorm:"-"`
|
||||
TreePath string
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
Status string `xorm:"status"` //
|
||||
DeletedUnix timeutil.TimeStamp `xorm:"deleted"`
|
||||
CommitSHA string `xorm:"VARCHAR(64)"`
|
||||
// CommitID int64
|
||||
|
||||
}
|
||||
|
||||
type CreateAiPullCommentOption struct {
|
||||
Doer *user_model.User
|
||||
Repo *repo_model.Repository
|
||||
Pull *Issue
|
||||
TreePath string
|
||||
Content string
|
||||
CommitSHA string
|
||||
// CommitID string
|
||||
|
||||
}
|
||||
|
||||
type ErrAiPullCommentNotExist struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
}
|
||||
|
||||
func IsErrAiPullCommentNotExist(err error) bool {
|
||||
_, ok := err.(ErrIssueWasClosed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrAiPullCommentNotExist) Error() string {
|
||||
return fmt.Sprintf("AiPullComment does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID)
|
||||
}
|
||||
|
||||
func CreateAiPullComment(ctx context.Context, opts *CreateAiPullCommentOption) (*AiPullComment, error) {
|
||||
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
aiPullComment := &AiPullComment{
|
||||
PosterID: opts.Doer.ID,
|
||||
PullID: opts.Pull.ID,
|
||||
TreePath: opts.TreePath,
|
||||
Content: opts.Content,
|
||||
CommitSHA: opts.CommitSHA,
|
||||
}
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
_, err = e.Insert(aiPullComment)
|
||||
if err != nil {
|
||||
fmt.Errorf("new Comment insert is invalid")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = committer.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return aiPullComment, nil
|
||||
|
||||
}
|
||||
|
||||
func GetAIPullCommentByID(ctx context.Context, id int64) (*AiPullComment, error) {
|
||||
comment := new(AiPullComment)
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(comment)
|
||||
|
||||
if err != nil {
|
||||
return nil, ErrAiPullCommentNotExist{id, 0}
|
||||
} else if !has {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
|
||||
}
|
||||
|
||||
func DeleteAiPullCommentByID(ctx context.Context, id int64) error {
|
||||
_, err := GetAIPullCommentByID(ctx, id)
|
||||
|
||||
if err != nil {
|
||||
|
||||
if IsErrAiPullCommentNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
dbCtx, commiter, err := db.TxContext(ctx)
|
||||
|
||||
defer commiter.Close()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.DeleteByID[AiPullComment](dbCtx, id)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return commiter.Commit()
|
||||
|
||||
}
|
||||
|
||||
// TODOC repo가 삭제되면 Ai Comment도 삭제하는 로직
|
||||
// TODOC
|
52
models/issues/ai_comment_test.go
Normal file
52
models/issues/ai_comment_test.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package issues_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
ai_comment "code.gitea.io/gitea/models/issues"
|
||||
)
|
||||
|
||||
func TestCreateAiPullComment(t *testing.T) {
|
||||
|
||||
assert := assert.New(t)
|
||||
assert.NoError(unittest.PrepareTestDatabase())
|
||||
|
||||
pull := unittest.AssertExistsAndLoadBean(t, &ai_comment.Issue{ID: 2})
|
||||
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pull.RepoID})
|
||||
content := "this code is sh**"
|
||||
treePath := "/src/ddd"
|
||||
newAiComment, err := ai_comment.CreateAiPullComment(db.DefaultContext, &ai_comment.CreateAiPullCommentOption{
|
||||
Doer: doer,
|
||||
Pull: pull,
|
||||
Repo: repo,
|
||||
Content: content,
|
||||
TreePath: treePath,
|
||||
})
|
||||
assert.NoError(err)
|
||||
assert.EqualValues(newAiComment.Content, content)
|
||||
assert.EqualValues(newAiComment.TreePath, treePath)
|
||||
assert.EqualValues(newAiComment.PosterID, doer.ID)
|
||||
assert.EqualValues(newAiComment.PullID, pull.ID)
|
||||
|
||||
}
|
||||
|
||||
func TestDeleteAiPullRequest(t *testing.T) {
|
||||
|
||||
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &ai_comment.AiPullComment{ID: 2})
|
||||
assert.NoError(t, ai_comment.DeleteAiPullCommentByID(db.DefaultContext, comment.ID))
|
||||
unittest.AssertNotExistsBean(t, &ai_comment.AiPullComment{ID: comment.ID})
|
||||
|
||||
assert.NoError(t, ai_comment.DeleteAiPullCommentByID(db.DefaultContext, unittest.NonexistentID))
|
||||
unittest.CheckConsistencyFor(t, &ai_comment.AiPullComment{})
|
||||
}
|
|
@ -36,6 +36,7 @@ type IssuesOptions struct { //nolint
|
|||
ProjectBoardID int64
|
||||
IsClosed optional.Option[bool]
|
||||
IsPull optional.Option[bool]
|
||||
IsDiscussion optional.Option[bool]
|
||||
LabelIDs []int64
|
||||
IncludedLabelNames []string
|
||||
ExcludedLabelNames []string
|
||||
|
|
|
@ -117,7 +117,13 @@ func GetIssueStats(ctx context.Context, opts *IssuesOptions) (*IssueStats, error
|
|||
|
||||
func getIssueStatsChunk(ctx context.Context, opts *IssuesOptions, issueIDs []int64) (*IssueStats, error) {
|
||||
stats := &IssueStats{}
|
||||
|
||||
if opts.IsDiscussion.Has() && opts.IsDiscussion.Value() {
|
||||
// dummy data for open/closed
|
||||
// TODO: 백엔드 연동시 수정 필요
|
||||
stats.OpenCount = 2
|
||||
stats.ClosedCount = 0
|
||||
return stats, nil
|
||||
}
|
||||
sess := db.GetEngine(ctx).
|
||||
Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
|
||||
|
||||
|
|
|
@ -453,7 +453,7 @@ func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeuti
|
|||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// 커멘트에서 멘션된 유저를 찾고 데이터베이스에 저장하기
|
||||
// FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database.
|
||||
func FindAndUpdateIssueMentions(ctx context.Context, issue *Issue, doer *user_model.User, content string) (mentions []*user_model.User, err error) {
|
||||
rawMentions := references.FindAllMentionsMarkdown(content)
|
||||
|
|
|
@ -10,12 +10,14 @@ import (
|
|||
)
|
||||
|
||||
func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOptions {
|
||||
|
||||
searchOpt := &SearchOptions{
|
||||
Keyword: keyword,
|
||||
RepoIDs: opts.RepoIDs,
|
||||
AllPublic: opts.AllPublic,
|
||||
IsPull: opts.IsPull,
|
||||
IsClosed: opts.IsClosed,
|
||||
Keyword: keyword,
|
||||
RepoIDs: opts.RepoIDs,
|
||||
AllPublic: opts.AllPublic,
|
||||
IsPull: opts.IsPull,
|
||||
IsDiscussion: opts.IsDiscussion,
|
||||
IsClosed: opts.IsClosed,
|
||||
}
|
||||
|
||||
if len(opts.LabelIDs) == 1 && opts.LabelIDs[0] == 0 {
|
||||
|
|
|
@ -79,8 +79,9 @@ type SearchOptions struct {
|
|||
RepoIDs []int64 // repository IDs which the issues belong to
|
||||
AllPublic bool // if include all public repositories
|
||||
|
||||
IsPull optional.Option[bool] // if the issues is a pull request
|
||||
IsClosed optional.Option[bool] // if the issues is closed
|
||||
IsPull optional.Option[bool] // if the issues is a pull request
|
||||
IsDiscussion optional.Option[bool] // if the issues is a discussion
|
||||
IsClosed optional.Option[bool] // if the issues is closed
|
||||
|
||||
IncludedLabelIDs []int64 // labels the issues have
|
||||
ExcludedLabelIDs []int64 // labels the issues don't have
|
||||
|
|
20
modules/setting/ai_server.go
Normal file
20
modules/setting/ai_server.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package setting
|
||||
|
||||
import "fmt"
|
||||
|
||||
var AiServer = struct {
|
||||
Host string
|
||||
Port int
|
||||
Url string
|
||||
}{
|
||||
Host: "localhost",
|
||||
Port: 8000,
|
||||
Url: "http://localhost:8000",
|
||||
}
|
||||
|
||||
func loadAiServerFrom(rootCfg ConfigProvider) {
|
||||
sec := rootCfg.Section("ai_server")
|
||||
AiServer.Host = sec.Key("host").MustString("localhost")
|
||||
AiServer.Port = sec.Key("PORT").MustInt(8000)
|
||||
AiServer.Url = fmt.Sprintf("http://%s:%d", AiServer.Host, AiServer.Port)
|
||||
}
|
20
modules/setting/discussion_server.go
Normal file
20
modules/setting/discussion_server.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package setting
|
||||
|
||||
import "fmt"
|
||||
|
||||
var DiscussionServer = struct {
|
||||
Host string
|
||||
Port int
|
||||
Url string
|
||||
}{
|
||||
Host: "localhost",
|
||||
Port: 8081,
|
||||
Url: "http://localhost:8081",
|
||||
}
|
||||
|
||||
func loadDiscussionServerFrom(rootCfg ConfigProvider) {
|
||||
sec := rootCfg.Section("discussion_server")
|
||||
DiscussionServer.Host = sec.Key("HOST").MustString("localhost")
|
||||
DiscussionServer.Port = sec.Key("PORT").MustInt(8081)
|
||||
DiscussionServer.Url = fmt.Sprintf("http://%s:%d", DiscussionServer.Host, DiscussionServer.Port)
|
||||
}
|
|
@ -118,6 +118,8 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
|
|||
loadLogGlobalFrom(cfg)
|
||||
loadServerFrom(cfg)
|
||||
loadSSHFrom(cfg)
|
||||
loadDiscussionServerFrom(cfg)
|
||||
loadAiServerFrom(cfg)
|
||||
|
||||
mustCurrentRunUserMatch(cfg) // it depends on the SSH config, only non-builtin SSH server requires this check
|
||||
|
||||
|
|
13
modules/structs/ai.go
Normal file
13
modules/structs/ai.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package structs
|
||||
|
||||
type CreateAiPullCommentForm struct {
|
||||
Branch string `json:"branch"`
|
||||
FileContents *[]PathContentMap `json:"file_contents"`
|
||||
RepoID string `json:"repo_id"`
|
||||
PullID string `json:"pull_id"`
|
||||
}
|
||||
|
||||
type PathContentMap struct {
|
||||
TreePath string `json:"file_path"`
|
||||
Content string `json:"code"`
|
||||
}
|
|
@ -1214,6 +1214,7 @@ find_tag = Find tag
|
|||
branches = Branches
|
||||
tags = Tags
|
||||
issues = Issues
|
||||
discussions=Discussions
|
||||
pulls = Pull Requests
|
||||
project_board = Projects
|
||||
packages = Packages
|
||||
|
@ -1755,6 +1756,8 @@ issues.reference_link = Reference: %s
|
|||
compare.compare_base = base
|
||||
compare.compare_head = compare
|
||||
|
||||
discussions.new = New Discussion
|
||||
|
||||
pulls.desc = Enable pull requests and code reviews.
|
||||
pulls.new = New Pull Request
|
||||
pulls.new.blocked_user = Cannot create pull request because you are blocked by the repository owner.
|
||||
|
|
|
@ -110,7 +110,7 @@ license=오픈 소스
|
|||
[install]
|
||||
install=설치
|
||||
title=초기 설정
|
||||
docker_helper="Gitea를 Docker에서 실행하려면 설정 전에 이 <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">문서</a>를 읽어보세요."
|
||||
docker_helper=Gitea를 Docker에서 실행하려면 설정 전에 이 <a target="_blank" rel="noopener noreferrer" href="%s">문서</a>를 읽어보세요.
|
||||
db_title=데이터베이스 설정
|
||||
db_type=데이터베이스 유형
|
||||
host=호스트
|
||||
|
@ -441,8 +441,8 @@ manage_gpg_keys=GPG 키 관리
|
|||
add_key=키 추가
|
||||
ssh_desc=이러한 SSH 공용 키는 귀하의 계정과 연결되어 있습니다. 해당 개인 키는 당신의 저장소에 대한 전체 액세스를 가능하게 합니다.
|
||||
gpg_desc=이러한 GPG 공개키는 당신의 계정과 연결되어있습니다. 커밋이 검증될 수 있도록 당신의 개인 키를 안전하게 유지하십시오.
|
||||
ssh_helper="<strong>도움이 필요하세요?</strong> GitHub의 설명서를 참조하시기 바랍니다: <a href=\"%s\">SSH 키 생성하기</a> 또는 SSH를 사용할 때 <a href=\"%s\">일반적인 문제</a>"
|
||||
gpg_helper="<strong>도움이 필요하세요?</strong> GitHub의 설명서를 참조하시기 바랍니다: <a href=\"%s\">GPG키에 대하여</a>."
|
||||
ssh_helper=<strong>도움이 필요하세요?</strong> GitHub의 설명서를 참조하시기 바랍니다: <a href="%s">SSH 키 생성하기</a> 또는 SSH를 사용할 때 <a href="%s">일반적인 문제</a>
|
||||
gpg_helper=<strong>도움이 필요하세요?</strong> GitHub의 설명서를 참조하시기 바랍니다: <a href="%s">GPG키에 대하여</a>.
|
||||
add_new_key=SSH 키 추가
|
||||
add_new_gpg_key=GPG 키 추가
|
||||
gpg_key_id_used=같은 ID의 GPG 공개키가 이미 존재합니다.
|
||||
|
@ -547,7 +547,7 @@ template_helper=템플릿으로 저장소 만들기
|
|||
visibility=가시성
|
||||
visibility_helper_forced=사이트 관리자가 새 레포지토리에 대해 비공개로만 생성되도록 하였습니다.
|
||||
visibility_fork_helper=(변경사항을 적용하는 경우 모든 포크가 영향을 받게 됩니다.)
|
||||
clone_helper="클론하는데에 도움이 필요하면 <a target=\"_blank\" href=\"%s\">Help</a>에 방문하세요."
|
||||
clone_helper=클론하는데에 도움이 필요하면 <a target="_blank" href="%s">Help</a>에 방문하세요.
|
||||
fork_repo=저장소 포크
|
||||
fork_from=원본 프로젝트 :
|
||||
fork_visibility_helper=포크된 저장소의 가시성은 변경하실 수 없습니다.
|
||||
|
@ -621,6 +621,7 @@ branches=브랜치
|
|||
tags=태그
|
||||
issues=이슈
|
||||
pulls=풀 리퀘스트
|
||||
discussions=토론
|
||||
labels=레이블
|
||||
|
||||
milestones=마일스톤
|
||||
|
@ -650,7 +651,7 @@ editor.or=혹은
|
|||
editor.cancel_lower=취소
|
||||
editor.commit_changes=변경 내용을 커밋
|
||||
editor.commit_message_desc=선택적 확장 설명을 추가...
|
||||
editor.commit_directly_to_this_branch="<strong class=\"branch-name\">%s</strong> 브랜치에서 직접 커밋해주세요."
|
||||
editor.commit_directly_to_this_branch=<strong class="branch-name">%s</strong> 브랜치에서 직접 커밋해주세요.
|
||||
editor.create_new_branch=이 커밋에 대한 <strong>새로운 브랜치</strong>를 만들고 끌어오기 요청을 시작합니다.
|
||||
editor.new_branch_name_desc=새로운 브랜치 명...
|
||||
editor.cancel=취소
|
||||
|
@ -743,7 +744,7 @@ issues.action_milestone=마일스톤
|
|||
issues.action_milestone_no_select=마일스톤 없음
|
||||
issues.action_assignee=담당자
|
||||
issues.action_assignee_no_select=담당자 없음
|
||||
issues.opened_by="<a href=\"%[2]s\"> %[3]s</a>가 %[1]s을 오픈"
|
||||
issues.opened_by=<a href="%[2]s"> %[3]s</a>가 %[1]s을 오픈
|
||||
issues.previous=이전
|
||||
issues.next=다음
|
||||
issues.open_title=오픈
|
||||
|
@ -761,7 +762,7 @@ issues.create_comment=코멘트
|
|||
issues.commit_ref_at=` 커밋 <a id="%[1]s" href="#%[1]s">%[2]s</a>에서 이 이슈 언급`
|
||||
issues.role.owner=소유자
|
||||
issues.role.member=멤버
|
||||
issues.sign_in_require_desc="<a href=\"%s\">로그인</a>하여 이 대화에 참여"
|
||||
issues.sign_in_require_desc=<a href="%s">로그인</a>하여 이 대화에 참여
|
||||
issues.edit=수정
|
||||
issues.cancel=취소
|
||||
issues.save=저장
|
||||
|
@ -835,6 +836,7 @@ issues.review.reviewers=리뷰어
|
|||
issues.review.show_outdated=오래된 내역 보기
|
||||
issues.review.hide_outdated=오래된 내역 숨기기
|
||||
|
||||
discussions.new=새로운 토론
|
||||
|
||||
pulls.new=새 풀 리퀘스트
|
||||
pulls.compare_changes=새 풀 리퀘스트
|
||||
|
@ -842,7 +844,7 @@ pulls.compare_base=병합하기
|
|||
pulls.compare_compare=다음으로부터 풀
|
||||
pulls.filter_branch=Filter Branch
|
||||
pulls.create=풀 리퀘스트 생성
|
||||
pulls.title_desc="<code>%[2]s</code> 에서 <code id=\"branch_target\">%[3]s</code> 로 %[1]d commits 를 머지하려 합니다"
|
||||
pulls.title_desc=<code>%[2]s</code> 에서 <code id="branch_target">%[3]s</code> 로 %[1]d commits 를 머지하려 합니다
|
||||
pulls.merged_title_desc=<code>%[2]s</code> 에서 <code>%[3]s</code> 로 %[1]d commits 를 머지했습니다 %[4]s
|
||||
pulls.tab_conversation=대화
|
||||
pulls.tab_commits=커밋
|
||||
|
|
|
@ -920,6 +920,8 @@ func Routes() *web.Route {
|
|||
Patch(reqToken(), notify.ReadThread)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
|
||||
|
||||
|
||||
|
||||
// Users (requires user scope)
|
||||
m.Group("/users", func() {
|
||||
m.Get("/search", reqExploreSignIn(), user.Search)
|
||||
|
|
56
routers/api/v1/repo/ai_comment.go
Normal file
56
routers/api/v1/repo/ai_comment.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
ai_service "code.gitea.io/gitea/services/ai"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
// TODOC 특정한 PR에 대한 AI comment 리퀘스트가 이미 이루어졌을 경우를 체크해서 중복 요청 차단.
|
||||
// CreateAiPullComment creates an attachment and saves the given file
|
||||
func CreateAiPullComment(ctx *context.Context) {
|
||||
// swagger:operation POST /ai/pull/review repository repoCreateAiPullComment
|
||||
// ---
|
||||
// summary: Create ai pull comment
|
||||
// produces:
|
||||
// - application/json
|
||||
// consumes:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateAiPullCommentForm"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/Attachment"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
// Check if attachments are enabled
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateAiPullCommentForm)
|
||||
|
||||
// TODOC 싱글톤으로 바꾼 뒤에
|
||||
// TODOC 설정을 통해 의존성 주입하는 방식으로 바꾸기
|
||||
|
||||
aiService := new(ai_service.AiServiceImpl)
|
||||
aiRequester := new(ai_service.AiRequesterImpl)
|
||||
adapter := new(ai_service.DbAdapterImpl)
|
||||
err := ai_service.AiService.CreateAiPullComment(aiService, ctx, form, aiRequester, adapter)
|
||||
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, map[string]any{
|
||||
"message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusAccepted, map[string]any{
|
||||
"message": "request has accepted",
|
||||
})
|
||||
}
|
|
@ -399,6 +399,12 @@ func SubmitInstall(ctx *context.Context) {
|
|||
cfg.Section("server").Key("ROOT_URL").SetValue(form.AppURL)
|
||||
cfg.Section("server").Key("APP_DATA_PATH").SetValue(setting.AppDataPath)
|
||||
|
||||
// TODO: hardcoded for now, make it configurable later
|
||||
cfg.Section("discussion_server").Key("HOST").SetValue("localhost")
|
||||
cfg.Section("discussion_server").Key("PORT").SetValue("8081")
|
||||
cfg.Section("ai_server").Key("HOST").SetValue("localhost")
|
||||
cfg.Section("ai_server").Key("PORT").SetValue("8000")
|
||||
|
||||
if form.SSHPort == 0 {
|
||||
cfg.Section("server").Key("DISABLE_SSH").SetValue("true")
|
||||
} else {
|
||||
|
|
60
routers/web/repo/ai_review.go
Normal file
60
routers/web/repo/ai_review.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package repo
|
||||
|
||||
// TODO 추후에 api.go로 옮기기
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
|
||||
// issues_model "code.gitea.io/gitea/models/issues"
|
||||
// ai_service "code.gitea.io/gitea/services/ai"
|
||||
|
||||
// api "code.gitea.io/gitea/modules/structs"
|
||||
// "code.gitea.io/gitea/modules/web"
|
||||
// "code.gitea.io/gitea/services/context"
|
||||
// )
|
||||
|
||||
// type AiController interface {
|
||||
// CreateAiReviewComment(ctx *context.Context, service *ai_service.AiService, aiRequest ai_service.AiRequester, issueService ai_service.DbAdapter)
|
||||
// }
|
||||
|
||||
// type AiControllerImpl struct{}
|
||||
|
||||
// func GetActionPull(ctx *context.Context) *issues_model.Issue {
|
||||
// return GetActionIssue(ctx)
|
||||
// }
|
||||
|
||||
// var _ AiController = &AiControllerImpl{}
|
||||
|
||||
// func (aiController *AiControllerImpl) CreateAiReviewComment(ctx *context.Context, service *ai_service.AiService, aiRequest ai_service.AiRequester, issueService ai_service.DbAdapter) {
|
||||
|
||||
// pull := GetActionPull(ctx)
|
||||
// form := web.GetForm(ctx).(*api.CreateAiPullCommentForm)
|
||||
// // TODO 결과를 받아서 AiPullComment로 저장
|
||||
|
||||
// if ctx.Written() {
|
||||
// return
|
||||
// }
|
||||
// if !pull.IsPull {
|
||||
// return
|
||||
// }
|
||||
|
||||
// if ctx.HasError() {
|
||||
// ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
|
||||
// ctx.Redirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, pull.Index))
|
||||
// return
|
||||
// }
|
||||
|
||||
// ai_service.AiService.CreateAiPullComment(*service, ctx, form, aiRequest, issueService)
|
||||
|
||||
// // ai_service.AiService.CreateAiPullComment(*service,
|
||||
// // ctx,
|
||||
// // &ai_service.AiReviewCommentResult{
|
||||
// // PullID: form.PullID,
|
||||
// // RepoID: form.RepoID,
|
||||
// // Content: result.Content,
|
||||
// // TreePath: result.TreePath,
|
||||
// // })
|
||||
|
||||
// }
|
||||
|
||||
// // TODOC AI 리뷰 삭제
|
|
@ -141,7 +141,17 @@ func MustAllowPulls(ctx *context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption optional.Option[bool]) {
|
||||
func MustAllowDiscussions(ctx *context.Context) {
|
||||
// var repositoryId = ctx.Repo.Repository.ID
|
||||
// TODO: check enable discussions && check enable Read
|
||||
var check = true
|
||||
if !check {
|
||||
ctx.NotFound("MustAllowDiscussions", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption optional.Option[bool], isDiscussionOption optional.Option[bool]) {
|
||||
var err error
|
||||
viewType := ctx.FormString("type")
|
||||
sortType := ctx.FormString("sort")
|
||||
|
@ -213,6 +223,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||
ReviewRequestedID: reviewRequestedID,
|
||||
ReviewedID: reviewedID,
|
||||
IsPull: isPullOption,
|
||||
IsDiscussion: isDiscussionOption,
|
||||
IssueIDs: nil,
|
||||
}
|
||||
if keyword != "" {
|
||||
|
@ -299,6 +310,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||
ProjectID: projectID,
|
||||
IsClosed: isShowClosed,
|
||||
IsPull: isPullOption,
|
||||
IsDiscussion: isDiscussionOption,
|
||||
LabelIDs: labelIDs,
|
||||
SortType: sortType,
|
||||
})
|
||||
|
@ -310,6 +322,8 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||
ctx.Data["IssueIndexerUnavailable"] = true
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: GetIssuesByIDs를 디스커션도 조회하도록 만들기
|
||||
issues, err = issues_model.GetIssuesByIDs(ctx, ids, true)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetIssuesByIDs", err)
|
||||
|
@ -486,9 +500,19 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||
}
|
||||
|
||||
func issueIDsFromSearch(ctx *context.Context, keyword string, opts *issues_model.IssuesOptions) ([]int64, error) {
|
||||
ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SearchIssues: %w", err)
|
||||
|
||||
var searchOptions = issue_indexer.ToSearchOptions(keyword, opts)
|
||||
var ids []int64
|
||||
var err error
|
||||
|
||||
if !searchOptions.IsDiscussion.ValueOrDefault(false) {
|
||||
ids, _, err = issue_indexer.SearchIssues(ctx, searchOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SearchIssues: %w", err)
|
||||
}
|
||||
} else {
|
||||
ids = []int64{1, 2}
|
||||
// TODO: 특정 레포지토리의 디스커션의 아이디들을 반환할수 있도록 로직 작성
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
@ -496,14 +520,26 @@ func issueIDsFromSearch(ctx *context.Context, keyword string, opts *issues_model
|
|||
// Issues render issues page
|
||||
func Issues(ctx *context.Context) {
|
||||
isPullList := ctx.Params(":type") == "pulls"
|
||||
isDiscussion := ctx.Params(":type") == "discussions"
|
||||
|
||||
if isPullList {
|
||||
// handle pull requests
|
||||
MustAllowPulls(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
ctx.Data["Title"] = ctx.Tr("repo.pulls")
|
||||
ctx.Data["PageIsPullList"] = true
|
||||
} else if isDiscussion {
|
||||
// handle discussions
|
||||
MustAllowDiscussions(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
ctx.Data["Title"] = ctx.Tr("repo.discussions")
|
||||
ctx.Data["PageIsDiscussionList"] = true
|
||||
} else {
|
||||
// handle issuses
|
||||
MustEnableIssues(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
|
@ -513,7 +549,7 @@ func Issues(ctx *context.Context) {
|
|||
ctx.Data["NewIssueChooseTemplate"] = issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo)
|
||||
}
|
||||
|
||||
issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), optional.Some(isPullList))
|
||||
issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), optional.Some(isPullList), optional.Some(isDiscussion))
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -292,7 +292,7 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
|
|||
ctx.Data["Title"] = milestone.Name
|
||||
ctx.Data["Milestone"] = milestone
|
||||
|
||||
issues(ctx, milestoneID, projectID, optional.None[bool]())
|
||||
issues(ctx, milestoneID, projectID, optional.None[bool](), optional.None[bool]())
|
||||
|
||||
ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
|
||||
ctx.Data["NewIssueChooseTemplate"] = len(ret.IssueTemplates) > 0
|
||||
|
|
|
@ -58,6 +58,7 @@ func RenderNewCodeCommentForm(ctx *context.Context) {
|
|||
ctx.HTML(http.StatusOK, tplNewComment)
|
||||
}
|
||||
|
||||
// 체크 코드 코멘트를 만드는 곳
|
||||
// CreateCodeComment will create a code comment including an pending review if required
|
||||
func CreateCodeComment(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.CodeCommentForm)
|
||||
|
|
|
@ -5,6 +5,7 @@ package web
|
|||
|
||||
import (
|
||||
gocontext "context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
|
@ -23,6 +24,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/modules/web/routing"
|
||||
api_repo_router "code.gitea.io/gitea/routers/api/v1/repo"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/routers/web/admin"
|
||||
"code.gitea.io/gitea/routers/web/auth"
|
||||
|
@ -47,7 +49,7 @@ import (
|
|||
"code.gitea.io/gitea/services/lfs"
|
||||
|
||||
_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
"gitea.com/go-chi/captcha"
|
||||
chi_middleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
|
@ -290,6 +292,19 @@ func Routes() *web.Route {
|
|||
return routes
|
||||
}
|
||||
|
||||
// bind binding an obj to a func(ctx *context.APIContext)
|
||||
func bind[T any](_ T) any {
|
||||
return func(ctx *context.Context) {
|
||||
theObj := new(T) // create a new form obj for every request but not use obj directly
|
||||
errs := binding.Bind(ctx.Req, theObj)
|
||||
if len(errs) > 0 {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "validationError", fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error()))
|
||||
return
|
||||
}
|
||||
web.SetForm(ctx, theObj)
|
||||
}
|
||||
}
|
||||
|
||||
var ignSignInAndCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true})
|
||||
|
||||
// registerRoutes register routes
|
||||
|
@ -841,6 +856,10 @@ func registerRoutes(m *web.Route) {
|
|||
}
|
||||
}
|
||||
|
||||
m.Group("/ai/pull/review", func() {
|
||||
m.Post("", bind(structs.CreateAiPullCommentForm{}), api_repo_router.CreateAiPullComment) // 라우팅
|
||||
})
|
||||
|
||||
m.Group("/org", func() {
|
||||
m.Group("/{org}", func() {
|
||||
m.Get("/members", org.Members)
|
||||
|
@ -1162,7 +1181,7 @@ func registerRoutes(m *web.Route) {
|
|||
// end "/{username}/{reponame}": view milestone, label, issue, pull, etc
|
||||
|
||||
m.Group("/{username}/{reponame}", func() {
|
||||
m.Group("/{type:issues|pulls}", func() {
|
||||
m.Group("/{type:issues|pulls|discussions}", func() {
|
||||
m.Get("", repo.Issues)
|
||||
m.Group("/{index}", func() {
|
||||
m.Get("", repo.ViewIssue)
|
||||
|
@ -1499,6 +1518,7 @@ func registerRoutes(m *web.Route) {
|
|||
m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange)
|
||||
m.Group("/reviews", func() {
|
||||
m.Get("/new_comment", repo.RenderNewCodeCommentForm)
|
||||
// 체크 = CreateCodeComment를 호출
|
||||
m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment)
|
||||
m.Post("/submit", web.Bind(forms.SubmitReviewForm{}), repo.SubmitReview)
|
||||
}, context.RepoMustNotBeArchived())
|
||||
|
|
165
services/ai/review.go
Normal file
165
services/ai/review.go
Normal file
|
@ -0,0 +1,165 @@
|
|||
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)
|
||||
|
||||
}
|
111
services/ai/review_test.go
Normal file
111
services/ai/review_test.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package ai
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
)
|
||||
|
||||
// MockAiRequester is a mock implementation of AiRequester
|
||||
type MockAiRequester struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockAiRequester) RequestReviewToAI(ctx *context.Context, request *AiReviewRequest) (*AiReviewResponse, error) {
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
args := m.Called(ctx, request)
|
||||
return args.Get(0).(*AiReviewResponse), args.Error(1)
|
||||
}
|
||||
|
||||
// MockDbAdapter is a mock implementation of DbAdapter
|
||||
type MockDbAdapter struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockDbAdapter) GetIssueByID(ctx *context.Context, id int64) (*issues.Issue, error) {
|
||||
args := m.Called(ctx, id)
|
||||
return args.Get(0).(*issues.Issue), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockDbAdapter) CreateAiPullComment(ctx *context.Context, opts *issues.CreateAiPullCommentOption) (*issues.AiPullComment, error) {
|
||||
args := m.Called(ctx, opts)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
commentID := args.Get(0).(*issues.AiPullComment)
|
||||
return commentID, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockDbAdapter) DeleteAiPullCommentByID(ctx *context.Context, id int64) error {
|
||||
args := m.Called(ctx, 1)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestCreateAiPullComment(t *testing.T) {
|
||||
// Set up the mock AiRequester
|
||||
mockRequester := new(MockAiRequester)
|
||||
aiService := &AiServiceImpl{}
|
||||
|
||||
// Set up the mock DbAdapter
|
||||
mockDbAdapter := new(MockDbAdapter)
|
||||
|
||||
|
||||
// Mock context and form
|
||||
ctx := &context.Context{}
|
||||
|
||||
var fileContent *[]structs.PathContentMap = new([]structs.PathContentMap)
|
||||
for i := 0; i < 100; i++ {
|
||||
*fileContent = append(*fileContent, structs.PathContentMap{
|
||||
TreePath: fmt.Sprintf("file%d.go", i),
|
||||
Content: fmt.Sprintf("code content %d", i),
|
||||
|
||||
|
||||
})
|
||||
|
||||
mockRequester.On("RequestReviewToAI", ctx, &AiReviewRequest{
|
||||
Branch: "main",
|
||||
TreePath: fmt.Sprintf("file%d.go", i),
|
||||
Content: fmt.Sprintf("code content %d", i),
|
||||
}).Return(&AiReviewResponse{
|
||||
Branch: "main",
|
||||
TreePath: fmt.Sprintf("file%d.go", i+100),
|
||||
Content: fmt.Sprintf("code content %d", i+100),
|
||||
}, nil)
|
||||
|
||||
}
|
||||
|
||||
form := &structs.CreateAiPullCommentForm{
|
||||
PullID: "123",
|
||||
Branch: "main",
|
||||
FileContents: fileContent,
|
||||
}
|
||||
|
||||
// Mock response from AI
|
||||
|
||||
|
||||
// Mock GetIssueByID
|
||||
issue := &issues.Issue{}
|
||||
mockDbAdapter.On("GetIssueByID", ctx, int64(123)).Return(issue, nil)
|
||||
|
||||
// Mock CreateAiPullComment
|
||||
comment := issues.AiPullComment{ID: 10}
|
||||
mockDbAdapter.On("CreateAiPullComment", ctx, mock.Anything).Return(&comment, nil)
|
||||
|
||||
// Call the method under test
|
||||
err := aiService.CreateAiPullComment(ctx, form, mockRequester, mockDbAdapter)
|
||||
|
||||
// Assert the expectations
|
||||
assert.NoError(t, err)
|
||||
mockRequester.AssertExpectations(t)
|
||||
mockDbAdapter.AssertExpectations(t)
|
||||
}
|
||||
|
||||
// TODOC delete 테스트
|
|
@ -621,12 +621,12 @@ func (f *MergePullRequestForm) Validate(req *http.Request, errs binding.Errors)
|
|||
|
||||
// CodeCommentForm form for adding code comments for PRs
|
||||
type CodeCommentForm struct {
|
||||
Origin string `binding:"Required;In(timeline,diff)"`
|
||||
Content string `binding:"Required"`
|
||||
Side string `binding:"Required;In(previous,proposed)"`
|
||||
Line int64
|
||||
TreePath string `form:"path" binding:"Required"`
|
||||
SingleReview bool `form:"single_review"`
|
||||
Origin string `binding:"Required;In(timeline,diff)"` // diff인지 timeline인지
|
||||
Content string `binding:"Required"` // 리뷰 내용 들어갈
|
||||
Side string `binding:"Required;In(previous,proposed)"` //
|
||||
Line int64 // 줄 수
|
||||
TreePath string `form:"path" binding:"Required"` // 해당 파일의 경로 같은 거
|
||||
SingleReview bool `form:"single_review"` //
|
||||
Reply int64 `form:"reply"`
|
||||
LatestCommitID string
|
||||
Files []string
|
||||
|
@ -902,3 +902,10 @@ func (f *DeadlineForm) Validate(req *http.Request, errs binding.Errors) binding.
|
|||
ctx := context.GetValidateContext(req)
|
||||
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type AiReviewRequestForm struct {
|
||||
|
||||
TreePath []string `form:"path" binding:"Required"` // 해당 파일의 경로 같은 거
|
||||
LatestCommitID string
|
||||
Files []string
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestLis
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 체크 코드 코멘트에 필요한 것들이 모여있음.
|
||||
// CreateCodeComment creates a comment on the code line
|
||||
func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue, line int64, content, treePath string, pendingReview bool, replyReviewID int64, latestCommitID string, attachments []string) (*issues_model.Comment, error) {
|
||||
var (
|
||||
|
@ -182,7 +182,7 @@ func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git.
|
|||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
// 체크 코드에 대한 코멘트가 작성되는 곳
|
||||
// createCodeComment creates a plain code comment at the specified line / path
|
||||
func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content, treePath string, line, reviewID int64, attachments []string) (*issues_model.Comment, error) {
|
||||
var commitID, patch string
|
||||
|
|
|
@ -152,6 +152,11 @@
|
|||
{{svg "octicon-link-external"}} {{ctx.Locale.Tr "repo.issues"}}
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
<!-- discussions -->
|
||||
<a class="{{if .PageIsDiscussionList}}active {{end}}item" href="{{.RepoLink}}/discussions" >
|
||||
{{ svg "octicon-comment-discussion" }} {{ctx.Locale.Tr "repo.discussions"}}
|
||||
</a>
|
||||
|
||||
{{if and .Repository.CanEnablePulls (.Permission.CanRead ctx.Consts.RepoUnitTypePullRequests)}}
|
||||
<a class="{{if .PageIsPullList}}active {{end}}item" href="{{.RepoLink}}/pulls">
|
||||
|
|
|
@ -19,9 +19,20 @@
|
|||
{{template "repo/issue/search" .}}
|
||||
{{if not .Repository.IsArchived}}
|
||||
{{if .PageIsIssueList}}
|
||||
<a class="ui small primary button issue-list-new" href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
|
||||
{{else}}
|
||||
<a class="ui small primary button new-pr-button issue-list-new{{if not .PullRequestCtx.Allowed}} disabled{{end}}" href="{{if .PullRequestCtx.Allowed}}{{.Repository.Link}}/compare/{{.Repository.DefaultBranch | PathEscapeSegments}}...{{if ne .Repository.Owner.Name .PullRequestCtx.BaseRepo.Owner.Name}}{{PathEscape .Repository.Owner.Name}}:{{end}}{{.Repository.DefaultBranch | PathEscapeSegments}}{{end}}">{{ctx.Locale.Tr "repo.pulls.new"}}</a>
|
||||
<a class="ui small primary button issue-list-new"
|
||||
href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">
|
||||
{{ctx.Locale.Tr "repo.issues.new"}}
|
||||
</a>
|
||||
{{else if .PageIsPullList}}
|
||||
<a class="ui small primary button new-pr-button issue-list-new{{if not .PullRequestCtx.Allowed}} disabled{{end}}"
|
||||
href="{{if .PullRequestCtx.Allowed}}{{.Repository.Link}}/compare/{{.Repository.DefaultBranch | PathEscapeSegments}}...{{if ne .Repository.Owner.Name .PullRequestCtx.BaseRepo.Owner.Name}}{{PathEscape .Repository.Owner.Name}}:{{end}}{{.Repository.DefaultBranch | PathEscapeSegments}}{{end}}">
|
||||
{{ctx.Locale.Tr "repo.pulls.new"}}
|
||||
</a>
|
||||
{{else if .PageIsDiscussionList}}
|
||||
<a class="ui small primary button new-discussion-button issue-list-new"
|
||||
href="{{.RepoLink}}/discussions/new">
|
||||
{{ctx.Locale.Tr "repo.discussions.new"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{if not .PageIsIssueList}}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
{{svg "octicon-milestone" 16 "tw-mr-2"}}
|
||||
{{else if .PageIsPullList}}
|
||||
{{svg "octicon-git-pull-request" 16 "tw-mr-2"}}
|
||||
{{else if .PageIsDiscussionList}}
|
||||
{{svg "octicon-comment-discussion" 16 "tw-mr-2"}}
|
||||
{{else}}
|
||||
{{svg "octicon-issue-opened" 16 "tw-mr-2"}}
|
||||
{{end}}
|
||||
|
|
|
@ -387,7 +387,7 @@
|
|||
{{if ne .Issue.DeadlineUnix 0}}
|
||||
{{svg "octicon-pencil"}}
|
||||
{{else}}
|
||||
{{svg "octicon-plus"}}
|
||||
{{svg "octicon-check"}}
|
||||
{{end}}
|
||||
</button>
|
||||
</form>
|
||||
|
|
35
templates/swagger/v1_json.tmpl
generated
35
templates/swagger/v1_json.tmpl
generated
|
@ -1009,6 +1009,41 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/ai/pull/review": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Create ai pull comment",
|
||||
"operationId": "repoCreateAiPullComment",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/CreateAiPullCommentForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"$ref": "#/responses/Attachment"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/gitignore/templates": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
|
Loading…
Reference in New Issue
Block a user