diff --git a/CHANGELOG.md b/CHANGELOG.md index cd8d9ad8..48c96c50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ * Bug修复:修复 issue [ 管理界面操作用户存在的两个问题](https://github.com/yangjian102621/chatgpt-plus/issues/117#issuecomment-1909201532) * 功能优化:在对话和聊天记录表中新增冗余字段 model,存储对话模型 +* Bug修复:IPhone 手机验证码触摸事件坐标错位 [issue 144](https://github.com/yangjian102621/chatgpt-plus/issues/144) +* Bug修复:重新生成按钮功能失效问题 +* Bug修复:对话输入HTML标签不显示的问题 +* 功能优化:gpt-4-all/gpts/midjourney-plus 支持第三方平台的 API KEY +* 功能新增:新增删除文件功能 ## v3.2.6 * 功能优化:恢复关闭注册系统配置项,管理员可以在后台关闭用户注册,只允许内部添加账号 diff --git a/api/handler/chatimpl/chat_handler.go b/api/handler/chatimpl/chat_handler.go index 529120c4..be4e771b 100644 --- a/api/handler/chatimpl/chat_handler.go +++ b/api/handler/chatimpl/chat_handler.go @@ -462,11 +462,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf req.Messages = nil break default: - if req.Model == "gpt-4-all" || strings.HasPrefix(req.Model, "gpt-4-gizmo-g-") { - apiURL = "https://gpt.bemore.lol/v1/chat/completions" - } else { - apiURL = apiKey.ApiURL - } + apiURL = apiKey.ApiURL } // 更新 API KEY 的最后使用时间 h.db.Model(apiKey).UpdateColumn("last_used_at", time.Now().Unix()) diff --git a/api/handler/upload_handler.go b/api/handler/upload_handler.go index 6a3da8a4..2a85b5df 100644 --- a/api/handler/upload_handler.go +++ b/api/handler/upload_handler.go @@ -35,6 +35,7 @@ func (h *UploadHandler) Upload(c *gin.Context) { res := h.db.Create(&model.File{ UserId: userId, Name: file.Name, + ObjKey: file.ObjKey, URL: file.URL, Ext: file.Ext, Size: file.Size, @@ -52,7 +53,7 @@ func (h *UploadHandler) List(c *gin.Context) { userId := h.GetLoginUserId(c) var items []model.File var files = make([]vo.File, 0) - h.db.Debug().Where("user_id = ?", userId).Find(&items) + h.db.Where("user_id = ?", userId).Find(&items) if len(items) > 0 { for _, v := range items { var file vo.File @@ -68,3 +69,29 @@ func (h *UploadHandler) List(c *gin.Context) { resp.SUCCESS(c, files) } + +// Remove remove files +func (h *UploadHandler) Remove(c *gin.Context) { + userId := h.GetLoginUserId(c) + id := h.GetInt(c, "id", 0) + var file model.File + tx := h.db.Where("user_id = ? AND id = ?", userId, id).First(&file) + if tx.Error != nil || file.Id == 0 { + resp.ERROR(c, "file not existed") + return + } + + // remove database + tx = h.db.Model(&model.File{}).Delete("id = ?", id) + if tx.Error != nil || tx.RowsAffected == 0 { + resp.ERROR(c, "failed to update database") + return + } + // remove files + objectKey := file.ObjKey + if objectKey == "" { + objectKey = file.URL + } + _ = h.uploaderManager.GetUploadHandler().Delete(objectKey) + resp.SUCCESS(c) +} diff --git a/api/main.go b/api/main.go index 7c38b33d..78385d44 100644 --- a/api/main.go +++ b/api/main.go @@ -218,6 +218,7 @@ func main() { fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) { s.Engine.POST("/api/upload", h.Upload) s.Engine.GET("/api/upload/list", h.List) + s.Engine.GET("/api/upload/remove", h.Remove) }), fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) { group := s.Engine.Group("/api/sms/") diff --git a/api/service/mj/pool.go b/api/service/mj/pool.go index 4c676589..4db4b510 100644 --- a/api/service/mj/pool.go +++ b/api/service/mj/pool.go @@ -33,8 +33,6 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa if config.Enabled == false { continue } - // rewrite api key - config.ApiURL = "https://api.chat-plus.net" client := plus.NewClient(config) name := fmt.Sprintf("mj-service-plus-%d", k) servicePlus := plus.NewService(name, taskQueue, notifyQueue, 10, 600, db, client) diff --git a/api/service/oss/aliyun_oss.go b/api/service/oss/aliyun_oss.go index 5ae1063b..28ec6882 100644 --- a/api/service/oss/aliyun_oss.go +++ b/api/service/oss/aliyun_oss.go @@ -7,6 +7,7 @@ import ( "fmt" "net/url" "path/filepath" + "strings" "time" "github.com/aliyun/aliyun-oss-go-sdk/oss" @@ -67,10 +68,11 @@ func (s AliYunOss) PutFile(ctx *gin.Context, name string) (File, error) { } return File{ - Name: file.Filename, - URL: fmt.Sprintf("%s/%s", s.config.Domain, objectKey), - Ext: fileExt, - Size: file.Size, + Name: file.Filename, + ObjKey: objectKey, + URL: fmt.Sprintf("%s/%s", s.config.Domain, objectKey), + Ext: fileExt, + Size: file.Size, }, nil } @@ -100,9 +102,14 @@ func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) { } func (s AliYunOss) Delete(fileURL string) error { - objectName := filepath.Base(fileURL) - key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName) - return s.bucket.DeleteObject(key) + var objectKey string + if strings.HasPrefix(fileURL, "http") { + filename := filepath.Base(fileURL) + objectKey = fmt.Sprintf("%s/%s", s.config.SubDir, filename) + } else { + objectKey = fileURL + } + return s.bucket.DeleteObject(objectKey) } var _ Uploader = AliYunOss{} diff --git a/api/service/oss/localstorage.go b/api/service/oss/localstorage.go index 5e98927a..184b6011 100644 --- a/api/service/oss/localstorage.go +++ b/api/service/oss/localstorage.go @@ -42,10 +42,11 @@ func (s LocalStorage) PutFile(ctx *gin.Context, name string) (File, error) { ext := filepath.Ext(file.Filename) return File{ - Name: file.Filename, - URL: utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, path), - Ext: ext, - Size: file.Size, + Name: file.Filename, + ObjKey: path, + URL: utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, path), + Ext: ext, + Size: file.Size, }, nil } @@ -73,6 +74,9 @@ func (s LocalStorage) PutImg(imageURL string, useProxy bool) (string, error) { } func (s LocalStorage) Delete(fileURL string) error { + if _, err := os.Stat(fileURL); err == nil { + return os.Remove(fileURL) + } filePath := strings.Replace(fileURL, s.config.BaseURL, s.config.BasePath, 1) return os.Remove(filePath) } diff --git a/api/service/oss/minio_oss.go b/api/service/oss/minio_oss.go index fa6fe68e..ba11d333 100644 --- a/api/service/oss/minio_oss.go +++ b/api/service/oss/minio_oss.go @@ -88,17 +88,23 @@ func (s MiniOss) PutFile(ctx *gin.Context, name string) (File, error) { } return File{ - Name: file.Filename, - URL: fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key), - Ext: fileExt, - Size: file.Size, + Name: file.Filename, + ObjKey: info.Key, + URL: fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key), + Ext: fileExt, + Size: file.Size, }, nil } func (s MiniOss) Delete(fileURL string) error { - objectName := filepath.Base(fileURL) - key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName) - return s.client.RemoveObject(context.Background(), s.config.Bucket, key, minio.RemoveObjectOptions{}) + var objectKey string + if strings.HasPrefix(fileURL, "http") { + filename := filepath.Base(fileURL) + objectKey = fmt.Sprintf("%s/%s", s.config.SubDir, filename) + } else { + objectKey = fileURL + } + return s.client.RemoveObject(context.Background(), s.config.Bucket, objectKey, minio.RemoveObjectOptions{}) } var _ Uploader = MiniOss{} diff --git a/api/service/oss/qiniu_oss.go b/api/service/oss/qiniu_oss.go index e2df7ee6..84aa941c 100644 --- a/api/service/oss/qiniu_oss.go +++ b/api/service/oss/qiniu_oss.go @@ -8,6 +8,7 @@ import ( "fmt" "net/url" "path/filepath" + "strings" "time" "github.com/gin-gonic/gin" @@ -75,10 +76,11 @@ func (s QinNiuOss) PutFile(ctx *gin.Context, name string) (File, error) { } return File{ - Name: file.Filename, - URL: fmt.Sprintf("%s/%s", s.config.Domain, ret.Key), - Ext: fileExt, - Size: file.Size, + Name: file.Filename, + ObjKey: key, + URL: fmt.Sprintf("%s/%s", s.config.Domain, ret.Key), + Ext: fileExt, + Size: file.Size, }, nil } @@ -111,9 +113,15 @@ func (s QinNiuOss) PutImg(imageURL string, useProxy bool) (string, error) { } func (s QinNiuOss) Delete(fileURL string) error { - objectName := filepath.Base(fileURL) - key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName) - return s.manager.Delete(s.config.Bucket, key) + var objectKey string + if strings.HasPrefix(fileURL, "http") { + filename := filepath.Base(fileURL) + objectKey = fmt.Sprintf("%s/%s", s.config.SubDir, filename) + } else { + objectKey = fileURL + } + + return s.manager.Delete(s.config.Bucket, objectKey) } var _ Uploader = QinNiuOss{} diff --git a/api/service/oss/uploader.go b/api/service/oss/uploader.go index 566dfe29..ce410d02 100644 --- a/api/service/oss/uploader.go +++ b/api/service/oss/uploader.go @@ -8,10 +8,11 @@ const QiNiu = "QINIU" const AliYun = "ALIYUN" type File struct { - Name string `json:"name"` - Size int64 `json:"size"` - URL string `json:"url"` - Ext string `json:"ext"` + Name string `json:"name"` + ObjKey string `json:"obj_key"` + Size int64 `json:"size"` + URL string `json:"url"` + Ext string `json:"ext"` } type Uploader interface { PutFile(ctx *gin.Context, name string) (File, error) diff --git a/api/store/model/file.go b/api/store/model/file.go index dafd650e..7541ec8d 100644 --- a/api/store/model/file.go +++ b/api/store/model/file.go @@ -6,6 +6,7 @@ type File struct { Id uint `gorm:"primarykey;column:id"` UserId uint Name string + ObjKey string URL string Ext string Size int64 diff --git a/api/store/vo/file.go b/api/store/vo/file.go index 2a0ebc6b..c5e83dcc 100644 --- a/api/store/vo/file.go +++ b/api/store/vo/file.go @@ -1,9 +1,10 @@ package vo type File struct { - Id uint + Id uint `json:"id"` UserId uint `json:"user_id"` Name string `json:"name"` + ObjKey string `json:"obj_key"` URL string `json:"url"` Ext string `json:"ext"` Size int64 `json:"size"` diff --git a/database/update-v3.2.7.sql b/database/update-v3.2.7.sql index 2f0e8f80..2fff0e90 100644 --- a/database/update-v3.2.7.sql +++ b/database/update-v3.2.7.sql @@ -10,4 +10,6 @@ UPDATE chatgpt_chat_items s SET model=(SELECT value FROM chatgpt_chat_models WHE UPDATE chatgpt_chat_history s SET model=(SELECT model FROM chatgpt_chat_items WHERE chat_id = s.chat_id); -- 清理对话已删除的聊天记录(可选) --- DELETE FROM `chatgpt_chat_history` WHERE model is NULL; \ No newline at end of file +-- DELETE FROM `chatgpt_chat_history` WHERE model is NULL; + +ALTER TABLE `chatgpt_files` ADD `obj_key` VARCHAR(100) NULL COMMENT '文件标识' AFTER `name`; \ No newline at end of file diff --git a/web/src/components/FileSelect.vue b/web/src/components/FileSelect.vue index 58c59cc3..1b838f24 100644 --- a/web/src/components/FileSelect.vue +++ b/web/src/components/FileSelect.vue @@ -39,9 +39,13 @@ effect="dark" :content="file.name" placement="top"> - - + + + +
+ +
@@ -54,8 +58,8 @@ import {ref} from "vue"; import {ElMessage} from "element-plus"; import {httpGet, httpPost} from "@/utils/http"; -import {PictureFilled, Plus} from "@element-plus/icons-vue"; -import {isImage} from "@/utils/libs"; +import {Delete, PictureFilled, Plus} from "@element-plus/icons-vue"; +import {isImage, removeArrayItem} from "@/utils/libs"; const props = defineProps({ userId: String, @@ -103,6 +107,17 @@ const afterRead = (file) => { }) }; +const removeFile = (file) => { + httpGet('/api/upload/remove?id=' + file.id).then(() => { + fileList.value = removeArrayItem(fileList.value, file, (v1, v2) => { + return v1.id === v2.id + }) + ElMessage.success("文件删除成功!") + }).catch((e) => { + ElMessage.error('文件删除失败:' + e.message) + }) +} + const insertURL = (url) => { show.value = false emits('selected', url) @@ -129,6 +144,7 @@ const insertURL = (url) => { .grid-content { margin-bottom 10px + position relative .avatar-uploader { width 100% @@ -145,6 +161,7 @@ const insertURL = (url) => { } .el-image { + width 100% height 80px border 1px solid #ffffff border-radius 6px @@ -160,6 +177,19 @@ const insertURL = (url) => { color #20a0ff font-size 40px } + + .opt { + display none + position absolute + top 5px + right 5px + } + + &:hover { + .opt { + display block + } + } } }