diff --git a/spx-backend/.env.dev b/spx-backend/.env.dev
index 388b2b16a..9a5b25bee 100644
--- a/spx-backend/.env.dev
+++ b/spx-backend/.env.dev
@@ -46,3 +46,7 @@ Iw==
-----END CERTIFICATE-----"
GOP_CASDOOR_ORGANIZATIONNAME="GoPlus"
GOP_CASDOOR_APPLICATIONNAME="application_x8aevk"
+
+# Anthropic Service
+ANTHROPIC_ENDPOINT=
+ANTHROPIC_API_KEY=
diff --git a/spx-backend/cmd/spx-backend/gop_autogen.go b/spx-backend/cmd/spx-backend/gop_autogen.go
index da827c12f..26e1e4831 100644
--- a/spx-backend/cmd/spx-backend/gop_autogen.go
+++ b/spx-backend/cmd/spx-backend/gop_autogen.go
@@ -95,6 +95,10 @@ type post_asset struct {
yap.Handler
*AppV2
}
+type post_copilot_message struct {
+ yap.Handler
+ *AppV2
+}
type post_project_release struct {
yap.Handler
*AppV2
@@ -192,7 +196,7 @@ func (this *AppV2) MainEntry() {
}
}
func (this *AppV2) Main() {
- yap.Gopt_AppV2_Main(this, new(delete_asset_id), new(delete_project_owner_name), new(delete_project_owner_name_liking), new(delete_user_username_following), new(get_asset_id), new(get_assets_list), new(get_project_release_owner_project_release), new(get_project_releases_list), new(get_project_owner_name), new(get_project_owner_name_liking), new(get_projects_list), new(get_user_username), new(get_user_username_following), new(get_users_list), new(get_util_upinfo), new(post_aigc_matting), new(post_asset), new(post_project_release), new(post_project), new(post_project_owner_name_liking), new(post_project_owner_name_view), new(post_user_username_following), new(post_util_fileurls), new(post_util_fmtcode), new(put_asset_id), new(put_project_owner_name), new(put_user))
+ yap.Gopt_AppV2_Main(this, new(delete_asset_id), new(delete_project_owner_name), new(delete_project_owner_name_liking), new(delete_user_username_following), new(get_asset_id), new(get_assets_list), new(get_project_release_owner_project_release), new(get_project_releases_list), new(get_project_owner_name), new(get_project_owner_name_liking), new(get_projects_list), new(get_user_username), new(get_user_username_following), new(get_users_list), new(get_util_upinfo), new(post_aigc_matting), new(post_asset), new(post_copilot_message), new(post_project_release), new(post_project), new(post_project_owner_name_liking), new(post_project_owner_name_view), new(post_user_username_following), new(post_util_fileurls), new(post_util_fmtcode), new(put_asset_id), new(put_project_owner_name), new(put_user))
}
//line cmd/spx-backend/delete_asset_#id.yap:6
func (this *delete_asset_id) Main(_gop_arg0 *yap.Context) {
@@ -953,6 +957,49 @@ func (this *post_asset) Main(_gop_arg0 *yap.Context) {
func (this *post_asset) Classfname() string {
return "post_asset"
}
+//line cmd/spx-backend/post_copilot_message.yap:10
+func (this *post_copilot_message) Main(_gop_arg0 *yap.Context) {
+ this.Handler.Main(_gop_arg0)
+//line cmd/spx-backend/post_copilot_message.yap:10:1
+ ctx := &this.Context
+//line cmd/spx-backend/post_copilot_message.yap:11:1
+ if
+//line cmd/spx-backend/post_copilot_message.yap:11:1
+ _, isAuthed := ensureAuthedUser(ctx); !isAuthed {
+//line cmd/spx-backend/post_copilot_message.yap:12:1
+ return
+ }
+//line cmd/spx-backend/post_copilot_message.yap:15:1
+ params := &controller.GenerateMessageParams{}
+//line cmd/spx-backend/post_copilot_message.yap:16:1
+ if !parseJSON(ctx, params) {
+//line cmd/spx-backend/post_copilot_message.yap:17:1
+ return
+ }
+//line cmd/spx-backend/post_copilot_message.yap:19:1
+ if
+//line cmd/spx-backend/post_copilot_message.yap:19:1
+ ok, msg := params.Validate(); !ok {
+//line cmd/spx-backend/post_copilot_message.yap:20:1
+ replyWithCodeMsg(ctx, errorInvalidArgs, msg)
+//line cmd/spx-backend/post_copilot_message.yap:21:1
+ return
+ }
+//line cmd/spx-backend/post_copilot_message.yap:24:1
+ result, err := this.ctrl.GenerateMessage(ctx.Context(), params)
+//line cmd/spx-backend/post_copilot_message.yap:25:1
+ if err != nil {
+//line cmd/spx-backend/post_copilot_message.yap:26:1
+ replyWithInnerError(ctx, err)
+//line cmd/spx-backend/post_copilot_message.yap:27:1
+ return
+ }
+//line cmd/spx-backend/post_copilot_message.yap:29:1
+ this.Json__1(result)
+}
+func (this *post_copilot_message) Classfname() string {
+ return "post_copilot_message"
+}
//line cmd/spx-backend/post_project-release.yap:10
func (this *post_project_release) Main(_gop_arg0 *yap.Context) {
this.Handler.Main(_gop_arg0)
diff --git a/spx-backend/cmd/spx-backend/post_copilot_message.yap b/spx-backend/cmd/spx-backend/post_copilot_message.yap
new file mode 100644
index 000000000..91c28f251
--- /dev/null
+++ b/spx-backend/cmd/spx-backend/post_copilot_message.yap
@@ -0,0 +1,29 @@
+// Generate a message by sending a list of input messages.
+//
+// Request:
+// POST /copilot/message
+
+import (
+ "github.com/goplus/builder/spx-backend/internal/controller"
+)
+
+ctx := &Context
+if _, isAuthed := ensureAuthedUser(ctx); !isAuthed {
+ return
+}
+
+params := &controller.GenerateMessageParams{}
+if !parseJSON(ctx, params) {
+ return
+}
+if ok, msg := params.Validate(); !ok {
+ replyWithCodeMsg(ctx, errorInvalidArgs, msg)
+ return
+}
+
+result, err := ctrl.GenerateMessage(ctx.Context(), params)
+if err != nil {
+ replyWithInnerError(ctx, err)
+ return
+}
+json result
diff --git a/spx-backend/go.mod b/spx-backend/go.mod
index 3764e2ded..140abd668 100644
--- a/spx-backend/go.mod
+++ b/spx-backend/go.mod
@@ -10,11 +10,12 @@ require (
github.com/qiniu/x v1.13.10
gocloud.dev v0.36.0 // indirect
golang.org/x/mod v0.17.0
- golang.org/x/tools v0.19.0
+ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
)
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
+ github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.8
github.com/casdoor/casdoor-go-sdk v0.36.0
github.com/goplus/gop v1.2.6
github.com/qiniu/go-sdk/v7 v7.18.0
@@ -28,23 +29,25 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
- github.com/golang/protobuf v1.5.3 // indirect
- github.com/googleapis/gax-go/v2 v2.12.0 // indirect
+ github.com/googleapis/gax-go/v2 v2.12.5 // indirect
github.com/goplus/gogen v1.15.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/tidwall/gjson v1.14.4 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.1 // indirect
+ github.com/tidwall/sjson v1.2.5 // indirect
go.opencensus.io v0.24.0 // indirect
- golang.org/x/net v0.22.0 // indirect
- golang.org/x/oauth2 v0.14.0 // indirect
- golang.org/x/sync v0.6.0 // indirect
- golang.org/x/sys v0.18.0 // indirect
- golang.org/x/text v0.14.0 // indirect
+ golang.org/x/net v0.27.0 // indirect
+ golang.org/x/oauth2 v0.21.0 // indirect
+ golang.org/x/sync v0.7.0 // indirect
+ golang.org/x/sys v0.22.0 // indirect
+ golang.org/x/text v0.16.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
- google.golang.org/api v0.151.0 // indirect
- google.golang.org/appengine v1.6.8 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
- google.golang.org/grpc v1.59.0 // indirect
- google.golang.org/protobuf v1.31.0 // indirect
+ google.golang.org/api v0.189.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade // indirect
+ google.golang.org/grpc v1.64.1 // indirect
+ google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/spx-backend/go.sum b/spx-backend/go.sum
index ad1ab0fb8..4607ce589 100644
--- a/spx-backend/go.sum
+++ b/spx-backend/go.sum
@@ -1,10 +1,13 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
-cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic=
-cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
-cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
-cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
-cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
+cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
+cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
+cloud.google.com/go/auth v0.7.2 h1:uiha352VrCDMXg+yoBtaD0tUF4Kv9vrtrWPYXwutnDE=
+cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs=
+cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
+cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
+cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
+cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
+cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w=
@@ -14,46 +17,48 @@ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
+github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.8 h1:ss/c/eeyILgoK2sMsTJdcdLdhY3wZSt//+nanM41B9w=
+github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.8/go.mod h1:GJxtdOs9K4neo8Gg65CjJ7jNautmldGli5/OFNabOoo=
github.com/aws/aws-sdk-go v1.49.0 h1:g9BkW1fo9GqKfwg2+zCD+TW/D36Ux+vtfJ8guF4AYmY=
github.com/aws/aws-sdk-go v1.49.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
-github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk=
-github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
-github.com/aws/aws-sdk-go-v2/config v1.26.1 h1:z6DqMxclFGL3Zfo+4Q0rLnAZ6yVkzCRxhRMsiRQnD1o=
-github.com/aws/aws-sdk-go-v2/config v1.26.1/go.mod h1:ZB+CuKHRbb5v5F0oJtGdhFTelmrxd4iWO1lf0rQwSAg=
-github.com/aws/aws-sdk-go-v2/credentials v1.16.12 h1:v/WgB8NxprNvr5inKIiVVrXPuuTegM+K8nncFkr1usU=
-github.com/aws/aws-sdk-go-v2/credentials v1.16.12/go.mod h1:X21k0FjEJe+/pauud82HYiQbEr9jRKY3kXEIQ4hXeTQ=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw=
+github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
+github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
+github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
+github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 h1:FnLf60PtjXp8ZOzQfhJVsqF0OtYKQZWQfqOLshh8YXg=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7/go.mod h1:tDVvl8hyU6E9B8TrnNrZQEVkQlB8hjJwcgpPhgtlnNg=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9/go.mod h1:dN/Of9/fNZet7UrQQ6kTDo/VSwKPIq94vjlU16bRARc=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 h1:iEAeF6YC3l4FzlJPP9H3Ko1TXpdjdqWffxXjp8SY6uk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9/go.mod h1:kjsXoK23q9Z/tLBrckZLLyvjhZoS+AGrzqzUfEClvMM=
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 h1:Keso8lIOS+IzI2MkPZyK6G0LYcK3My2LQ+T5bxghEAY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5/go.mod h1:vADO6Jn+Rq4nDtfwNjhgR84qkZwiC6FqCaXdw/kYwjA=
-github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM=
-github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38=
-github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 h1:5UYvv8JUvllZsRnfrcMQ+hJ9jNICmcgKPAO1CER25Wg=
-github.com/aws/aws-sdk-go-v2/service/sts v1.26.5/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU=
-github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
-github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
+github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
+github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
+github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
+github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
+github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
+github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/casdoor/casdoor-go-sdk v0.36.0 h1:0kK98ptEhqSb2/QR3EO5DvOHTa/Rr9y1Lc7D/jOSFmE=
github.com/casdoor/casdoor-go-sdk v0.36.0/go.mod h1:hVSgmSdwTCsBEJNt9r2K5aLVsoeMc37/N4Zzescy5SA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -67,8 +72,12 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
-github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
@@ -96,17 +105,14 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
-github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
@@ -114,14 +120,14 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
-github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
-github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
-github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
+github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
+github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
github.com/goplus/gogen v1.15.2 h1:Q6XaSx/Zi5tWnjfAziYsQI6Jv6MgODRpFtOYqNkiiqM=
github.com/goplus/gogen v1.15.2/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
github.com/goplus/gop v1.2.6 h1:kog3c5Js+8EopqmI4+CwueXsqibnBwYVt5q5N7juRVY=
@@ -173,9 +179,29 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
+github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
+go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
+go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
+go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
+go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
+go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
+go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
gocloud.dev v0.36.0 h1:q5zoXux4xkOZP473e1EZbG8Gq9f0vlg1VNH5Du/ybus=
gocloud.dev v0.36.0/go.mod h1:bLxah6JQVKBaIxzsr5BQLYB4IYdWHkMZdzCXlo6F0gg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -183,8 +209,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
+golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -202,17 +228,17 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
-golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
-golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
+golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
-golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
+golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
+golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
-golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -223,8 +249,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -233,12 +259,11 @@ 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=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-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/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
-golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+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=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -246,34 +271,32 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
-golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
-google.golang.org/api v0.151.0 h1:FhfXLO/NFdJIzQtCqjpysWwqKk8AzGWBUhMIx67cVDU=
-google.golang.org/api v0.151.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg=
+google.golang.org/api v0.189.0 h1:equMo30LypAkdkLMBqfeIqtyAnlyig1JSZArl4XPwdI=
+google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
-google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB5KrjI2q0wCOWEOIh0OEsleqakHJg=
-google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY=
-google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY=
-google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
+google.golang.org/genproto v0.0.0-20240722135656-d784300faade h1:lKFsS7wpngDgSCeFn7MoLy+wBDQZ1UQIJD4UNM1Qvkg=
+google.golang.org/genproto v0.0.0-20240722135656-d784300faade/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY=
+google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 h1:QW9+G6Fir4VcRXVH8x3LilNAb6cxBGLa6+GM4hRwexE=
+google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3/go.mod h1:kdrSS/OiLkPrNUpzD4aHgCq2rVuC/YRxok32HXZ4vRE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade h1:oCRSWfwGXQsqlVdErcyTt4A93Y8fo0/9D4b1gnI++qo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
-google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
+google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
+google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -283,10 +306,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
diff --git a/spx-backend/internal/controller/controller.go b/spx-backend/internal/controller/controller.go
index b892302df..ef2bd724b 100644
--- a/spx-backend/internal/controller/controller.go
+++ b/spx-backend/internal/controller/controller.go
@@ -9,6 +9,8 @@ import (
"strconv"
"time"
+ "github.com/anthropics/anthropic-sdk-go"
+ anthropicOption "github.com/anthropics/anthropic-sdk-go/option"
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
_ "github.com/go-sql-driver/mysql"
"github.com/goplus/builder/spx-backend/internal/aigc"
@@ -36,10 +38,11 @@ type contextKey struct {
// Controller is the controller for the service.
type Controller struct {
- db *gorm.DB
- kodo *kodoConfig
- aigcClient *aigc.AigcClient
- casdoorClient casdoorClient
+ db *gorm.DB
+ kodo *kodoConfig
+ aigcClient *aigc.AigcClient
+ casdoorClient casdoorClient
+ anthropicClient *anthropic.Client
}
// New creates a new controller.
@@ -62,12 +65,17 @@ func New(ctx context.Context) (*Controller, error) {
kodoConfig := newKodoConfig(logger)
aigcClient := aigc.NewAigcClient(mustEnv(logger, "AIGC_ENDPOINT"))
casdoorClient := newCasdoorClient(logger)
+ anthropicClient := anthropic.NewClient(
+ anthropicOption.WithAPIKey(mustEnv(logger, "ANTHROPIC_API_KEY")),
+ anthropicOption.WithBaseURL(mustEnv(logger, "ANTHROPIC_ENDPOINT")),
+ )
return &Controller{
- db: db,
- kodo: kodoConfig,
- aigcClient: aigcClient,
- casdoorClient: casdoorClient,
+ db: db,
+ kodo: kodoConfig,
+ aigcClient: aigcClient,
+ casdoorClient: casdoorClient,
+ anthropicClient: anthropicClient,
}, nil
}
diff --git a/spx-backend/internal/controller/copilot.go b/spx-backend/internal/controller/copilot.go
new file mode 100644
index 000000000..319f88196
--- /dev/null
+++ b/spx-backend/internal/controller/copilot.go
@@ -0,0 +1,144 @@
+package controller
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/anthropics/anthropic-sdk-go"
+ "github.com/goplus/builder/spx-backend/internal/copilot"
+ "github.com/goplus/builder/spx-backend/internal/log"
+)
+
+const (
+ MAX_CONTENT_TEXT_LENGTH = 5000
+ MAX_MESSAGE_COUNT = 50
+ MAX_TOKENS = 1024
+)
+
+type Role string
+
+const (
+ RoleUser Role = "user"
+ RoleCopilot Role = "copilot"
+)
+
+func (r Role) Validate() (ok bool, msg string) {
+ switch r {
+ case RoleUser, RoleCopilot:
+ return true, ""
+ default:
+ return false, "invalid role"
+ }
+}
+
+type ContentType string
+
+const (
+ ContentTypeText ContentType = "text"
+)
+
+func (c ContentType) Validate() (ok bool, msg string) {
+ switch c {
+ case ContentTypeText:
+ return true, ""
+ default:
+ return false, "invalid content type"
+ }
+}
+
+type Content struct {
+ Type ContentType `json:"type"`
+ Text string `json:"text"`
+}
+
+func (c *Content) Validate() (ok bool, msg string) {
+ if ok, msg := c.Type.Validate(); !ok {
+ return false, msg
+ }
+ if c.Text == "" {
+ return false, "missing text"
+ }
+ if len(c.Text) > MAX_CONTENT_TEXT_LENGTH {
+ return false, "text too long"
+ }
+ return true, ""
+}
+
+type Message struct {
+ Role Role `json:"role"`
+ Content Content `json:"content"`
+}
+
+func (m *Message) Validate() (ok bool, msg string) {
+ if ok, msg := m.Role.Validate(); !ok {
+ return false, fmt.Sprintf("invalid role: %s", msg)
+ }
+ if ok, msg := m.Content.Validate(); !ok {
+ return false, fmt.Sprintf("invalid content: %s", msg)
+ }
+ return true, ""
+}
+
+type GenerateMessageParams struct {
+ Messages []Message `json:"messages"`
+}
+
+func (p *GenerateMessageParams) Validate() (ok bool, msg string) {
+ if len(p.Messages) > MAX_MESSAGE_COUNT {
+ return false, "too many messages"
+ }
+ for i, m := range p.Messages {
+ if ok, msg := m.Validate(); !ok {
+ return false, fmt.Sprintf("invalid message at index %d: %s", i, msg)
+ }
+ }
+ return true, ""
+}
+
+type GenerateMessageResult Message
+
+// GenerateMessage generates response message based on input messages.
+func (ctrl *Controller) GenerateMessage(ctx context.Context, params *GenerateMessageParams) (*GenerateMessageResult, error) {
+ logger := log.GetReqLogger(ctx)
+ messages := []anthropic.MessageParam{}
+ for _, m := range params.Messages {
+ var message anthropic.MessageParam
+ if m.Role == RoleUser {
+ message = anthropic.NewUserMessage(anthropic.NewTextBlock(m.Content.Text))
+ } else if m.Role == RoleCopilot {
+ message = anthropic.NewAssistantMessage(anthropic.NewTextBlock(m.Content.Text))
+ }
+ messages = append(messages, message)
+ }
+ generatedMsg, err := ctrl.anthropicClient.Messages.New(ctx, anthropic.MessageNewParams{
+ Model: anthropic.F(anthropic.ModelClaude3_5Haiku20241022),
+ MaxTokens: anthropic.F(int64(MAX_TOKENS)),
+ // Now we are using APIs from api.gptsapi.net for testing. `[]anthropic.TextBlockParam` is not supported.
+ // So we use `anthropic.Raw` to pass the string-type system prompt. TODO:
+ // * Switch to official API
+ // * Use `[]anthropic.TextBlockParam` instead of `string`
+ // * Enable prompt caching if it helps
+ System: anthropic.Raw[[]anthropic.TextBlockParam](copilot.SystemPrompt),
+ Messages: anthropic.F(messages),
+ })
+ if err != nil {
+ logger.Printf("failed to generate message: %v", err)
+ return nil, err
+ }
+ generatedContent := generatedMsg.Content
+ if len(generatedContent) == 0 {
+ logger.Printf("empty content from anthropic")
+ return nil, fmt.Errorf("empty content")
+ }
+ if len(generatedContent) > 1 {
+ logger.Printf("too much content from anthropic: %d", len(generatedContent))
+ return nil, fmt.Errorf("too much content")
+ }
+ return &GenerateMessageResult{
+ Role: RoleCopilot,
+ Content: Content{
+ Type: ContentTypeText,
+ Text: generatedContent[0].Text,
+ },
+ }, nil
+}
diff --git a/spx-backend/internal/controller/copilot_test.go b/spx-backend/internal/controller/copilot_test.go
new file mode 100644
index 000000000..9d4c95108
--- /dev/null
+++ b/spx-backend/internal/controller/copilot_test.go
@@ -0,0 +1,87 @@
+package controller
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGenerateMessageParamsValidate(t *testing.T) {
+ t.Run("Normal", func(t *testing.T) {
+ params := &GenerateMessageParams{}
+ ok, msg := params.Validate()
+ assert.True(t, ok)
+ assert.Empty(t, msg)
+ })
+
+ t.Run("Too many messages", func(t *testing.T) {
+ params := &GenerateMessageParams{
+ Messages: make([]Message, MAX_MESSAGE_COUNT+1),
+ }
+ ok, msg := params.Validate()
+ assert.False(t, ok)
+ assert.Equal(t, "too many messages", msg)
+ })
+
+ t.Run("Invalid message role", func(t *testing.T) {
+ params := &GenerateMessageParams{
+ Messages: []Message{
+ {
+ Role: Role("invalid"),
+ },
+ },
+ }
+ ok, msg := params.Validate()
+ assert.False(t, ok)
+ assert.Equal(t, "invalid message at index 0: invalid role: invalid role", msg)
+ })
+
+ t.Run("Invalid message content type", func(t *testing.T) {
+ params := &GenerateMessageParams{
+ Messages: []Message{
+ {
+ Role: RoleUser,
+ Content: Content{
+ Type: ContentType("invalid"),
+ },
+ },
+ },
+ }
+ ok, msg := params.Validate()
+ assert.False(t, ok)
+ assert.Equal(t, "invalid message at index 0: invalid content: invalid content type", msg)
+ })
+
+ t.Run("Invalid message empty content text", func(t *testing.T) {
+ params := &GenerateMessageParams{
+ Messages: []Message{
+ {
+ Role: RoleUser,
+ Content: Content{
+ Type: ContentTypeText,
+ },
+ },
+ },
+ }
+ ok, msg := params.Validate()
+ assert.False(t, ok)
+ assert.Equal(t, "invalid message at index 0: invalid content: missing text", msg)
+ })
+
+ t.Run("Invalid message too long content text", func(t *testing.T) {
+ params := &GenerateMessageParams{
+ Messages: []Message{
+ {
+ Role: RoleUser,
+ Content: Content{
+ Type: ContentTypeText,
+ Text: "a" + string(make([]byte, MAX_CONTENT_TEXT_LENGTH)),
+ },
+ },
+ },
+ }
+ ok, msg := params.Validate()
+ assert.False(t, ok)
+ assert.Equal(t, "invalid message at index 0: invalid content: text too long", msg)
+ })
+}
diff --git a/spx-backend/internal/copilot/custom_elements.md b/spx-backend/internal/copilot/custom_elements.md
new file mode 100644
index 000000000..36c8172ea
--- /dev/null
+++ b/spx-backend/internal/copilot/custom_elements.md
@@ -0,0 +1,47 @@
+# `code-link`
+
+Display a link to a code location in the project. By clicking on the link, the user will be navigated to the code location. A location can be a position or a range.
+
+## Attributes
+
+### `file`
+
+Text document URI, e.g., `file:///NiuXiaoQi.spx`
+
+### `position`
+
+`${line},${column}`, e.g., `10,20`.
+
+`line` & `column` are numbers start from 1. `1,1` means the first column of the first line.
+
+### `range`
+
+`${startLine},${startColumn}-${endLine}${endColumn}`, e.g., `10,20-12,10`
+
+`startLine`, `startColumn`, `endLine`, `endColumn` are numbers start from 1. `10,20-12,10` means the range from line 10, column 20 to line 12, column 10. The end position is exclusive.
+
+## Examples
+
+### Basic example
+
+```xml
+
+```
+
+This is a link to line 10, column 20 in the code of sprite `NiuXiaoQi`.
+
+### Example with text
+
+```xml
+Details
+```
+
+This is a link to the beginning of the code of stage with the text "Details".
+
+### Example with range
+
+```xml
+onStart
+```
+
+This is a link to a range (line 2, column 1 to line 2, column 10) in the code of stage with the text "onStart".
diff --git a/spx-backend/internal/copilot/gop_defs.json b/spx-backend/internal/copilot/gop_defs.json
new file mode 100644
index 000000000..fa0a383ce
--- /dev/null
+++ b/spx-backend/internal/copilot/gop_defs.json
@@ -0,0 +1 @@
+[{"name":"for_iterate","sample":"for i, v <- set { ... }","desc":"Iterate within given set"},{"name":"for_loop_with_condition","sample":"for condition { ... }","desc":"Loop with condition"},{"name":"for_loop_with_range","sample":"for i <- start:end { ... }","desc":"Loop with range"},{"name":"func_declaration","sample":"func name(params) { ... }","desc":"Function declaration, e.g., `func add(a int, b int) int {}`"},{"name":"if_else_statement","sample":"if condition { ... } else { ... }","desc":"If else statement"},{"name":"if_statement","sample":"if condition { ... }","desc":"If statement"},{"name":"println","sample":"println msg, ...","desc":"Print line, e.g., `println \"Hello, world!\"`"},{"name":"var_declaration","sample":"var name type","desc":"Variable declaration, e.g., `var count int`"}]
diff --git a/spx-backend/internal/copilot/prompt.go b/spx-backend/internal/copilot/prompt.go
new file mode 100644
index 000000000..a1461053a
--- /dev/null
+++ b/spx-backend/internal/copilot/prompt.go
@@ -0,0 +1,47 @@
+package copilot
+
+import (
+ _ "embed"
+ "strings"
+ "text/template"
+)
+
+// For details about maintaining `*_defs.json` files, see:
+// spx-gui/src/components/editor/code-editor/document-base/helpers.ts
+
+//go:embed gop_defs.json
+var gopDefs string
+
+//go:embed spx_defs.json
+var spxDefs string
+
+//go:embed custom_elements.md
+var customElements string
+
+//go:embed system_prompt.md
+var systemPromptTpl string
+
+type systemPromptTplData struct {
+ GopDefs string
+ SpxDefs string
+ CustomElements string
+}
+
+var SystemPrompt string
+
+func init() {
+ tplData := systemPromptTplData{
+ GopDefs: gopDefs,
+ SpxDefs: spxDefs,
+ CustomElements: customElements,
+ }
+ tpl, err := template.New("system-prompt").Parse(systemPromptTpl)
+ if err != nil {
+ panic(err)
+ }
+ var sb strings.Builder
+ if err := tpl.Execute(&sb, tplData); err != nil {
+ panic(err)
+ }
+ SystemPrompt = sb.String()
+}
diff --git a/spx-backend/internal/copilot/spx_defs.json b/spx-backend/internal/copilot/spx_defs.json
new file mode 100644
index 000000000..fd5e31104
--- /dev/null
+++ b/spx-backend/internal/copilot/spx_defs.json
@@ -0,0 +1 @@
+{"game":[{"pkg":"spx","name":"backdropIndex","sample":"backdropIndex","desc":"Get the index of the current backdrop"},{"pkg":"spx","name":"backdropName","sample":"backdropName","desc":"Get the name of the current backdrop"},{"pkg":"spx","name":"broadcast","sample":"broadcast message","desc":"Broadcast a message, e.g., `broadcast \"message\"`"},{"pkg":"spx","name":"broadcast","sample":"broadcast message, wait","desc":"Broadcast a message with waiting, e.g., `broadcast \"message\", true`"},{"pkg":"spx","name":"broadcast","sample":"broadcast message, data, wait","desc":"Broadcast a message with data and waiting, e.g., `broadcast \"message\", data, true`"},{"pkg":"spx","name":"changeVolume","sample":"changeVolume dVolume","desc":"Change the volume for sounds with given volume change, e.g., `changeVolume 10`"},{"pkg":"spx","name":"exit","sample":"exit","desc":"Exit the game"},{"pkg":"spx","name":"onClick","sample":"onClick => { ... }","desc":"Listen to stage clicked"},{"pkg":"spx","name":"onKey","sample":"onKey key, => { ... }","desc":"Listen to given key pressed"},{"pkg":"spx","name":"onKey","sample":"onKey keys, key => { ... }","desc":"Listen to given keys pressed (and get the key)"},{"pkg":"spx","name":"onKey","sample":"onKey keys, => { ... }","desc":"Listen to given keys pressed"},{"pkg":"spx","name":"onMsg","sample":"onMsg (message, data) => { ... }","desc":"Listen to any message broadcasted, get the broadcasted message and data"},{"pkg":"spx","name":"onMsg","sample":"onMsg message, => { ... }","desc":"Listen to specific message broadcasted"},{"pkg":"spx","name":"onStart","sample":"onStart => { ... }","desc":"Listen to game start"},{"pkg":"spx","name":"getWidget","sample":"getWidget(type, name)","desc":"Get the widget by given name"},{"pkg":"spx","name":"keyPressed","sample":"keyPressed(key)","desc":"Check if given key is currently pressed, e.g., `keyPressed(KeyA)`"},{"pkg":"spx","name":"mouseHitItem","sample":"mouseHitItem","desc":"Get the topmost sprite which is hit by mouse, e.g., `hitSprite, ok := mouseHitItem`"},{"pkg":"spx","name":"mousePressed","sample":"mousePressed","desc":"Check if the mouse is currently pressed"},{"pkg":"spx","name":"mouseX","sample":"mouseX","desc":"Get X position of the mouse"},{"pkg":"spx","name":"mouseY","sample":"mouseY","desc":"Get Y position of the mouse"},{"pkg":"spx","name":"nextBackdrop","sample":"nextBackdrop","desc":"Switch to the next backdrop"},{"pkg":"spx","name":"onAnyKey","sample":"onAnyKey key => { ... }","desc":"Listen to any key pressed"},{"pkg":"spx","name":"onBackdrop","sample":"onBackdrop backdrop => { ... }","desc":"Listen to backdrop switching"},{"pkg":"spx","name":"onBackdrop","sample":"onBackdrop backdrop, => { ... }","desc":"Listen to switching to specific backdrop"},{"pkg":"spx","name":"play","sample":"play sound","desc":"Play given sound, e.g., `play explosion`"},{"pkg":"spx","name":"play","sample":"play sound, wait","desc":"Play given sound with waiting, e.g., `play explosion, true`"},{"pkg":"spx","name":"play","sample":"play sound, options","desc":"Play given sound with options, e.g., `play explosion, { Loop: true }`"},{"pkg":"spx","name":"play","sample":"play soundName","desc":"Play sound with given name, e.g., `play \"explosion\"`"},{"pkg":"spx","name":"play","sample":"play soundName, wait","desc":"Play sound with given name and waiting, e.g., `play \"explosion\", true`"},{"pkg":"spx","name":"play","sample":"play soundName, options","desc":"Play sound with given name and options, e.g., `play \"explosion\", { Loop: true }`"},{"pkg":"spx","name":"prevBackdrop","sample":"prevBackdrop","desc":"Switch to the previous backdrop"},{"pkg":"spx","name":"rand","sample":"rand(from, to)","desc":"Generate a random number, e.g., `rand(1, 10)`"},{"pkg":"spx","name":"setVolume","sample":"setVolume volume","desc":"Set the volume for sounds, e.g., `setVolume 100`"},{"pkg":"spx","name":"startBackdrop","sample":"startBackdrop backdropName","desc":"Set the current backdrop by specifying name, e.g., `startBackdrop \"backdrop1\"`"},{"pkg":"spx","name":"startBackdrop","sample":"startBackdrop backdropName, wait","desc":"Set the current backdrop by specifying name, with waiting, e.g., `startBackdrop \"backdrop1\", true`"},{"pkg":"spx","name":"stopAllSounds","sample":"stopAllSounds","desc":"Stop all playing sounds"},{"pkg":"spx","name":"volume","sample":"volume","desc":"Get the volume for sounds"},{"pkg":"spx","name":"wait","sample":"wait seconds","desc":"Block current execution (coroutine) for given seconds, e.g., `wait 0.5`"}],"sprite":[{"pkg":"spx","name":"animate","sample":"animate name","desc":"Play animation with given name, e.g., `animate \"jump\"`"},{"pkg":"spx","name":"bounceOffEdge","sample":"bounceOffEdge","desc":"Check & bounce off current sprite if touching the edge"},{"pkg":"spx","name":"changeHeading","sample":"changeHeading dDirection","desc":"Change heading with given direction change, e.g., `changeHeading 90`"},{"pkg":"spx","name":"changeSize","sample":"changeSize dSize","desc":"Change the size of current sprite, e.g., `changeSize 1`"},{"pkg":"spx","name":"changeXYpos","sample":"changeXYpos dX, dY","desc":"Move with given position (X, Y) change, e.g., `changeXYpos 10, 10`"},{"pkg":"spx","name":"changeXpos","sample":"changeXpos dX","desc":"Move with given X position change, e.g., `changeXpos 10`"},{"pkg":"spx","name":"changeYpos","sample":"changeYpos dY","desc":"Move with given Y position change, e.g., `changeYpos 10`"},{"pkg":"spx","name":"clone","sample":"clone","desc":"Make a clone of current sprite, with optional data (for `onCloned` callback)"},{"pkg":"spx","name":"costumeName","sample":"costumeName","desc":"The name of the current costume"},{"pkg":"spx","name":"die","sample":"die","desc":"Let current sprite die. Animation bound to state \"die\" will be played."},{"pkg":"spx","name":"distanceTo","sample":"distanceTo(sprite)","desc":"Get the distance from current sprite to given sprite"},{"pkg":"spx","name":"distanceTo","sample":"distanceTo(spriteName)","desc":"Get the distance from current sprite to the sprite with given name, e.g., `distanceTo(\"Enemy\")`"},{"pkg":"spx","name":"distanceTo","sample":"distanceTo(obj)","desc":"Get the distance from current sprite to given object, e.g., `distanceTo(Mouse)`"},{"pkg":"spx","name":"glide","sample":"glide x, y, seconds","desc":"Move to given position (X, Y), using a glide animation"},{"pkg":"spx","name":"glide","sample":"glide sprite, seconds","desc":"Move to given sprite, using a glide animation"},{"pkg":"spx","name":"glide","sample":"glide spriteName, seconds","desc":"Move to the sprite with given name, using a glide animation"},{"pkg":"spx","name":"glide","sample":"glide obj, seconds","desc":"Move to given obj, using a glide animation, e.g., `glide Mouse, 2`"},{"pkg":"spx","name":"goto","sample":"goto sprite","desc":"Move to given sprite"},{"pkg":"spx","name":"goto","sample":"goto spriteName","desc":"Move to the sprite with given name, e.g., `goto \"Enemy\"`"},{"pkg":"spx","name":"goto","sample":"goto obj","desc":"Move to given obj, e.g., `goto Mouse`"},{"pkg":"spx","name":"heading","sample":"heading","desc":"Get current heading direction"},{"pkg":"spx","name":"hide","sample":"hide","desc":"Make current sprite invisible"},{"pkg":"spx","name":"move","sample":"move distance","desc":"Move given distance, toward current heading"},{"pkg":"spx","name":"onCloned","sample":"onCloned => { ... }","desc":"Listen to current sprite cloned"},{"pkg":"spx","name":"onMoving","sample":"onMoving => { ... }","desc":"Listen to current sprite moving (position change)"},{"pkg":"spx","name":"onTouchStart","sample":"onTouchStart => { ... }","desc":"Listen to current sprite starting to be touched by any other sprites"},{"pkg":"spx","name":"onTouchStart","sample":"onTouchStart sprite => { ... }","desc":"Listen to current sprite starting to be touched by any other sprites (and get the sprite)"},{"pkg":"spx","name":"onTouchStart","sample":"onTouchStart spriteName, => { ... }","desc":"Listen to current sprite starting to be touched by sprite of given name"},{"pkg":"spx","name":"onTouchStart","sample":"onTouchStart spriteName, sprite => { ... }","desc":"Listen to current sprite starting to be touched by sprite of given name (and get the sprite)"},{"pkg":"spx","name":"onTurning","sample":"onTurning => { ... }","desc":"Listen to current sprite turning (heading change)"},{"pkg":"spx","name":"say","sample":"say word","desc":"Make the sprite say some word, e.g., `say \"Hello!\"`"},{"pkg":"spx","name":"say","sample":"say word, seconds","desc":"Make the sprite say some word with duration, e.g., `say \"Hello!\", 2`"},{"pkg":"spx","name":"setCostume","sample":"setCostume name","desc":"Set the current costume by specifying name, e.g., `setCostume \"happy\"`"},{"pkg":"spx","name":"setHeading","sample":"setHeading direction","desc":"Set heading to given value, e.g., `setHeading Up`"},{"pkg":"spx","name":"setRotationStyle","sample":"setRotationStyle style","desc":"Set the rotation style of the sprite, e.g., `setRotationStyle LeftRight`"},{"pkg":"spx","name":"setSize","sample":"setSize size","desc":"Set the size of current sprite, e.g., `setSize 2`"},{"pkg":"spx","name":"setXYpos","sample":"setXYpos x, y","desc":"Move to given position, e.g., `setXYpos 100, 100`"},{"pkg":"spx","name":"setXpos","sample":"setXpos x","desc":"Move to given X position, e.g., `setXpos 100`"},{"pkg":"spx","name":"setYpos","sample":"setYpos y","desc":"Move to given Y position, e.g., `setYpos 100`"},{"pkg":"spx","name":"show","sample":"show","desc":"Make current sprite visible"},{"pkg":"spx","name":"size","sample":"size","desc":"Get the size of current sprite"},{"pkg":"spx","name":"onClick","sample":"onClick => { ... }","desc":"Listen to current sprite clicked"},{"pkg":"spx","name":"onKey","sample":"onKey key, => { ... }","desc":"Listen to given key pressed"},{"pkg":"spx","name":"onKey","sample":"onKey keys, key => { ... }","desc":"Listen to given keys pressed (and get the key)"},{"pkg":"spx","name":"onKey","sample":"onKey keys, => { ... }","desc":"Listen to given keys pressed"},{"pkg":"spx","name":"onMsg","sample":"onMsg (message, data) => { ... }","desc":"Listen to any message broadcasted, get the broadcasted message and data"},{"pkg":"spx","name":"onMsg","sample":"onMsg message, => { ... }","desc":"Listen to specific message broadcasted"},{"pkg":"spx","name":"onStart","sample":"onStart => { ... }","desc":"Listen to game start"},{"pkg":"spx","name":"step","sample":"step distance","desc":"Step given distance, toward current heading. Animation bound to state \"step\" will be played"},{"pkg":"spx","name":"think","sample":"think word","desc":"Make the sprite think of some word, e.g., `think \"Wow!\"`"},{"pkg":"spx","name":"think","sample":"think word, seconds","desc":"Make the sprite think of some word with duration, e.g., `think \"Wow!\", 2`"},{"pkg":"spx","name":"touching","sample":"touching(spriteName)","desc":"Check if current sprite touching sprite with given name"},{"pkg":"spx","name":"touching","sample":"touching(sprite)","desc":"Check if current sprite touching given sprite"},{"pkg":"spx","name":"touching","sample":"touching(obj)","desc":"Check if current sprite touching given object, e.g., `touching(Mouse)`"},{"pkg":"spx","name":"turn","sample":"turn degree","desc":"Turn with given degree, e.g., `turn 90`"},{"pkg":"spx","name":"turn","sample":"turn direction","desc":"Turn with given direction, e.g., `turn Left`"},{"pkg":"spx","name":"turnTo","sample":"turnTo sprite","desc":"Turn heading to given sprite"},{"pkg":"spx","name":"turnTo","sample":"turnTo spriteName","desc":"Turn heading to given sprite by name, e.g., `turnTo \"Enemy\"`"},{"pkg":"spx","name":"turnTo","sample":"turnTo degree","desc":"Turn heading to given degree, e.g., `turnTo 90`"},{"pkg":"spx","name":"turnTo","sample":"turnTo direction","desc":"Turn heading to given direction, e.g., `turnTo Left`"},{"pkg":"spx","name":"turnTo","sample":"turnTo obj","desc":"Turn heading to given object, e.g., `turnTo Mouse`"},{"pkg":"spx","name":"visible","sample":"visible","desc":"If current sprite visible"},{"pkg":"spx","name":"xpos","sample":"xpos","desc":"Get current X position"},{"pkg":"spx","name":"ypos","sample":"ypos","desc":"Get current Y position"}],"others":[{"pkg":"spx","name":"Down","sample":"Down","desc":"Down direction"},{"pkg":"spx","name":"Edge","sample":"Edge","desc":"Any edge"},{"pkg":"spx","name":"EdgeBottom","sample":"EdgeBottom","desc":"Bottom edge"},{"pkg":"spx","name":"EdgeLeft","sample":"EdgeLeft","desc":"Left edge"},{"pkg":"spx","name":"EdgeRight","sample":"EdgeRight","desc":"Right edge"},{"pkg":"spx","name":"EdgeTop","sample":"EdgeTop","desc":"Top edge"},{"pkg":"spx","name":"Left","sample":"Left","desc":"Left direction"},{"pkg":"spx","name":"LeftRight","sample":"LeftRight","desc":"Left-Right"},{"pkg":"spx","name":"Mouse","sample":"Mouse","desc":"Mouse"},{"pkg":"spx","name":"Next","sample":"Next","desc":"Next item"},{"pkg":"spx","name":"None","sample":"None","desc":"Don't Rotate"},{"pkg":"spx","name":"Normal","sample":"Normal","desc":"Normal"},{"pkg":"spx","name":"PlayContinue","sample":"PlayContinue","desc":"Continue"},{"pkg":"spx","name":"PlayPause","sample":"PlayPause","desc":"Pause"},{"pkg":"spx","name":"PlayResume","sample":"PlayResume","desc":"Resume"},{"pkg":"spx","name":"PlayRewind","sample":"PlayRewind","desc":"Rewind"},{"pkg":"spx","name":"PlayStop","sample":"PlayStop","desc":"Stop"},{"pkg":"spx","name":"Prev","sample":"Prev","desc":"Previous item"},{"pkg":"spx","name":"Right","sample":"Right","desc":"Right direction"},{"pkg":"spx","name":"Up","sample":"Up","desc":"Up direction"}]}
diff --git a/spx-backend/internal/copilot/system_prompt.md b/spx-backend/internal/copilot/system_prompt.md
new file mode 100644
index 000000000..c02ca8019
--- /dev/null
+++ b/spx-backend/internal/copilot/system_prompt.md
@@ -0,0 +1,156 @@
+
+
+
+
+{{.GopDefs}}
+
+
+
+
+
+{{.SpxDefs}}
+
+
+
+
+
+{{.CustomElements}}
+
+
+
+
+You are a copilot who helps children to develop games in Go+ Builder. You are expert in Go/Go+ language and spx game engine.
+
+# About Go+
+
+The Go+ programming language is designed for engineering, STEM education, and data science. Go+ is superset of the Go language, while with some special features.
+
+Like Go, Go+ is a statically typed, compiled language with a focus on simplicity and efficiency.
+
+### How Go+ simplifies Go's expressions
+
+Different from the function call style of most languages, Go+ recommends command-line style code:
+
+```gop
+println "Hello world"
+```
+
+NOTE: It is only suitable for function-calls whose return values are not used.
+
+Apart from that, Go+ simplifies the expression of the most common tasks, such as:
+
+| Go code | Go+ code | Note |
+| ---- | ---- | ---- |
+| package main
import "fmt"
func main() { fmt.Println("Hi") } | import "fmt"
fmt.Println("Hi") | Program structure: Go+ allows omitting `package main` and `func main` |
+| fmt.Println("Hi") | println("Hi") | More builtin functions: It simplifies the expression of the most common tasks |
+| fmt.Println("Hi") | println "Hi" | Command-line style code: It reduces the number of parentheses in the code as much as possible, making it closer to natural language |
+| a := []int{1, 2, 3} | a := [1, 2, 3] | List literals |
+| a := map[string]int{ "Monday": 1, "Tuesday": 2, } | a := { "Monday": 1, "Tuesday": 2, } | Mapping literals |
+| OnStart(func() {...}) OnMsg(msg, func() {...}) | onStart => {...} onMsg msg, => {...} | Lambda expressions |
+| type Rect struct { Width float64 Height float64 }
func Area() float64 { return Width * Height } | Go+ Classfiles: We can express OOP with global variables and functions. |
+| this.Step__0(5.5) this.Step__1(5.5, "run") this.Step__2(10) | step 5.5 step 5.5, "run" step 10 | Function overloading: Go+ allows t calling multiple functions (they are defined as `Xxx__0`, `Xxx__1`, etc.) with the same name (`xxx`) in Go+ but different implementations. |
+
+In document `gop-defs.json`, you can find some definitions for Go+ language syntax.
+
+### About Go+ Classfiles
+
+Go+ classfiles provide a mechanism to abstract domain knowledge, making Go+ more accessible and friendly to various user groups, especially those new to programming or unfamiliar with object-oriented programming concepts. Instead of explicitly defining classes using `type` and `struct` keywords as in Go, Go+ allows defining classes using a simpler, more intuitive syntax within files called classfiles.
+
+**Key Aspects of Go+ Classfiles:**
+
+* **Simplified Syntax:** Classfiles define classes using a syntax closer to sequential programming. Variables and functions are declared directly within the classfile, eliminating the need for explicit `struct` and method declarations. This makes it easier for beginners to grasp the concept of classes.
+
+* **Abstraction of Domain Knowledge:** The primary purpose is to abstract domain-specific knowledge. This is achieved by defining a base class for a project and organizing related worker classes under it. This structure helps organize code and promotes reusability.
+
+* **Project and Worker Classes:** A classfile typically consists of a project class and multiple worker classes. The project class represents the main entity, while worker classes represent supporting components. A classfile can also consist of only a project class without any worker classes.
+
+# About spx
+
+spx is a Scratch-like 2D Game Engine for STEM education. It is designed for children to learn programming by developing games. spx is developed based on Go+ classfiles. In spx, there are two types of classes: `Game` classes and `Sprite` classes. The `Game` class is the project class that represents the whole game, while `Sprite` classes are worker classes which are used to define game objects.
+
+In document `spx-defs.json`, you can find definitions for most APIs of spx game engine.
+
+Here is a example spx project with detailed comments:
+
+
+
+
+
+ Code for the stage (coresponding to class `Game`)
+
+var (
+ // By declaring sprite variables in stage code, we get the reference of the sprite instance
+ XiaoQi Sprite
+ Apple Sprite
+ // Here is a normal variable declared in stage code, it can be accessed by game and all sprites code
+ currentTime int
+)
+
+// Typically we put all listener-binding code top-level
+onStart => {
+ for {
+ // Here we use a `for` loop to increment the `currentTime` variable by 1 every second
+ currentTime++
+ wait 1
+ }
+}
+
+
+
+
+ Code for sprite `Apple`
+
+onClick => {
+ // We can use `broadcast` to send a message to all sprites
+ broadcast "apple-clicked"
+}
+
+
+
+
+ Code for sprite `XiaoQi`
+
+onClick => {
+ // We can use `play` to play a sound
+ play "explosion"
+ say "I'm clicked"
+}
+
+// `onMsg` is used to bind listeners to event "message received".
+// Here we listen to the message "apple-clicked" which is sent by sprite `Apple`
+// Remind that `"apple-clicked"` & the callback lambda `=> { ... }` are both arguments of `onMsg`, so we use `,` to separate them
+onMsg "apple-clicked", => {
+ play "explosion"
+ say "Apple is clicked"
+}
+
+onStart => {
+ println "distance from Apple:", distanceTo(Apple)
+}
+
+
+
+
+
+# About Go+ Builder
+
+Go+ Builder provides a visual programming interface for children to learn programming by developing games. The game engine used in Go+ Builder is spx, which is based on Go+ classfiles. The users of Go+ Builder are expected to be children aged around 10 who are new to programming.
+
+# Requirements about responses
+
+* Remind that the user is a child who is new to programming. Do not use complex terms or concepts. Do not respond with inappropriate content.
+* Only respond messages about learning and programming in Go+ Builder. For other messages, just ignore them. Typical messages include:
+ - Provide guidance on how to archive a specific task with spx.
+ - Explain Go+ language syntax or spx API.
+ - Explain or review a spx code snippet.
+ - Help with debugging spx code.
+* Do not talk about things that you are not sure about:
+ - Do not respond with how to interact with the UI of Go+ Builder, because you are not able to see the UI.
+ - Do not respond with how to do non-programming related tasks in Go+ Builder. Because you lack the knowledge about that.
+* Do not make up APIs or features that do not exist in Go/Go+ or spx. For details that your are not sure about, think about what it is like in Go language. Remember that Go+ is a superset of Go language.
+* Respond in language the same as the input language.
+* Talk to the user in a friendly and encouraging manner. Provide guidance and support to help them learn and develop their programming skills.
+* You are not allowed to provide any external links or resources to the user.
+* Do not provide any personal information or ask for personal information from the user.
+* If possible, use short and concise responses.
+* There are some special elements you can use in your responses, you can find them in the document `custom-elements.md`. Use them just like any other HTML tags, to make your responses more interactive and informative.
diff --git a/spx-gui/src/apis/aigc.ts b/spx-gui/src/apis/aigc.ts
index b238ca5bc..c40dcbe1f 100644
--- a/spx-gui/src/apis/aigc.ts
+++ b/spx-gui/src/apis/aigc.ts
@@ -1,5 +1,5 @@
/**
- * @desc AI-related APIs of spx-backend
+ * @desc AIGC-related APIs of spx-backend
*/
import { client } from './common'
diff --git a/spx-gui/src/apis/copilot.ts b/spx-gui/src/apis/copilot.ts
new file mode 100644
index 000000000..aeb12a24c
--- /dev/null
+++ b/spx-gui/src/apis/copilot.ts
@@ -0,0 +1,19 @@
+/**
+ * @desc Copilot-related APIs of spx-backend
+ */
+
+import { client } from './common'
+
+export type MessageContent = {
+ type: 'text'
+ text: string
+}
+
+export type Message = {
+ role: 'user' | 'copilot'
+ content: MessageContent
+}
+
+export async function generateMessage(messages: Message[], signal?: AbortSignal) {
+ return (await client.post('/copilot/message', { messages }, { timeout: 15 * 1000, signal })) as Message
+}
diff --git a/spx-gui/src/components/editor/code-editor/code-editor.ts b/spx-gui/src/components/editor/code-editor/code-editor.ts
index 0d15f88d2..4e83bf981 100644
--- a/spx-gui/src/components/editor/code-editor/code-editor.ts
+++ b/spx-gui/src/components/editor/code-editor/code-editor.ts
@@ -2,6 +2,7 @@ import * as lsp from 'vscode-languageserver-protocol'
import { Disposable } from '@/utils/disposable'
import Emitter from '@/utils/emitter'
import { insertSpaces, tabSize } from '@/utils/spx/highlighter'
+import type { I18n } from '@/utils/i18n'
import type { Runtime } from '@/models/runtime'
import type { Project } from '@/models/project'
import { Copilot } from './copilot'
@@ -336,10 +337,11 @@ export class CodeEditor extends Disposable {
constructor(
private project: Project,
private runtime: Runtime,
- private monaco: Monaco
+ private monaco: Monaco,
+ private i18n: I18n
) {
super()
- this.copilot = new Copilot()
+ this.copilot = new Copilot(i18n, project)
this.documentBase = new DocumentBase()
this.lspClient = new SpxLSPClient(project)
this.resourceReferencesProvider = new ResourceReferencesProvider(this.lspClient)
diff --git a/spx-gui/src/components/editor/code-editor/context.ts b/spx-gui/src/components/editor/code-editor/context.ts
index 96509dcd1..0ed4a66cf 100644
--- a/spx-gui/src/components/editor/code-editor/context.ts
+++ b/spx-gui/src/components/editor/code-editor/context.ts
@@ -136,7 +136,7 @@ export function useProvideCodeEditorCtx(
untilNotNull(runtimeRef),
untilQueryLoaded(monacoQueryRet)
])
- return new CodeEditor(project, runtime, monaco)
+ return new CodeEditor(project, runtime, monaco, i18n)
},
{ en: 'Failed to load code editor', zh: '加载代码编辑器失败' }
)
diff --git a/spx-gui/src/components/editor/code-editor/copilot.ts b/spx-gui/src/components/editor/code-editor/copilot.ts
index b3a761b10..0d39f0db0 100644
--- a/spx-gui/src/components/editor/code-editor/copilot.ts
+++ b/spx-gui/src/components/editor/code-editor/copilot.ts
@@ -1,27 +1,161 @@
import { Disposable } from '@/utils/disposable'
+import type { I18n } from '@/utils/i18n'
+import { generateMessage, type Message } from '@/apis/copilot'
+import type { Project } from '@/models/project'
+import type { Stage } from '@/models/stage'
+import type { Sprite } from '@/models/sprite'
+import type { Sound } from '@/models/sound'
+import type { Widget } from '@/models/widget'
+import type { Animation } from '@/models/animation'
+import type { Backdrop } from '@/models/backdrop'
+import type { Chat, ChatContext, ChatMessage, ICopilot } from './ui/code-editor-ui'
import { makeBasicMarkdownString, type BasicMarkdownString } from './common'
-import type { Chat, ChatContext, ICopilot } from './ui/code-editor-ui'
+
+const maxChatMessageCount = 20
+const maxCodeLength = 2000
export class Copilot extends Disposable implements ICopilot {
+ constructor(
+ private i18n: I18n,
+ private project: Project
+ ) {
+ super()
+ }
+
+ private makeContextMessage(): Message {
+ let currentTarget: string
+ if (this.project.selectedSprite != null) {
+ currentTarget = this.i18n.t({
+ en: `Sprite ${this.project.selectedSprite.name}`,
+ zh: `精灵 ${this.project.selectedSprite.name}`
+ })
+ } else if (this.project.selected?.type === 'stage') {
+ currentTarget = this.i18n.t({ en: 'Stage', zh: '舞台' })
+ } else {
+ throw new Error(`Invalid project selected state: ${this.project.selected?.type}`)
+ }
+ const projectInfo = JSON.stringify(getProjectInfo(this.project))
+ return {
+ role: 'user',
+ content: {
+ type: 'text',
+ text: this.i18n.t({
+ en: `Here is some context information:
+I opened a project:
+
+${projectInfo}
+
+Now I am working on ${currentTarget}`,
+ zh: `这里是一些上下文信息:
+我打开了一个项目:
+
+${projectInfo}
+
+我现在正在编辑${currentTarget}`
+ })
+ }
+ }
+ }
+
+ private chatMessage2Message({ role, content }: ChatMessage): Message {
+ const contentText = typeof content.value === 'string' ? content.value : this.i18n.t(content.value)
+ return {
+ role,
+ content: {
+ type: 'text',
+ text: contentText
+ }
+ }
+ }
+
+ private makeSkippingMessage(toSkip: number): Message {
+ return {
+ role: 'user',
+ content: {
+ type: 'text',
+ text: this.i18n.t({
+ en: `(${toSkip} messages skipped)`,
+ zh: `(跳过了 ${toSkip} 条消息)`
+ })
+ }
+ }
+ }
+
async getChatCompletion(ctx: ChatContext, chat: Chat): Promise {
- console.warn('TODO', ctx, chat)
- return Promise.race([
- new Promise((_, reject) => ctx.signal.addEventListener('abort', () => reject(ctx.signal.reason))),
- new Promise((resolve) => setTimeout(resolve, 1000)).then(() =>
- makeBasicMarkdownString(`
-I do not have access to real-time information, including the specifics of the \`turn\` API for the spx game engine. My knowledge is based on the data I was trained on. To understand the \`turn\` API, you should consult the official spx documentation or the source code itself. The GitHub repository you linked ([https://github.com/goplus/spx](https://github.com/goplus/spx)) is the best place to find this information.
+ const messages = [this.makeContextMessage()]
+ const toSkip = chat.messages.length - maxChatMessageCount
+ // skip chat messages in range `[1, toSkip]`
+ chat.messages.forEach((message, i) => {
+ if (i === 0) {
+ messages.push(this.chatMessage2Message(message))
+ if (toSkip > 0) messages.push(this.makeSkippingMessage(toSkip))
+ return
+ }
+ if (i > toSkip) messages.push(this.chatMessage2Message(message))
+ })
+ const message = await generateMessage(messages, ctx.signal)
+ return makeBasicMarkdownString(message.content.text)
+ }
+}
+
+function getProjectInfo(project: Project) {
+ const codeLength = [project.stage.code.length, ...project.sprites.map((s) => s.code.length)].reduce(
+ (a, b) => a + b,
+ 0
+ )
+ const codeSampleRatio = Math.min(maxCodeLength / codeLength, 1)
+ return {
+ name: project.name,
+ desc: project.description,
+ instructions: project.instructions,
+ stage: getStageInfo(project.stage, codeSampleRatio),
+ sprites: project.sprites.map((s) => getSpriteInfo(s, codeSampleRatio)),
+ sounds: project.sounds.map(getSoundInfo)
+ }
+}
-Based on the context of the provided tutorials, it's likely that the \`turn\` function or method within the spx game engine is related to game logic and sprite movement. The tutorials show examples of sprites moving randomly using functions like \`step\` and \`turn\`. It's probable that \`turn\` modifies the rotation or orientation of a sprite, potentially in conjunction with \`step\` to control its movement direction. However, without access to the engine's API documentation, this is just speculation.
+function getStageInfo(stage: Stage, codeSampleRatio: number) {
+ return {
+ mapSize: stage.getMapSize(),
+ widgets: stage.widgets.map(getWidgetInfo),
+ backdrops: stage.backdrops.map(getBackdropInfo),
+ defaultBackdrop: stage.defaultBackdrop?.name,
+ code: getCodeInfo(stage.code, codeSampleRatio)
+ }
+}
-To find the precise functionality of \`turn\`, I recommend the following steps:
+function getSpriteInfo(sprite: Sprite, codeSampleRatio: number) {
+ return {
+ name: sprite.name,
+ visible: sprite.visible,
+ animations: sprite.animations.map(getAnimationInfo),
+ code: getCodeInfo(sprite.code, codeSampleRatio)
+ }
+}
+
+function getSoundInfo(sound: Sound) {
+ return sound.name
+}
-1. **Check the spx documentation:** Look for official documentation or tutorials that explain the API.
-2. **Examine the source code:** The GitHub repository contains the source code. Search for the \`turn\` function or method within the relevant files. The surrounding code will provide context and explain its usage.
-3. **Search the spx issues and discussions:** The GitHub repository likely has issues or discussions where users have asked about the \`turn\` API. These discussions might provide helpful insights.
+function getBackdropInfo(backdrop: Backdrop) {
+ return backdrop.name
+}
+
+function getWidgetInfo(widget: Widget) {
+ return {
+ type: widget.type,
+ name: widget.name
+ }
+}
+
+function getAnimationInfo(animation: Animation) {
+ return animation.name
+}
-Remember to always refer to the official documentation for the most accurate and up-to-date information.
-`)
- )
- ])
+function getCodeInfo(code: string, codeSampleRatio: number) {
+ return {
+ len: code.length,
+ // TODO: consider sampling code based on AST
+ sampled: code.slice(0, Math.floor(code.length * codeSampleRatio))
}
}
diff --git a/spx-gui/src/components/editor/code-editor/document-base/helpers.ts b/spx-gui/src/components/editor/code-editor/document-base/helpers.ts
new file mode 100644
index 000000000..35eb3217e
--- /dev/null
+++ b/spx-gui/src/components/editor/code-editor/document-base/helpers.ts
@@ -0,0 +1,59 @@
+/**
+ * @file Definition-related helpers
+ * @desc Here we define some helper functions to get definitions for Go+ and SPX.
+ * They are expected to be generated, then copy-pasted to spx-backend code as part of prompt for Copilot.
+ */
+
+import { type DefinitionDocumentationItem } from '../common'
+import * as gopDefinitions from './gop'
+import * as spxDefinitions from './spx'
+
+function transformItems(items: DefinitionDocumentationItem[]) {
+ function simplifyPackage(pkg: string | undefined) {
+ if (pkg === 'github.com/goplus/spx') return 'spx'
+ return pkg
+ }
+ const result = items.map((d) => {
+ return {
+ pkg: simplifyPackage(d.definition.package),
+ name: d.definition.name,
+ sample: d.overview,
+ desc: typeof d.detail.value === 'string' ? d.detail.value : d.detail.value.en
+ }
+ })
+ return result
+}
+
+// Run `copy(getGopDefinitions())` / `copy(getSpxDefinitions())` in browser console to copy generated definitions.
+;(globalThis as any).getGopDefinitions = () => JSON.stringify(transformItems(Object.values(gopDefinitions)))
+;(globalThis as any).getSpxDefinitions = () => {
+ const items = Object.values(spxDefinitions)
+ const gameItems: DefinitionDocumentationItem[] = []
+ const spriteItems: DefinitionDocumentationItem[] = []
+ const others: DefinitionDocumentationItem[] = []
+ items.forEach((item) => {
+ const name = item.definition.name ?? ''
+ if (name.startsWith('Game.'))
+ gameItems.push({
+ ...item,
+ definition: {
+ ...item.definition,
+ name: name.slice(5)
+ }
+ })
+ else if (name.startsWith('Sprite.'))
+ spriteItems.push({
+ ...item,
+ definition: {
+ ...item.definition,
+ name: name.slice(7)
+ }
+ })
+ else others.push(item)
+ })
+ return JSON.stringify({
+ game: transformItems(gameItems),
+ sprite: transformItems(spriteItems),
+ others: transformItems(others)
+ })
+}
diff --git a/spx-gui/src/components/editor/code-editor/document-base/index.ts b/spx-gui/src/components/editor/code-editor/document-base/index.ts
index 9f55dc76c..6b8095e54 100644
--- a/spx-gui/src/components/editor/code-editor/document-base/index.ts
+++ b/spx-gui/src/components/editor/code-editor/document-base/index.ts
@@ -2,6 +2,7 @@ import { Disposable } from '@/utils/disposable'
import { type DefinitionIdentifier, type DefinitionDocumentationItem, stringifyDefinitionId } from '../common'
import * as gopDefinitions from './gop'
import * as spxDefinitions from './spx'
+import './helpers'
export class DocumentBase extends Disposable {
private storage = new Map()
diff --git a/spx-gui/src/components/editor/code-editor/ui/copilot/index.ts b/spx-gui/src/components/editor/code-editor/ui/copilot/index.ts
index d8835d640..c5aacbf62 100644
--- a/spx-gui/src/components/editor/code-editor/ui/copilot/index.ts
+++ b/spx-gui/src/components/editor/code-editor/ui/copilot/index.ts
@@ -75,10 +75,7 @@ export type ChatTopicFixProblem = {
export type ChatTopic = ChatTopicInspire | ChatTopicExplain | ChatTopicReview | ChatTopicFixProblem
-export enum MessageRole {
- User,
- Copilot
-}
+export type MessageRole = 'user' | 'copilot'
export type ChatMessage = {
role: MessageRole
@@ -201,8 +198,8 @@ export class CopilotController extends Disposable {
const currentChat = this.currentChat
if (currentChat == null) throw new Error('No active chat')
const messages = currentChat.rounds.flatMap((round) => {
- const roundMessages = [{ role: MessageRole.User, content: round.problem }]
- if (round.answer != null) roundMessages.push({ role: MessageRole.Copilot, content: round.answer })
+ const roundMessages: ChatMessage[] = [{ role: 'user', content: round.problem }]
+ if (round.answer != null) roundMessages.push({ role: 'copilot', content: round.answer })
return roundMessages
})
return {
diff --git a/spx-gui/src/components/editor/code-editor/ui/markdown/MarkdownView.vue b/spx-gui/src/components/editor/code-editor/ui/markdown/MarkdownView.vue
index 4c0ec3f66..e2b6e56d9 100644
--- a/spx-gui/src/components/editor/code-editor/ui/markdown/MarkdownView.vue
+++ b/spx-gui/src/components/editor/code-editor/ui/markdown/MarkdownView.vue
@@ -24,7 +24,7 @@ const basicComponents = {
/**
* Usage:
* ```html
- *
+ *
* Default link text here
*
* ```
diff --git a/spx-gui/src/models/widget/monitor.ts b/spx-gui/src/models/widget/monitor.ts
index 877769aa9..249a3b9cc 100644
--- a/spx-gui/src/models/widget/monitor.ts
+++ b/spx-gui/src/models/widget/monitor.ts
@@ -37,7 +37,7 @@ export class Monitor extends BaseWidget {
}
constructor(name: string, { label, variableName, ...extraInits }: MonitorInits) {
- super(name, extraInits)
+ super(name, 'monitor', extraInits)
this.label = label ?? ''
this.variableName = variableName ?? ''
return reactive(this) as this
diff --git a/spx-gui/src/models/widget/widget.ts b/spx-gui/src/models/widget/widget.ts
index f27ffff1c..6195c6377 100644
--- a/spx-gui/src/models/widget/widget.ts
+++ b/spx-gui/src/models/widget/widget.ts
@@ -1,8 +1,8 @@
import { reactive } from 'vue'
+import { nanoid } from 'nanoid'
import { Disposable } from '@/utils/disposable'
import { validateWidgetName } from '../common/asset-name'
import type { Stage } from '../stage'
-import { nanoid } from 'nanoid'
export type BaseWidgetInits = {
id?: string
@@ -13,12 +13,14 @@ export type BaseWidgetInits = {
}
export type BaseRawWidgetConfig = Omit & {
+ type?: string
builder_id?: string
name?: string
}
export class BaseWidget extends Disposable {
id: string
+ type: string
private stage: Stage | null = null
setStage(stage: Stage | null) {
@@ -52,9 +54,10 @@ export class BaseWidget extends Disposable {
this.visible = visible
}
- constructor(name: string, inits?: BaseWidgetInits) {
+ constructor(name: string, type: string, inits?: BaseWidgetInits) {
super()
this.name = name
+ this.type = type
this.x = inits?.x ?? 0
this.y = inits?.y ?? 0
this.size = inits?.size ?? 1
@@ -63,13 +66,15 @@ export class BaseWidget extends Disposable {
return reactive(this) as this
}
- static load({ builder_id: id, name, ...inits }: BaseRawWidgetConfig) {
+ static load({ builder_id: id, type, name, ...inits }: BaseRawWidgetConfig) {
if (name == null) throw new Error('name expected for widget')
- return new BaseWidget(name, { ...inits, id })
+ if (type == null) throw new Error('type expected for widget')
+ return new BaseWidget(name, type, { ...inits, id })
}
export(): BaseRawWidgetConfig {
return {
+ type: this.type,
name: this.name,
x: this.x,
y: this.y,