diff --git a/api/api.go b/api/api.go
index 2d63b30..d9316e1 100644
--- a/api/api.go
+++ b/api/api.go
@@ -32,7 +32,7 @@ func init() {
group.GET("/g/:gizmoId/c/:convId", GC)
group.GET(("/gpts/mine"), Mine)
- // s.BindHandler("/_next/data/*any", Next)
+ s.BindHandler("/_next/data/*any", ProxyNext)
group.GET("/login", Login)
group.POST("/login", LoginPost)
diff --git a/api/index.go b/api/index.go
index d1a76a3..ffa18ac 100644
--- a/api/index.go
+++ b/api/index.go
@@ -10,7 +10,8 @@ import (
func Index(r *ghttp.Request) {
- if r.Session.MustGet("userToken").IsEmpty() {
+ ctx := r.GetCtx()
+ if r.Session.MustGet("offical-session").IsEmpty() {
r.Response.RedirectTo("/login")
// r.Response.Writer.Write([]byte("Hello XyHelper"))
return
@@ -55,19 +56,22 @@ func Index(r *ghttp.Request) {
}`
propsJson := gjson.New(props)
- propsJson.Set("query.model", model)
- propsJson.Set("buildId", config.BuildId)
+ if model != "" {
+ propsJson.Set("query.model", model)
+ }
+ propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)
- r.Response.WriteTpl("chat-"+config.BuildDate+".html", g.Map{
+ r.Response.WriteTpl(config.CacheBuildId+"/chat.html", g.Map{
"props": propsJson,
"arkoseUrl": config.ArkoseUrl,
"assetPrefix": config.AssetPrefix,
+ "envScript": config.GetEnvScript(ctx),
})
}
func C(r *ghttp.Request) {
-
- if r.Session.MustGet("userToken").IsEmpty() {
+ ctx := r.GetCtx()
+ if r.Session.MustGet("offical-session").IsEmpty() {
r.Response.RedirectTo("/login")
return
}
@@ -111,20 +115,21 @@ func C(r *ghttp.Request) {
propsJson := gjson.New(props)
propsJson.Set("query.default.1", convId)
- propsJson.Set("buildId", config.BuildId)
+ propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)
- r.Response.WriteTpl("chat-"+config.BuildDate+".html", g.Map{
+ r.Response.WriteTpl(config.CacheBuildId+"/chat.html", g.Map{
"props": propsJson,
"arkoseUrl": config.ArkoseUrl,
"assetPrefix": config.AssetPrefix,
+ "envScript": config.GetEnvScript(ctx),
})
}
// Discovery 发现
func Discovery(r *ghttp.Request) {
- if r.Session.MustGet("userToken").IsEmpty() {
+ if r.Session.MustGet("offical-session").IsEmpty() {
r.Response.RedirectTo("/login")
return
}
@@ -163,19 +168,20 @@ func Discovery(r *ghttp.Request) {
}
`
propsJson := gjson.New(props)
- propsJson.Set("buildId", config.BuildId)
+ propsJson.Set("buildId", config.CacheBuildId)
- r.Response.WriteTpl("discovery-"+config.BuildDate+".html", g.Map{
+ r.Response.WriteTpl(config.CacheBuildId+"/discovery.html", g.Map{
"arkoseUrl": config.ArkoseUrl,
"props": propsJson,
"assetPrefix": config.AssetPrefix,
+ "envScript": config.GetEnvScript(r.GetCtx()),
})
}
// Editor 编辑器
func Editor(r *ghttp.Request) {
- if r.Session.MustGet("userToken").IsEmpty() {
+ if r.Session.MustGet("offical-session").IsEmpty() {
r.Response.RedirectTo("/login")
return
}
@@ -215,7 +221,7 @@ func Editor(r *ghttp.Request) {
}
`
propsJson := gjson.New(props)
- propsJson.Set("buildId", config.BuildId)
+ propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)
// if slug != "" {
@@ -224,17 +230,18 @@ func Editor(r *ghttp.Request) {
// }
// propsJson.Dump()
- r.Response.WriteTpl("editor-"+config.BuildDate+".html", g.Map{
+ r.Response.WriteTpl(config.CacheBuildId+"/editor.html", g.Map{
"arkoseUrl": config.ArkoseUrl,
"props": propsJson,
"assetPrefix": config.AssetPrefix,
+ "envScript": config.GetEnvScript(r.GetCtx()),
})
}
// Slug 编辑器
func Slug(r *ghttp.Request) {
- if r.Session.MustGet("userToken").IsEmpty() {
+ if r.Session.MustGet("offical-session").IsEmpty() {
r.Response.RedirectTo("/login")
return
}
@@ -277,20 +284,21 @@ func Slug(r *ghttp.Request) {
propsJson := gjson.New(props)
propsJson.Set("query.slug", slug)
- propsJson.Set("buildId", config.BuildId)
+ propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)
- r.Response.WriteTpl("slug-"+config.BuildDate+".html", g.Map{
+ r.Response.WriteTpl(config.CacheBuildId+"/slug.html", g.Map{
"arkoseUrl": config.ArkoseUrl,
"props": propsJson,
"assetPrefix": config.AssetPrefix,
+ "envScript": config.GetEnvScript(r.GetCtx()),
})
}
// G 游戏
func G(r *ghttp.Request) {
- if r.Session.MustGet("userToken").IsEmpty() {
+ if r.Session.MustGet("offical-session").IsEmpty() {
r.Response.RedirectTo("/login")
return
}
@@ -333,20 +341,21 @@ func G(r *ghttp.Request) {
`
propsJson := gjson.New(props)
propsJson.Set("query.gizmoId", gizmoId)
- propsJson.Set("buildId", config.BuildId)
+ propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)
- r.Response.WriteTpl("g-"+config.BuildDate+".html", g.Map{
+ r.Response.WriteTpl(config.CacheBuildId+"/g.html", g.Map{
"arkoseUrl": config.ArkoseUrl,
"props": propsJson,
"assetPrefix": config.AssetPrefix,
+ "envScript": config.GetEnvScript(r.GetCtx()),
})
}
// GC 游戏会话
func GC(r *ghttp.Request) {
- if r.Session.MustGet("userToken").IsEmpty() {
+ if r.Session.MustGet("offical-session").IsEmpty() {
r.Response.RedirectTo("/login")
return
}
@@ -393,18 +402,19 @@ func GC(r *ghttp.Request) {
propsJson := gjson.New(props)
propsJson.Set("query.gizmoId", gizmoId)
propsJson.Set("query.convId", convId)
- propsJson.Set("buildId", config.BuildId)
+ propsJson.Set("buildId", config.CacheBuildId)
- r.Response.WriteTpl("gc-"+config.BuildDate+".html", g.Map{
+ r.Response.WriteTpl(config.CacheBuildId+"/gc.html", g.Map{
"arkoseUrl": config.ArkoseUrl,
"props": propsJson,
"assetPrefix": config.AssetPrefix,
+ "envScript": config.GetEnvScript(r.GetCtx()),
})
}
// Mine 我的
func Mine(r *ghttp.Request) {
- if r.Session.MustGet("userToken").IsEmpty() {
+ if r.Session.MustGet("offical-session").IsEmpty() {
r.Response.RedirectTo("/login")
return
}
@@ -446,13 +456,14 @@ func Mine(r *ghttp.Request) {
"scriptLoader": []
}`
propsJson := gjson.New(props)
- propsJson.Set("buildId", config.BuildId)
+ propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)
- r.Response.WriteTpl("mine-"+config.BuildDate+".html", g.Map{
+ r.Response.WriteTpl(config.CacheBuildId+"/mine.html", g.Map{
"arkoseUrl": config.ArkoseUrl,
"props": propsJson,
"assetPrefix": config.AssetPrefix,
+ "envScript": config.GetEnvScript(r.GetCtx()),
})
}
diff --git a/api/login.go b/api/login.go
index 9ec9cf3..8b3a0ea 100644
--- a/api/login.go
+++ b/api/login.go
@@ -25,7 +25,7 @@ func Login(r *ghttp.Request) {
func LoginPost(r *ghttp.Request) {
ctx := r.GetCtx()
// 如果用户名为空,就是token登录
- g.Log().Debug(ctx, "1232", r.Get("username").String() == "")
+ // g.Log().Debug(ctx, "1232", r.Get("username").String() == "")
if r.Get("username").String() == "" {
// token登录
userToken := r.Get("password").String()
@@ -45,6 +45,8 @@ func LoginPost(r *ghttp.Request) {
})
return
}
+ officialSession := record["officialSession"].String()
+ r.Session.Set("offical-session", officialSession)
r.Session.Set("userToken", userToken)
r.Response.RedirectTo("/")
return
@@ -94,6 +96,8 @@ func LoginPost(r *ghttp.Request) {
})
return
}
+ officialSession := user["officialSession"].String()
+ r.Session.Set("offical-session", officialSession)
r.Session.Set("userToken", user["userToken"].String())
r.Response.RedirectTo("/")
@@ -126,6 +130,8 @@ func LoginToken(r *ghttp.Request) {
})
return
}
+ officialSession := record["officialSession"].String()
+ r.Session.Set("offical-session", officialSession)
r.Session.Set("userToken", r.Get("access_token").String())
r.Response.RedirectTo("/")
}
diff --git a/api/next.go b/api/next.go
index 81746dc..deea325 100644
--- a/api/next.go
+++ b/api/next.go
@@ -1,60 +1,54 @@
package api
import (
- backendapi "chatgpt-mirror-server/backend-api"
+ "bytes"
"chatgpt-mirror-server/config"
- "chatgpt-mirror-server/utility"
+ "io"
"net/http"
- "time"
+ "net/http/httputil"
+ "net/url"
"github.com/gogf/gf/v2/encoding/gjson"
- "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gconv"
)
-func Next(r *ghttp.Request) {
+func ProxyNext(r *ghttp.Request) {
ctx := r.Context()
- path := r.RequestURI
- userToken := r.Session.MustGet("userToken").String()
- if userToken == "" {
- r.Response.WriteStatus(401)
- return
+ officalSession := gjson.New(r.Session.MustGet("offical-session"))
+ refreshCookie := officalSession.Get("refreshToken").String()
+ u, _ := url.Parse(config.CHATPROXY(ctx))
+ proxy := httputil.NewSingleHostReverseProxy(u)
+ proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, e error) {
+ writer.WriteHeader(http.StatusBadGateway)
}
- officialAccessToken := backendapi.AccessTokenCache.MustGet(ctx, userToken).String()
- if officialAccessToken == "" {
- record, _, err := ChatgptSessionService.GetSessionByUserToken(ctx, userToken)
+ req := r.Request.Clone(ctx)
+ // 替换path 中的 cacheBuildId 为 buildId
+ req.URL.Path = gstr.Replace(req.URL.Path, config.CacheBuildId, config.BuildId, 1)
+ req.Header.Set("Cookie", "__Secure-next-auth.session-token="+refreshCookie)
+ proxy.ModifyResponse = func(response *http.Response) error {
+ response.Header.Del("Set-Cookie")
+ // 读取响应体
+ body, err := io.ReadAll(response.Body)
if err != nil {
- g.Log().Error(ctx, err)
- r.Response.WriteStatus(http.StatusUnauthorized)
- return
+ return err
}
- if record.IsEmpty() {
- g.Log().Error(ctx, "session is empty")
- r.Response.WriteStatus(http.StatusUnauthorized)
- return
- }
- officialSession := record["officialSession"].String()
- if officialSession == "" {
- r.Response.WriteStatus(http.StatusUnauthorized)
- return
- }
- officialAccessToken = utility.AccessTokenFormSession(officialSession)
- backendapi.AccessTokenCache.Set(ctx, userToken, officialAccessToken, time.Minute)
- }
- refreshCookie := gjson.New(officialAccessToken).Get("refreshToken").String()
- res, err := g.Client().SetCookie("refreshToken", refreshCookie).Get(ctx, config.CHATPROXY(ctx)+path)
- if err != nil {
- r.Response.WriteStatus(http.StatusUnauthorized)
- return
- }
- res.RawDump()
- resStr := res.ReadAllString()
- if res.StatusCode != http.StatusOK {
- r.Response.Status = res.StatusCode
- r.Response.Write(resStr)
+ // 修改响应体
+ bodyJson := gjson.New(body)
+ bodyJson.Set("pageProps.user.email", "admin@openai.com")
+ bodyJson.Set("pageProps.user.name", "admin")
+ bodyJson.Set("pageProps.user.image", "/avatars.png")
+ bodyJson.Set("pageProps.user.picture", "/avatars.png")
+ bodyJson.Set("pageProps.user.id", "user-xadmin")
+
+ // 写入响应体
+ response.Body = io.NopCloser(bytes.NewReader(gconv.Bytes(bodyJson)))
+ // 重写响应头大小
+ response.ContentLength = int64(len(body))
- return
+ return nil
}
- r.Response.Write(resStr)
+ proxy.ServeHTTP(r.Response.Writer.RawWriter(), req)
}
diff --git a/api/session.go b/api/session.go
index 8599ddb..08cc494 100644
--- a/api/session.go
+++ b/api/session.go
@@ -19,7 +19,8 @@ func Session(r *ghttp.Request) {
record, expireTime, err := ChatgptSessionService.GetSessionByUserToken(ctx, userToken.String())
if err != nil {
g.Log().Error(ctx, err)
- r.Response.WriteStatus(http.StatusUnauthorized)
+ r.Session.RemoveAll()
+ r.Response.WriteJson(g.Map{})
return
}
if record.IsEmpty() {
@@ -39,12 +40,14 @@ func Session(r *ghttp.Request) {
sessionJson := gjson.New(sessionVar)
if sessionJson.Get("accessToken").String() == "" {
g.Log().Error(ctx, "get session error", sessionJson)
+ r.Session.RemoveAll()
r.Response.WriteStatus(http.StatusUnauthorized)
return
}
cool.DBM(model.NewChatgptSession()).Where("email=?", record["email"].String()).Update(g.Map{
"officialSession": sessionJson.String(),
})
+ r.Session.Set("offical-session", sessionJson.String())
backendapi.AccessTokenCache.Set(ctx, userToken.String(), sessionJson.Get("accessToken").String(), 10*24*time.Hour)
sessionJson.Set("accessToken", userToken.String())
sessionJson.Set("user.email", "admin@openai.com")
diff --git a/arkose/proxyarkose.go b/arkose/proxyarkose.go
deleted file mode 100644
index f9acffc..0000000
--- a/arkose/proxyarkose.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package arkose
-
-import (
- "bytes"
- "io"
- "net/http"
- "net/http/httputil"
- "net/url"
-
- "github.com/gogf/gf/v2/net/ghttp"
- "github.com/gogf/gf/v2/text/gstr"
- "github.com/gogf/gf/v2/util/gconv"
-)
-
-var (
- UpStream = "https://client-api.arkoselabs.com/"
- u, _ = url.Parse(UpStream)
- proxy = httputil.NewSingleHostReverseProxy(u)
-)
-
-func init() {
-
-}
-
-func Proxy(r *ghttp.Request) {
-
- proxy.Director = func(req *http.Request) {
- requrl := r.Request.URL.Path
- if requrl == "/fc/gt2/public_key/35536E1E-65B4-4D96-9D97-6ADB7EFF8147" {
- body := r.GetBodyString()
- bodyArray := gstr.Split(body, "&")
- // 遍历数组 当数组元素以 "site=http" 开头时,将其替换为 "site=http%3A%2F%2Flocalhost%3A3000"
- for i, v := range bodyArray {
- if gstr.HasPrefix(v, "site=http") {
- bodyArray[i] = "site=http%3A%2F%2Flocalhost%3A3000"
- }
- }
- body = gstr.Join(bodyArray, "&")
-
- req.Body = io.NopCloser(bytes.NewReader(gconv.Bytes(body)))
- req.ContentLength = int64(len(body))
- }
-
- req.Header = r.Header
- req.Host = u.Host
- req.URL.Scheme = u.Scheme
- req.URL.Host = u.Host
- req.URL.Path = requrl
-
- req.Header.Set("Origin", "http://localhost:3000")
- req.Header.Set("Referer", "http://localhost:3000/v2/1.5.4/enforcement.cd12da708fe6cbe6e068918c38de2ad9.html")
-
- }
-
- proxy.ServeHTTP(r.Response.RawWriter(), r.Request)
-
-}
diff --git a/backend-api/backend-api.go b/backend-api/backend-api.go
index 46249da..6a6a2f6 100644
--- a/backend-api/backend-api.go
+++ b/backend-api/backend-api.go
@@ -38,6 +38,7 @@ func NotFound(r *ghttp.Request) {
func ProxyAll(r *ghttp.Request) {
ctx := r.GetCtx()
// 获取header中的token Authorization: Bearer xxx 去掉Bearer
+
userToken := r.Header.Get("Authorization")[7:]
officialAccessToken := AccessTokenCache.MustGet(ctx, userToken).String()
if officialAccessToken == "" {
diff --git a/config.yaml b/config.yaml
index 0f4f3d4..8a98d77 100644
--- a/config.yaml
+++ b/config.yaml
@@ -57,6 +57,7 @@ modules:
# 接入网关地址
CHATPROXY: "https://demo.xyhelper.cn"
+# CHATPROXY: "http://172.17.0.1:7009"
# 接入网关的authkey
AUTHKEY: "xyhelper"
ONLYTOKEN: true
diff --git a/config/config.go b/config/config.go
index 8260b47..d7700e4 100644
--- a/config/config.go
+++ b/config/config.go
@@ -5,8 +5,12 @@ import (
"time"
"github.com/gogf/gf/v2/container/garray"
+ "github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
+ "github.com/gogf/gf/v2/os/gfile"
+ "github.com/gogf/gf/v2/os/gview"
+ "github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
@@ -15,7 +19,7 @@ func CHATPROXY(ctx g.Ctx) string {
}
func AUTHKEY(ctx g.Ctx) string {
- g.Log().Debug(ctx, "config.AUTHKEY", g.Cfg().MustGetWithEnv(ctx, "AUTHKEY").String())
+ // g.Log().Debug(ctx, "config.AUTHKEY", g.Cfg().MustGetWithEnv(ctx, "AUTHKEY").String())
return g.Cfg().MustGetWithEnv(ctx, "AUTHKEY").String()
}
@@ -27,10 +31,20 @@ var (
DefaultModel = "text-davinci-002-render-sha"
FreeModels = garray.NewStrArray()
PlusModels = garray.NewStrArray()
- ArkoseUrl = "https://tcr9i.xyhelper.com.cn/v2/"
- BuildDate = "20231202"
- BuildId = "h8j8GC1m0GPjiDDpUuDdq"
+ ArkoseUrl = "https://tcr9i.closeai.biz/v2/"
+ BuildId = "q1uPMR9VmRS6HPYxPKMbH"
+ CacheBuildId = "q1uPMR9VmRS6HPYxPKMbH"
AssetPrefix = "https://oaistatic-cdn.closeai.biz"
+ PK40 = "35536E1E-65B4-4D96-9D97-6ADB7EFF8147"
+ PK35 = "3D86FBBA-9D22-402A-B512-3420086BA6CC"
+ envScriptTpl = `
+
+ `
)
func init() {
@@ -52,6 +66,32 @@ func init() {
AssetPrefix = assetPrefix
}
g.Log().Info(ctx, "ASSET_PREFIX:", AssetPrefix)
+ cacheBuildId := CheckVersion(ctx, AssetPrefix)
+ if cacheBuildId != "" {
+ CacheBuildId = cacheBuildId
+ }
+ g.Log().Info(ctx, "CacheBuildId:", CacheBuildId)
+ build := CheckNewVersion(ctx)
+ if build != "" {
+ BuildId = build
+ }
+ g.Log().Info(ctx, "BuildId:", BuildId)
+ // 每小时更新一次
+ go func() {
+ for {
+ time.Sleep(time.Hour)
+ build := CheckNewVersion(ctx)
+ if build != "" {
+ BuildId = build
+ }
+ g.Log().Info(ctx, "BuildId:", BuildId)
+ cacheBuildId := CheckVersion(ctx, AssetPrefix)
+ if cacheBuildId != "" {
+ CacheBuildId = cacheBuildId
+ }
+ g.Log().Info(ctx, "CacheBuildId:", CacheBuildId)
+ }
+ }()
}
@@ -97,3 +137,78 @@ func APIAUTH(ctx g.Ctx) string {
func CLEARCHATHISTORY(ctx g.Ctx) bool {
return g.Cfg().MustGetWithEnv(ctx, "CLEARCHATHISTORY").Bool()
}
+
+// 检查版本号并同步资源
+func CheckVersion(ctx g.Ctx, assetPrefix string) (CacheBuildId string) {
+ gclient := g.Client()
+ // 读取 assetPrefix/version
+ versionVar := gclient.GetVar(ctx, assetPrefix+"/version.json")
+ CacheBuildId = gjson.New(versionVar).Get("cacheBuildId").String()
+ g.Log().Infof(ctx, "Get config From %s ,CacheBuildId: %s", AssetPrefix, CacheBuildId)
+ if CacheBuildId == "" {
+ return ""
+ }
+ // 读取buildDate目录索引
+ indexUrl := assetPrefix + "/template/" + CacheBuildId + "/index.txt"
+ g.Log().Info(ctx, "Get config From ", indexUrl)
+ buildDateVar := gclient.GetVar(ctx, indexUrl).String()
+ if buildDateVar == "" {
+ return ""
+ }
+ // 按回车分割
+ buildDateList := gstr.Split(buildDateVar, "\n")
+ g.Dump(buildDateList)
+ // 遍历目录索引 如果没有就下载
+ for _, v := range buildDateList {
+ if v == "" {
+ continue
+ }
+ // 检查文件是否存在
+ if !gfile.Exists("./resource/template/" + CacheBuildId + "/" + v) {
+ g.Log().Infof(ctx, "Download %s", v)
+ // 下载文件
+ res, err := gclient.Get(ctx, assetPrefix+"/template/"+CacheBuildId+"/"+v)
+ if err != nil {
+ g.Log().Error(ctx, "Download Error: ", v, err)
+ return ""
+ }
+ defer res.Close()
+ if res.StatusCode != 200 {
+ g.Log().Error(ctx, "Download Error: ", v, res.StatusCode)
+ return ""
+ }
+ // 写入文件
+ err = gfile.PutBytes("./resource/template/"+CacheBuildId+"/"+v, res.ReadAll())
+ if err != nil {
+ g.Log().Error(ctx, "Download Error: ", v, err)
+ return ""
+ }
+
+ }
+ }
+
+ return
+}
+
+func GetEnvScript(ctx g.Ctx) string {
+ script, err := gview.ParseContent(ctx, envScriptTpl, g.Map{
+ "ArkoseUrl": ArkoseUrl,
+ "AssetPrefix": AssetPrefix,
+ "PK40": PK40,
+ "PK35": PK35,
+ })
+ if err != nil {
+ g.Log().Error(ctx, "GetEnvScript Error: ", err)
+ return ""
+ }
+ return script
+}
+
+// 检查是否有新版本
+func CheckNewVersion(ctx g.Ctx) (buildId string) {
+ resVar := g.Client().GetVar(ctx, CHATPROXY(ctx)+"/ping")
+ resJson := gjson.New(resVar)
+
+ buildId = resJson.Get("buildId").String()
+ return
+}
diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json
index 07f6c52..f24801c 100644
--- a/frontend/.vscode/settings.json
+++ b/frontend/.vscode/settings.json
@@ -1,4 +1,4 @@
{
- "editor.cursorSmoothCaretAnimation": true,
+ "editor.cursorSmoothCaretAnimation": "on",
"editor.formatOnSave": true,
}
diff --git a/frontend/package.json b/frontend/package.json
index 55874ff..720bb08 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -19,6 +19,7 @@
"echarts": "^5.3.3",
"element-plus": "^2.2.28",
"file-saver": "^2.0.5",
+ "fkarkos": "^0.1.1",
"lodash-es": "^4.17.21",
"mitt": "^3.0.0",
"mockjs": "^1.1.0",
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
index 1c2c5ed..72068c9 100644
--- a/frontend/src/main.ts
+++ b/frontend/src/main.ts
@@ -3,7 +3,8 @@ import App from "./App.vue";
import { bootstrap } from "./cool";
const app = createApp(App);
-
+// import fkarkos from "fkarkos";
+// app.component("fkarkos", fkarkos);
// 启动
bootstrap(app)
.then(() => {
diff --git a/frontend/src/modules/chatgpt/views/FKArkos.vue b/frontend/src/modules/chatgpt/views/FKArkos.vue
new file mode 100644
index 0000000..03209d1
--- /dev/null
+++ b/frontend/src/modules/chatgpt/views/FKArkos.vue
@@ -0,0 +1,140 @@
+
+
+
+
+
diff --git a/frontend/src/modules/chatgpt/views/session.vue b/frontend/src/modules/chatgpt/views/session.vue
index b1b86cd..298db02 100644
--- a/frontend/src/modules/chatgpt/views/session.vue
+++ b/frontend/src/modules/chatgpt/views/session.vue
@@ -25,6 +25,13 @@