diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6ba6a56..20da1320 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,7 @@
* 功能新增:**支持AI解读 PDF, Word, Excel等文件**
* 功能优化:优化聊天界面的用户上传文件的列表样式
* 功能优化:优化聊天页面对话样式,支持列表样式和对话样式切换
-* 功能新增:支持微信等社交媒体登录
+* 功能新增:支持微信扫码登录,未注册用户微信扫码后会自动注册并登录。移动使用微信浏览器打开可以实现无感登录。
## v4.0.9
diff --git a/api/handler/mj_handler.go b/api/handler/mj_handler.go
index 7b8d7ca0..7f6f6b7d 100644
--- a/api/handler/mj_handler.go
+++ b/api/handler/mj_handler.go
@@ -446,14 +446,9 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize
}
if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" {
- // discord 服务器图片需要使用代理转发图片数据流
- if strings.HasPrefix(item.OrgURL, "https://cdn.discordapp.com") {
- image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
- if err == nil {
- job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
- }
- } else {
- job.ImgURL = job.OrgURL
+ image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
+ if err == nil {
+ job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
}
}
diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go
index deae433d..c765a100 100644
--- a/api/handler/user_handler.go
+++ b/api/handler/user_handler.go
@@ -285,9 +285,99 @@ func (h *UserHandler) CLoginRequest(c *gin.Context) {
// CLoginCallback 第三方登录回调
func (h *UserHandler) CLoginCallback(c *gin.Context) {
- //platform := h.GetTrim(c, "type")
- //code := h.GetTrim(c, "code")
+ loginType := h.GetTrim(c, "login_type")
+ code := h.GetTrim(c, "code")
+ var res types.BizVo
+ apiURL := fmt.Sprintf("%s/api/clogin/info", h.App.Config.ApiConfig.ApiURL)
+ r, err := req.C().R().SetBody(gin.H{"login_type": loginType, "code": code}).
+ SetHeader("AppId", h.App.Config.ApiConfig.AppId).
+ SetHeader("Authorization", fmt.Sprintf("Bearer %s", h.App.Config.ApiConfig.Token)).
+ SetSuccessResult(&res).
+ Post(apiURL)
+ if err != nil {
+ resp.ERROR(c, err.Error())
+ return
+ }
+ if r.IsErrorState() {
+ resp.ERROR(c, "error with login http status: "+r.Status)
+ return
+ }
+
+ if res.Code != types.Success {
+ resp.ERROR(c, "error with http response: "+res.Message)
+ return
+ }
+
+ // login successfully
+ data := res.Data.(map[string]interface{})
+ session := gin.H{}
+ var user model.User
+ tx := h.DB.Debug().Where("openid", data["openid"]).First(&user)
+ if tx.Error != nil { // user not exist, create new user
+ // 检测最大注册人数
+ var totalUser int64
+ h.DB.Model(&model.User{}).Count(&totalUser)
+ if h.licenseService.GetLicense().Configs.UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().Configs.UserNum {
+ resp.ERROR(c, "当前注册用户数已达上限,请请升级 License")
+ return
+ }
+
+ salt := utils.RandString(8)
+ password := fmt.Sprintf("%d", utils.RandomNumber(8))
+ user = model.User{
+ Username: fmt.Sprintf("%s@%d", loginType, utils.RandomNumber(10)),
+ Password: utils.GenPassword(password, salt),
+ Avatar: fmt.Sprintf("%s", data["avatar"]),
+ Salt: salt,
+ Status: true,
+ ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色
+ ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型
+ Power: h.App.SysConfig.InitPower,
+ OpenId: fmt.Sprintf("%s", data["openid"]),
+ Nickname: fmt.Sprintf("%s", data["nickname"]),
+ }
+
+ tx = h.DB.Create(&user)
+ if tx.Error != nil {
+ resp.ERROR(c, "保存数据失败")
+ logger.Error(tx.Error)
+ return
+ }
+ session["username"] = user.Username
+ session["password"] = password
+ } else { // login directly
+ // 更新最后登录时间和IP
+ user.LastLoginIp = c.ClientIP()
+ user.LastLoginAt = time.Now().Unix()
+ h.DB.Model(&user).Updates(user)
+
+ h.DB.Create(&model.UserLoginLog{
+ UserId: user.Id,
+ Username: user.Username,
+ LoginIp: c.ClientIP(),
+ LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()),
+ })
+ }
+
+ // 创建 token
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
+ "user_id": user.Id,
+ "expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)).Unix(),
+ })
+ tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
+ if err != nil {
+ resp.ERROR(c, "Failed to generate token, "+err.Error())
+ return
+ }
+ // 保存到 redis
+ key := fmt.Sprintf("users/%d", user.Id)
+ if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil {
+ resp.ERROR(c, "error with save token: "+err.Error())
+ return
+ }
+ session["token"] = tokenString
+ resp.SUCCESS(c, session)
}
// Session 获取/验证会话
diff --git a/database/update-v4.1.0.sql b/database/update-v4.1.0.sql
index 61ed1a8a..37d36e92 100644
--- a/database/update-v4.1.0.sql
+++ b/database/update-v4.1.0.sql
@@ -1,3 +1,6 @@
ALTER TABLE `chatgpt_chat_models` CHANGE `power` `power` SMALLINT NOT NULL COMMENT '消耗算力点数';
ALTER TABLE `chatgpt_users` ADD `openid` VARCHAR(100) NULL COMMENT '第三方登录账号ID' AFTER `last_login_ip`;
-ALTER TABLE `chatgpt_users` ADD `platform` VARCHAR(30) NULL COMMENT '登录平台' AFTER `openid`;
\ No newline at end of file
+ALTER TABLE `chatgpt_users` ADD UNIQUE(`openid`);
+ALTER TABLE `chatgpt_users` ADD `platform` VARCHAR(30) NULL COMMENT '登录平台' AFTER `openid`;
+ALTER TABLE `chatgpt_users` CHANGE `avatar` `avatar` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头像';
+ALTER TABLE `chatgpt_chat_history` CHANGE `icon` `icon` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色图标';
\ No newline at end of file
diff --git a/web/src/assets/css/login.styl b/web/src/assets/css/login.styl
new file mode 100644
index 00000000..bf8b6c49
--- /dev/null
+++ b/web/src/assets/css/login.styl
@@ -0,0 +1,115 @@
+.bg {
+ position fixed
+ left 0
+ right 0
+ top 0
+ bottom 0
+ background-color #313237
+ background-image url("~@/assets/img/login-bg.jpg")
+ background-size cover
+ background-position center
+ background-repeat repeat-y
+ //filter: blur(10px); /* 调整模糊程度,可以根据需要修改值 */
+}
+
+.main {
+ .contain {
+ position fixed
+ left 50%
+ top 40%
+ width 90%
+ max-width 400px;
+ transform translate(-50%, -50%)
+ padding 20px 10px;
+ color #ffffff
+ border-radius 10px;
+
+ .logo {
+ text-align center
+
+ .el-image {
+ width 120px;
+ cursor pointer
+ }
+ }
+
+ .header {
+ width 100%
+ margin-bottom 24px
+ font-size 24px
+ color $white_v1
+ letter-space 2px
+ text-align center
+ padding-top 10px
+ }
+
+ .content {
+ width 100%
+ height: auto
+ border-radius 3px
+
+ .block {
+ margin-bottom 16px
+
+ .el-input__inner {
+ border 1px solid $gray-v6 !important
+
+ .el-icon-user, .el-icon-lock {
+ font-size 20px
+ }
+ }
+ }
+
+ .btn-row {
+ padding-top 10px;
+
+ .login-btn {
+ width 100%
+ font-size 16px
+ letter-spacing 2px
+ }
+ }
+
+ .text-line {
+ justify-content center
+ padding-top 10px;
+ font-size 14px;
+ }
+
+ .opt {
+ padding 15px
+ .el-col {
+ text-align center
+ }
+ }
+
+ .divider {
+ border-top: 2px solid #c1c1c1;
+ }
+
+ .clogin {
+ padding 15px
+ display flex
+ justify-content center
+
+ .iconfont {
+ font-size 20px
+ background: #E9F1F6;
+ padding: 8px;
+ border-radius: 50%;
+ }
+ .iconfont.icon-wechat {
+ color #0bc15f
+ }
+ }
+ }
+ }
+
+ .footer {
+ color #ffffff;
+
+ .container {
+ padding 20px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/components/ChatReply.vue b/web/src/components/ChatReply.vue
index 033fefe5..6e64dbb0 100644
--- a/web/src/components/ChatReply.vue
+++ b/web/src/components/ChatReply.vue
@@ -155,6 +155,7 @@ const synthesis = (text) => {
// 重新生成
const reGenerate = (prompt) => {
+ console.log(prompt)
emits('regen', prompt)
}
diff --git a/web/src/components/TaskList.vue b/web/src/components/TaskList.vue
new file mode 100644
index 00000000..d2e63e0d
--- /dev/null
+++ b/web/src/components/TaskList.vue
@@ -0,0 +1,58 @@
+
+