diff --git a/CHANGELOG.md b/CHANGELOG.md index f858dcdd..5c6512f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # 更新日志 +## v4.1.3 +* 功能优化:重构用户登录模块,给所有的登录组件增加行为验证码功能,支持用户绑定手机,邮箱和微信 +* 功能优化:重构找回密码模块,支持通过手机或者邮箱找回密码 +* 功能优化:管理后台给可以拖动排序的组件添加拖动图标 +* 功能优化:Suno 支持合成完整歌曲,和上传自己的音乐作品进行二次创作 +* Bug修复:手机端角色和模型选择不生效 +* Bug修复:用户登录过期之后聊天页面出现大量报错,需要刷新页面才能正常 +* 功能优化:优化聊天页面 Websocket 断线重连代码,提高用户体验 +* 功能优化:给算力增减服务全部加上数据库事务和同步锁 +* 功能优化:支持用户在前端对话界面选择插件 +* 功能新增:支持 Luma 文生视频功能 + ## v4.1.2 * Bug修复:修复思维导图页面获取模型失败的问题 * 功能优化:优化MJ,SD,DALL-E 任务列表页面,显示失败任务的错误信息,删除失败任务可以恢复扣减算力 diff --git a/api/core/app_server.go b/api/core/app_server.go index 84bc89a8..b7187d21 100644 --- a/api/core/app_server.go +++ b/api/core/app_server.go @@ -201,7 +201,6 @@ func needLogin(c *gin.Context) bool { c.Request.URL.Path == "/api/admin/logout" || c.Request.URL.Path == "/api/admin/login/captcha" || c.Request.URL.Path == "/api/user/register" || - c.Request.URL.Path == "/api/user/session" || c.Request.URL.Path == "/api/chat/history" || c.Request.URL.Path == "/api/chat/detail" || c.Request.URL.Path == "/api/chat/list" || @@ -227,6 +226,8 @@ func needLogin(c *gin.Context) bool { c.Request.URL.Path == "/api/suno/client" || c.Request.URL.Path == "/api/suno/detail" || c.Request.URL.Path == "/api/suno/play" || + c.Request.URL.Path == "/api/download" || + c.Request.URL.Path == "/api/video/client" || strings.HasPrefix(c.Request.URL.Path, "/api/test") || strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") || strings.HasPrefix(c.Request.URL.Path, "/api/config/") || @@ -367,6 +368,7 @@ func staticResourceMiddleware() gin.HandlerFunc { // 直接输出图像数据流 c.Data(http.StatusOK, "image/jpeg", buffer.Bytes()) c.Abort() // 中断请求 + } c.Next() } diff --git a/api/core/types/chat.go b/api/core/types/chat.go index 9464ec8b..42c86a2b 100644 --- a/api/core/types/chat.go +++ b/api/core/types/chat.go @@ -57,6 +57,7 @@ type ChatSession struct { ClientIP string `json:"client_ip"` // 客户端 IP ChatId string `json:"chat_id"` // 客户端聊天会话 ID, 多会话模式专用字段 Model ChatModel `json:"model"` // GPT 模型 + Tools string `json:"tools"` // 函数 } type ChatModel struct { diff --git a/api/core/types/config.go b/api/core/types/config.go index f3882769..9638f620 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -131,10 +131,10 @@ func (c RedisConfig) Url() string { } type SystemConfig struct { - Title string `json:"title,omitempty"` // 网站标题 - Slogan string `json:"slogan,omitempty"` // 网站 slogan - AdminTitle string `json:"admin_title,omitempty"` // 管理后台标题 - Logo string `json:"logo,omitempty"` + Title string `json:"title,omitempty"` // 网站标题 + Slogan string `json:"slogan,omitempty"` // 网站 slogan + AdminTitle string `json:"admin_title,omitempty"` // 管理后台标题 + Logo string `json:"logo,omitempty"` // 方形 Logo InitPower int `json:"init_power,omitempty"` // 新用户注册赠送算力值 DailyPower int `json:"daily_power,omitempty"` // 每日赠送算力 InvitePower int `json:"invite_power,omitempty"` // 邀请新用户赠送算力值 @@ -150,8 +150,9 @@ type SystemConfig struct { MjPower int `json:"mj_power,omitempty"` // MJ 绘画消耗算力 MjActionPower int `json:"mj_action_power,omitempty"` // MJ 操作(放大,变换)消耗算力 SdPower int `json:"sd_power,omitempty"` // SD 绘画消耗算力 - DallPower int `json:"dall_power,omitempty"` // DALLE3 绘图消耗算力 + DallPower int `json:"dall_power,omitempty"` // DALL-E-3 绘图消耗算力 SunoPower int `json:"suno_power,omitempty"` // Suno 生成歌曲消耗算力 + LumaPower int `json:"luma_power,omitempty"` // Luma 生成视频消耗算力 WechatCardURL string `json:"wechat_card_url,omitempty"` // 微信客服地址 @@ -165,4 +166,6 @@ type SystemConfig struct { IndexNavs []int `json:"index_navs"` // 首页显示的导航菜单 Copyright string `json:"copyright"` // 版权信息 MarkMapText string `json:"mark_map_text"` // 思维导入的默认文本 + + EnabledVerify bool `json:"enabled_verify"` // 是否启用验证码 } diff --git a/api/core/types/task.go b/api/core/types/task.go index 72b0d7c6..d41e592a 100644 --- a/api/core/types/task.go +++ b/api/core/types/task.go @@ -85,13 +85,41 @@ type SunoTask struct { Channel string `json:"channel"` UserId int `json:"user_id"` Type int `json:"type"` - TaskId string `json:"task_id"` Title string `json:"title"` - RefTaskId string `json:"ref_task_id"` - RefSongId string `json:"ref_song_id"` + RefTaskId string `json:"ref_task_id,omitempty"` + RefSongId string `json:"ref_song_id,omitempty"` Prompt string `json:"prompt"` // 提示词/歌词 Tags string `json:"tags"` Model string `json:"model"` - Instrumental bool `json:"instrumental"` // 是否纯音乐 - ExtendSecs int `json:"extend_secs"` // 延长秒杀 + Instrumental bool `json:"instrumental"` // 是否纯音乐 + ExtendSecs int `json:"extend_secs,omitempty"` // 延长秒杀 + SongId string `json:"song_id,omitempty"` // 合并歌曲ID + AudioURL string `json:"audio_url"` // 用户上传音频地址 +} + +const ( + VideoLuma = "luma" + VideoRunway = "runway" + VideoCog = "cog" +) + +type VideoTask struct { + Id uint `json:"id"` + Channel string `json:"channel"` + UserId int `json:"user_id"` + Type string `json:"type"` + TaskId string `json:"task_id"` + Prompt string `json:"prompt"` // 提示词 + Params VideoParams `json:"params"` +} + +type VideoParams struct { + PromptOptimize bool `json:"prompt_optimize"` // 是否优化提示词 + Loop bool `json:"loop"` // 是否循环参考图 + StartImgURL string `json:"start_img_url"` // 第一帧参考图地址 + EndImgURL string `json:"end_img_url"` // 最后一帧参考图地址 + Model string `json:"model"` // 使用哪个模型生成视频 + Radio string `json:"radio"` // 视频尺寸 + Style string `json:"style"` // 风格 + Duration int `json:"duration"` // 视频时长(秒) } diff --git a/api/handler/admin/admin_handler.go b/api/handler/admin/admin_handler.go index eaaaf8fd..f4677723 100644 --- a/api/handler/admin/admin_handler.go +++ b/api/handler/admin/admin_handler.go @@ -14,6 +14,7 @@ import ( "geekai/core/types" "geekai/handler" logger2 "geekai/logger" + "geekai/service" "geekai/store/model" "geekai/store/vo" "geekai/utils" @@ -28,33 +29,49 @@ import ( var logger = logger2.GetLogger() -// Manager 管理员 -type Manager struct { - Username string `json:"username"` - Password string `json:"password"` - Captcha string `json:"captcha"` // 验证码 - CaptchaId string `json:"captcha_id"` // 验证码id -} - const SuperManagerID = 1 type ManagerHandler struct { handler.BaseHandler - redis *redis.Client + redis *redis.Client + captcha *service.CaptchaService } -func NewAdminHandler(app *core.AppServer, db *gorm.DB, client *redis.Client) *ManagerHandler { - return &ManagerHandler{BaseHandler: handler.BaseHandler{DB: db, App: app}, redis: client} +func NewAdminHandler(app *core.AppServer, db *gorm.DB, client *redis.Client, captcha *service.CaptchaService) *ManagerHandler { + return &ManagerHandler{ + BaseHandler: handler.BaseHandler{DB: db, App: app}, + redis: client, + captcha: captcha, + } } // Login 登录 func (h *ManagerHandler) Login(c *gin.Context) { - var data Manager + var data struct { + Username string `json:"username"` + Password string `json:"password"` + Key string `json:"key,omitempty"` + Dots string `json:"dots,omitempty"` + X int `json:"x,omitempty"` + } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) return } + if h.App.SysConfig.EnabledVerify { + var check bool + if data.X != 0 { + check = h.captcha.SlideCheck(data) + } else { + check = h.captcha.Check(data) + } + if !check { + resp.ERROR(c, "请先完人机验证") + return + } + } + var manager model.AdminUser res := h.DB.Model(&model.AdminUser{}).Where("username = ?", data.Username).First(&manager) if res.Error != nil { diff --git a/api/handler/admin/user_handler.go b/api/handler/admin/user_handler.go index 07b93700..275be140 100644 --- a/api/handler/admin/user_handler.go +++ b/api/handler/admin/user_handler.go @@ -49,7 +49,7 @@ func (h *UserHandler) List(c *gin.Context) { } session.Model(&model.User{}).Count(&total) - res := session.Offset(offset).Limit(pageSize).Find(&items) + res := session.Offset(offset).Limit(pageSize).Order("id DESC").Find(&items) if res.Error == nil { for _, item := range items { var user vo.User @@ -204,33 +204,69 @@ func (h *UserHandler) ResetPass(c *gin.Context) { } func (h *UserHandler) Remove(c *gin.Context) { - id := h.GetInt(c, "id", 0) - if id <= 0 { + id := c.Query("id") + ids := c.QueryArray("ids[]") + if id != "" { + ids = append(ids, id) + } + if len(ids) == 0 { resp.ERROR(c, types.InvalidArgs) return } - // 删除用户 - res := h.DB.Where("id = ?", id).Delete(&model.User{}) - if res.Error != nil { + + tx := h.DB.Begin() + var err error + for _, id = range ids { + // 删除用户 + if err = tx.Where("id", id).Delete(&model.User{}).Error; err != nil { + break + } + // 删除聊天记录 + if err = tx.Unscoped().Where("user_id = ?", id).Delete(&model.ChatItem{}).Error; err != nil { + break + } + // 删除聊天历史记录 + if err = tx.Unscoped().Where("user_id = ?", id).Delete(&model.ChatMessage{}).Error; err != nil { + break + } + // 删除登录日志 + if err = tx.Where("user_id = ?", id).Delete(&model.UserLoginLog{}).Error; err != nil { + break + } + // 删除算力日志 + if err = tx.Where("user_id = ?", id).Delete(&model.PowerLog{}).Error; err != nil { + break + } + if err = tx.Where("user_id = ?", id).Delete(&model.InviteLog{}).Error; err != nil { + break + } + // 删除众筹日志 + if err = tx.Where("user_id = ?", id).Delete(&model.Redeem{}).Error; err != nil { + break + } + // 删除绘图任务 + if err = tx.Where("user_id = ?", id).Delete(&model.MidJourneyJob{}).Error; err != nil { + break + } + if err = tx.Where("user_id = ?", id).Delete(&model.SdJob{}).Error; err != nil { + break + } + if err = tx.Where("user_id = ?", id).Delete(&model.DallJob{}).Error; err != nil { + break + } + if err = tx.Where("user_id = ?", id).Delete(&model.SunoJob{}).Error; err != nil { + break + } + if err = tx.Where("user_id = ?", id).Delete(&model.VideoJob{}).Error; err != nil { + break + } + } + if err != nil { resp.ERROR(c, "删除失败") + tx.Rollback() return } - - // 删除聊天记录 - h.DB.Where("user_id = ?", id).Delete(&model.ChatItem{}) - // 删除聊天历史记录 - h.DB.Where("user_id = ?", id).Delete(&model.ChatMessage{}) - // 删除登录日志 - h.DB.Where("user_id = ?", id).Delete(&model.UserLoginLog{}) - // 删除算力日志 - h.DB.Where("user_id = ?", id).Delete(&model.PowerLog{}) - // 删除众筹日志 - h.DB.Where("user_id = ?", id).Delete(&model.Redeem{}) - // 删除绘图任务 - h.DB.Where("user_id = ?", id).Delete(&model.MidJourneyJob{}) - h.DB.Where("user_id = ?", id).Delete(&model.SdJob{}) - // 删除订单 - h.DB.Where("user_id = ?", id).Delete(&model.Order{}) + tx.Commit() resp.SUCCESS(c) } diff --git a/api/handler/chatimpl/chat_handler.go b/api/handler/chatimpl/chat_handler.go index 6b1e9f8e..730043e9 100644 --- a/api/handler/chatimpl/chat_handler.go +++ b/api/handler/chatimpl/chat_handler.go @@ -46,9 +46,10 @@ type ChatHandler struct { licenseService *service.LicenseService ReqCancelFunc *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function ChatContexts *types.LMap[string, []types.Message] // 聊天上下文 Map [chatId] => []Message + userService *service.UserService } -func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manager *oss.UploaderManager, licenseService *service.LicenseService) *ChatHandler { +func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manager *oss.UploaderManager, licenseService *service.LicenseService, userService *service.UserService) *ChatHandler { return &ChatHandler{ BaseHandler: handler.BaseHandler{App: app, DB: db}, redis: redis, @@ -56,6 +57,7 @@ func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manag licenseService: licenseService, ReqCancelFunc: types.NewLMap[string, context.CancelFunc](), ChatContexts: types.NewLMap[string, []types.Message](), + userService: userService, } } @@ -71,6 +73,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) { roleId := h.GetInt(c, "role_id", 0) chatId := c.Query("chat_id") modelId := h.GetInt(c, "model_id", 0) + tools := c.Query("tools") client := types.NewWsClient(ws) var chatRole model.ChatRole @@ -97,6 +100,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) { SessionId: sessionId, ClientIP: c.ClientIP(), UserId: h.GetLoginUserId(c), + Tools: tools, } // use old chat data override the chat model and role ID @@ -209,34 +213,37 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio } req.Temperature = session.Model.Temperature req.MaxTokens = session.Model.MaxTokens - // OpenAI 支持函数功能 - var items []model.Function - res = h.DB.Where("enabled", true).Find(&items) - if res.Error == nil { - var tools = make([]types.Tool, 0) - for _, v := range items { - var parameters map[string]interface{} - err = utils.JsonDecode(v.Parameters, ¶meters) - if err != nil { - continue - } - tool := types.Tool{ - Type: "function", - Function: types.Function{ - Name: v.Name, - Description: v.Description, - Parameters: parameters, - }, - } - if v, ok := parameters["required"]; v == nil || !ok { - tool.Function.Parameters["required"] = []string{} - } - tools = append(tools, tool) - } - if len(tools) > 0 { - req.Tools = tools - req.ToolChoice = "auto" + if session.Tools != "" { + toolIds := strings.Split(session.Tools, ",") + var items []model.Function + res = h.DB.Where("enabled", true).Where("id IN ?", toolIds).Find(&items) + if res.Error == nil { + var tools = make([]types.Tool, 0) + for _, v := range items { + var parameters map[string]interface{} + err = utils.JsonDecode(v.Parameters, ¶meters) + if err != nil { + continue + } + tool := types.Tool{ + Type: "function", + Function: types.Function{ + Name: v.Name, + Description: v.Description, + Parameters: parameters, + }, + } + if v, ok := parameters["required"]; v == nil || !ok { + tool.Function.Parameters["required"] = []string{} + } + tools = append(tools, tool) + } + + if len(tools) > 0 { + req.Tools = tools + req.ToolChoice = "auto" + } } } @@ -270,7 +277,8 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio tks, _ := utils.CalcTokens(utils.JsonEncode(req.Tools), req.Model) tokens += tks + promptTokens - for _, v := range messages { + for i := len(messages) - 1; i >= 0; i-- { + v := messages[i] tks, _ := utils.CalcTokens(v.Content, req.Model) // 上下文 token 超出了模型的最大上下文长度 if tokens+tks >= session.Model.MaxContext { @@ -481,24 +489,15 @@ func (h *ChatHandler) subUserPower(userVo vo.User, session *types.ChatSession, p if session.Model.Power > 0 { power = session.Model.Power } - res := h.DB.Model(&model.User{}).Where("id = ?", userVo.Id).UpdateColumn("power", gorm.Expr("power - ?", power)) - if res.Error == nil { - // 记录算力消费日志 - var u model.User - h.DB.Where("id", userVo.Id).First(&u) - h.DB.Create(&model.PowerLog{ - UserId: userVo.Id, - Username: userVo.Username, - Type: types.PowerConsume, - Amount: power, - Mark: types.PowerSub, - Balance: u.Power, - Model: session.Model.Value, - Remark: fmt.Sprintf("模型名称:%s, 提问长度:%d,回复长度:%d", session.Model.Name, promptTokens, replyTokens), - CreatedAt: time.Now(), - }) - } + err := h.userService.DecreasePower(int(userVo.Id), power, model.PowerLog{ + Type: types.PowerConsume, + Model: session.Model.Value, + Remark: fmt.Sprintf("模型名称:%s, 提问长度:%d,回复长度:%d", session.Model.Name, promptTokens, replyTokens), + }) + if err != nil { + logger.Error(err) + } } func (h *ChatHandler) saveChatHistory( @@ -544,9 +543,9 @@ func (h *ChatHandler) saveChatHistory( } historyUserMsg.CreatedAt = promptCreatedAt historyUserMsg.UpdatedAt = promptCreatedAt - res := h.DB.Save(&historyUserMsg) - if res.Error != nil { - logger.Error("failed to save prompt history message: ", res.Error) + err = h.DB.Save(&historyUserMsg).Error + if err != nil { + logger.Error("failed to save prompt history message: ", err) } // for reply @@ -566,9 +565,9 @@ func (h *ChatHandler) saveChatHistory( } historyReplyMsg.CreatedAt = replyCreatedAt historyReplyMsg.UpdatedAt = replyCreatedAt - res = h.DB.Create(&historyReplyMsg) - if res.Error != nil { - logger.Error("failed to save reply history message: ", res.Error) + err = h.DB.Create(&historyReplyMsg).Error + if err != nil { + logger.Error("failed to save reply history message: ", err) } // 更新用户算力 @@ -577,8 +576,8 @@ func (h *ChatHandler) saveChatHistory( } // 保存当前会话 var chatItem model.ChatItem - res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem) - if res.Error != nil { + err = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem).Error + if err != nil { chatItem.ChatId = session.ChatId chatItem.UserId = userVo.Id chatItem.RoleId = role.Id @@ -589,7 +588,10 @@ func (h *ChatHandler) saveChatHistory( chatItem.Title = prompt } chatItem.Model = req.Model - h.DB.Create(&chatItem) + err = h.DB.Create(&chatItem).Error + if err != nil { + logger.Error("failed to save chat item: ", err) + } } } diff --git a/api/handler/dalle_handler.go b/api/handler/dalle_handler.go index d09f0651..bcf44ba8 100644 --- a/api/handler/dalle_handler.go +++ b/api/handler/dalle_handler.go @@ -11,32 +11,33 @@ import ( "fmt" "geekai/core" "geekai/core/types" + "geekai/service" "geekai/service/dalle" "geekai/service/oss" "geekai/store/model" "geekai/store/vo" "geekai/utils" "geekai/utils/resp" - "github.com/gorilla/websocket" - "net/http" - "time" - "github.com/gin-gonic/gin" "github.com/go-redis/redis/v8" + "github.com/gorilla/websocket" "gorm.io/gorm" + "net/http" ) type DallJobHandler struct { BaseHandler - redis *redis.Client - service *dalle.Service - uploader *oss.UploaderManager + redis *redis.Client + dallService *dalle.Service + uploader *oss.UploaderManager + userService *service.UserService } -func NewDallJobHandler(app *core.AppServer, db *gorm.DB, service *dalle.Service, manager *oss.UploaderManager) *DallJobHandler { +func NewDallJobHandler(app *core.AppServer, db *gorm.DB, service *dalle.Service, manager *oss.UploaderManager, userService *service.UserService) *DallJobHandler { return &DallJobHandler{ - service: service, - uploader: manager, + dallService: service, + uploader: manager, + userService: userService, BaseHandler: BaseHandler{ App: app, DB: db, @@ -61,14 +62,14 @@ func (h *DallJobHandler) Client(c *gin.Context) { } client := types.NewWsClient(ws) - h.service.Clients.Put(uint(userId), client) + h.dallService.Clients.Put(uint(userId), client) logger.Infof("New websocket connected, IP: %s", c.RemoteIP()) go func() { for { _, msg, err := client.Receive() if err != nil { client.Close() - h.service.Clients.Delete(uint(userId)) + h.dallService.Clients.Delete(uint(userId)) return } @@ -127,7 +128,7 @@ func (h *DallJobHandler) Image(c *gin.Context) { return } - h.service.PushTask(types.DallTask{ + h.dallService.PushTask(types.DallTask{ JobId: job.Id, UserId: uint(userId), Prompt: data.Prompt, @@ -137,7 +138,7 @@ func (h *DallJobHandler) Image(c *gin.Context) { Power: job.Power, }) - client := h.service.Clients.Get(job.UserId) + client := h.dallService.Clients.Get(job.UserId) if client != nil { _ = client.Send([]byte("Task Updated")) } @@ -175,7 +176,7 @@ func (h *DallJobHandler) JobList(c *gin.Context) { } // JobList 获取任务列表 -func (h *DallJobHandler) getData(finish bool, userId uint, page int, pageSize int, publish bool) (error, []vo.DallJob) { +func (h *DallJobHandler) getData(finish bool, userId uint, page int, pageSize int, publish bool) (error, vo.Page) { session := h.DB.Session(&gorm.Session{}) if finish { @@ -193,11 +194,14 @@ func (h *DallJobHandler) getData(finish bool, userId uint, page int, pageSize in offset := (page - 1) * pageSize session = session.Offset(offset).Limit(pageSize) } + // 统计总数 + var total int64 + session.Model(&model.DallJob{}).Count(&total) var items []model.DallJob res := session.Find(&items) if res.Error != nil { - return res.Error, nil + return res.Error, vo.Page{} } var jobs = make([]vo.DallJob, 0) @@ -210,7 +214,7 @@ func (h *DallJobHandler) getData(finish bool, userId uint, page int, pageSize in jobs = append(jobs, job) } - return nil, jobs + return nil, vo.NewPage(total, page, pageSize, jobs) } // Remove remove task image @@ -233,26 +237,11 @@ func (h *DallJobHandler) Remove(c *gin.Context) { // 如果任务未完成,或者任务失败,则恢复用户算力 if job.Progress != 100 { - err := tx.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)).Error - if err != nil { - tx.Rollback() - resp.ERROR(c, err.Error()) - return - } - - var user model.User - tx.Where("id = ?", job.UserId).First(&user) - err = tx.Create(&model.PowerLog{ - UserId: user.Id, - Username: user.Username, - Type: types.PowerRefund, - Amount: job.Power, - Balance: user.Power, - Mark: types.PowerAdd, - Model: "dall-e-3", - Remark: fmt.Sprintf("任务失败,退回算力。任务ID:%d,Err: %s", job.Id, job.ErrMsg), - CreatedAt: time.Now(), - }).Error + err := h.userService.IncreasePower(int(job.UserId), job.Power, model.PowerLog{ + Type: types.PowerRefund, + Model: "dall-e-3", + Remark: fmt.Sprintf("任务失败,退回算力。任务ID:%d,Err: %s", job.Id, job.ErrMsg), + }) if err != nil { tx.Rollback() resp.ERROR(c, err.Error()) diff --git a/api/handler/function_handler.go b/api/handler/function_handler.go index 6917efde..f1838d4d 100644 --- a/api/handler/function_handler.go +++ b/api/handler/function_handler.go @@ -8,15 +8,16 @@ package handler // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import ( + "errors" + "fmt" "geekai/core" "geekai/core/types" "geekai/service/dalle" "geekai/service/oss" "geekai/store/model" + "geekai/store/vo" "geekai/utils" "geekai/utils/resp" - "errors" - "fmt" "strings" "time" @@ -224,3 +225,27 @@ func (h *FunctionHandler) Dall3(c *gin.Context) { resp.SUCCESS(c, content) } + +// List 获取所有的工具函数列表 +func (h *FunctionHandler) List(c *gin.Context) { + var items []model.Function + err := h.DB.Where("enabled", true).Find(&items).Error + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + tools := make([]vo.Function, 0) + for _, v := range items { + var f vo.Function + err = utils.CopyObject(v, &f) + if err != nil { + continue + } + f.Action = "" + f.Token = "" + tools = append(tools, f) + } + + resp.SUCCESS(c, tools) +} diff --git a/api/handler/invite_handler.go b/api/handler/invite_handler.go index 3e4fdb53..e6e5c029 100644 --- a/api/handler/invite_handler.go +++ b/api/handler/invite_handler.go @@ -9,7 +9,6 @@ package handler import ( "geekai/core" - "geekai/core/types" "geekai/store/model" "geekai/store/vo" "geekai/utils" @@ -59,23 +58,16 @@ func (h *InviteHandler) Code(c *gin.Context) { // List Log 用户邀请记录 func (h *InviteHandler) List(c *gin.Context) { - - var data struct { - Page int `json:"page"` - PageSize int `json:"page_size"` - } - if err := c.ShouldBindJSON(&data); err != nil { - resp.ERROR(c, types.InvalidArgs) - return - } + page := h.GetInt(c, "page", 1) + pageSize := h.GetInt(c, "page_size", 20) userId := h.GetLoginUserId(c) session := h.DB.Session(&gorm.Session{}).Where("inviter_id = ?", userId) var total int64 session.Model(&model.InviteLog{}).Count(&total) var items []model.InviteLog var list = make([]vo.InviteLog, 0) - offset := (data.Page - 1) * data.PageSize - res := session.Order("id DESC").Offset(offset).Limit(data.PageSize).Find(&items) + offset := (page - 1) * pageSize + res := session.Order("id DESC").Offset(offset).Limit(pageSize).Find(&items) if res.Error == nil { for _, item := range items { var v vo.InviteLog @@ -89,7 +81,7 @@ func (h *InviteHandler) List(c *gin.Context) { } } } - resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, list)) + resp.SUCCESS(c, vo.NewPage(total, page, pageSize, list)) } // Hits 访问邀请码 diff --git a/api/handler/markmap_handler.go b/api/handler/markmap_handler.go index 8196a81e..b4147deb 100644 --- a/api/handler/markmap_handler.go +++ b/api/handler/markmap_handler.go @@ -15,6 +15,7 @@ import ( "fmt" "geekai/core" "geekai/core/types" + "geekai/service" "geekai/store/model" "geekai/utils" "github.com/gin-gonic/gin" @@ -30,13 +31,15 @@ import ( // MarkMapHandler 生成思维导图 type MarkMapHandler struct { BaseHandler - clients *types.LMap[int, *types.WsClient] + clients *types.LMap[int, *types.WsClient] + userService *service.UserService } -func NewMarkMapHandler(app *core.AppServer, db *gorm.DB) *MarkMapHandler { +func NewMarkMapHandler(app *core.AppServer, db *gorm.DB, userService *service.UserService) *MarkMapHandler { return &MarkMapHandler{ BaseHandler: BaseHandler{App: app, DB: db}, clients: types.NewLMap[int, *types.WsClient](), + userService: userService, } } @@ -185,22 +188,13 @@ func (h *MarkMapHandler) sendMessage(client *types.WsClient, prompt string, mode // 扣减算力 if chatModel.Power > 0 { - res = h.DB.Model(&model.User{}).Where("id", userId).UpdateColumn("power", gorm.Expr("power - ?", chatModel.Power)) - if res.Error == nil { - // 记录算力消费日志 - var u model.User - h.DB.Where("id", userId).First(&u) - h.DB.Create(&model.PowerLog{ - UserId: u.Id, - Username: u.Username, - Type: types.PowerConsume, - Amount: chatModel.Power, - Mark: types.PowerSub, - Balance: u.Power, - Model: chatModel.Value, - Remark: fmt.Sprintf("AI绘制思维导图,模型名称:%s, ", chatModel.Value), - CreatedAt: time.Now(), - }) + err = h.userService.DecreasePower(userId, chatModel.Power, model.PowerLog{ + Type: types.PowerConsume, + Model: chatModel.Value, + Remark: fmt.Sprintf("AI绘制思维导图,模型名称:%s, ", chatModel.Value), + }) + if err != nil { + return err } } diff --git a/api/handler/mj_handler.go b/api/handler/mj_handler.go index 212729b2..34996c81 100644 --- a/api/handler/mj_handler.go +++ b/api/handler/mj_handler.go @@ -30,16 +30,18 @@ import ( type MidJourneyHandler struct { BaseHandler - service *mj.Service - snowflake *service.Snowflake - uploader *oss.UploaderManager + mjService *mj.Service + snowflake *service.Snowflake + uploader *oss.UploaderManager + userService *service.UserService } -func NewMidJourneyHandler(app *core.AppServer, db *gorm.DB, snowflake *service.Snowflake, service *mj.Service, manager *oss.UploaderManager) *MidJourneyHandler { +func NewMidJourneyHandler(app *core.AppServer, db *gorm.DB, snowflake *service.Snowflake, service *mj.Service, manager *oss.UploaderManager, userService *service.UserService) *MidJourneyHandler { return &MidJourneyHandler{ - snowflake: snowflake, - service: service, - uploader: manager, + snowflake: snowflake, + mjService: service, + uploader: manager, + userService: userService, BaseHandler: BaseHandler{ App: app, DB: db, @@ -80,7 +82,7 @@ func (h *MidJourneyHandler) Client(c *gin.Context) { } client := types.NewWsClient(ws) - h.service.Clients.Put(uint(userId), client) + h.mjService.Clients.Put(uint(userId), client) logger.Infof("New websocket connected, IP: %s", c.RemoteIP()) } @@ -196,7 +198,7 @@ func (h *MidJourneyHandler) Image(c *gin.Context) { return } - h.service.PushTask(types.MjTask{ + h.mjService.PushTask(types.MjTask{ Id: job.Id, TaskId: taskId, Type: types.TaskType(data.TaskType), @@ -208,28 +210,22 @@ func (h *MidJourneyHandler) Image(c *gin.Context) { Mode: h.App.SysConfig.MjMode, }) - client := h.service.Clients.Get(uint(job.UserId)) + client := h.mjService.Clients.Get(uint(job.UserId)) if client != nil { _ = client.Send([]byte("Task Updated")) } // update user's power - tx := h.DB.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power - ?", job.Power)) - // 记录算力变化日志 - if tx.Error == nil && tx.RowsAffected > 0 { - user, _ := h.GetLoginUser(c) - h.DB.Create(&model.PowerLog{ - UserId: user.Id, - Username: user.Username, - Type: types.PowerConsume, - Amount: job.Power, - Balance: user.Power - job.Power, - Mark: types.PowerSub, - Model: "mid-journey", - Remark: fmt.Sprintf("%s操作,任务ID:%s", opt, job.TaskId), - CreatedAt: time.Now(), - }) + err = h.userService.DecreasePower(job.UserId, job.Power, model.PowerLog{ + Type: types.PowerConsume, + Model: "mid-journey", + Remark: fmt.Sprintf("%s操作,任务ID:%s", opt, job.TaskId), + }) + if err != nil { + resp.ERROR(c, err.Error()) + return } + resp.SUCCESS(c) } @@ -269,7 +265,7 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) { return } - h.service.PushTask(types.MjTask{ + h.mjService.PushTask(types.MjTask{ Id: job.Id, Type: types.TaskUpscale, UserId: userId, @@ -280,27 +276,22 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) { Mode: h.App.SysConfig.MjMode, }) - client := h.service.Clients.Get(uint(job.UserId)) + client := h.mjService.Clients.Get(uint(job.UserId)) if client != nil { _ = client.Send([]byte("Task Updated")) } + // update user's power - tx := h.DB.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power - ?", job.Power)) - // 记录算力变化日志 - if tx.Error == nil && tx.RowsAffected > 0 { - user, _ := h.GetLoginUser(c) - h.DB.Create(&model.PowerLog{ - UserId: user.Id, - Username: user.Username, - Type: types.PowerConsume, - Amount: job.Power, - Balance: user.Power - job.Power, - Mark: types.PowerSub, - Model: "mid-journey", - Remark: fmt.Sprintf("Upscale 操作,任务ID:%s", job.TaskId), - CreatedAt: time.Now(), - }) + err := h.userService.DecreasePower(job.UserId, job.Power, model.PowerLog{ + Type: types.PowerConsume, + Model: "mid-journey", + Remark: fmt.Sprintf("Upscale 操作,任务ID:%s", job.TaskId), + }) + if err != nil { + resp.ERROR(c, err.Error()) + return } + resp.SUCCESS(c) } @@ -334,7 +325,7 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) { return } - h.service.PushTask(types.MjTask{ + h.mjService.PushTask(types.MjTask{ Id: job.Id, Type: types.TaskVariation, UserId: userId, @@ -345,28 +336,21 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) { Mode: h.App.SysConfig.MjMode, }) - client := h.service.Clients.Get(uint(job.UserId)) + client := h.mjService.Clients.Get(uint(job.UserId)) if client != nil { _ = client.Send([]byte("Task Updated")) } - // update user's power - tx := h.DB.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power - ?", job.Power)) - // 记录算力变化日志 - if tx.Error == nil && tx.RowsAffected > 0 { - user, _ := h.GetLoginUser(c) - h.DB.Create(&model.PowerLog{ - UserId: user.Id, - Username: user.Username, - Type: types.PowerConsume, - Amount: job.Power, - Balance: user.Power - job.Power, - Mark: types.PowerSub, - Model: "mid-journey", - Remark: fmt.Sprintf("Variation 操作,任务ID:%s", job.TaskId), - CreatedAt: time.Now(), - }) + err := h.userService.DecreasePower(job.UserId, job.Power, model.PowerLog{ + Type: types.PowerConsume, + Model: "mid-journey", + Remark: fmt.Sprintf("Variation 操作,任务ID:%s", job.TaskId), + }) + if err != nil { + resp.ERROR(c, err.Error()) + return } + resp.SUCCESS(c) } @@ -401,7 +385,7 @@ func (h *MidJourneyHandler) JobList(c *gin.Context) { } // JobList 获取 MJ 任务列表 -func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize int, publish bool) (error, []vo.MidJourneyJob) { +func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize int, publish bool) (error, vo.Page) { session := h.DB.Session(&gorm.Session{}) if finish { session = session.Where("progress >= ?", 100).Order("id DESC") @@ -419,10 +403,14 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize session = session.Offset(offset).Limit(pageSize) } + // 统计总数 + var total int64 + session.Model(&model.MidJourneyJob{}).Count(&total) + var items []model.MidJourneyJob res := session.Find(&items) if res.Error != nil { - return res.Error, nil + return res.Error, vo.Page{} } var jobs = make([]vo.MidJourneyJob, 0) @@ -442,7 +430,7 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize jobs = append(jobs, job) } - return nil, jobs + return nil, vo.NewPage(total, page, pageSize, jobs) } // Remove remove task image @@ -465,25 +453,11 @@ func (h *MidJourneyHandler) Remove(c *gin.Context) { // 如果任务未完成,或者任务失败,则恢复用户算力 if job.Progress != 100 { - err := tx.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)).Error - if err != nil { - tx.Rollback() - resp.ERROR(c, err.Error()) - return - } - var user model.User - tx.Where("id = ?", job.UserId).First(&user) - err = tx.Create(&model.PowerLog{ - UserId: user.Id, - Username: user.Username, - Type: types.PowerRefund, - Amount: job.Power, - Balance: user.Power, - Mark: types.PowerAdd, - Model: "mid-journey", - Remark: fmt.Sprintf("绘画任务失败,退回算力。任务ID:%s,Err: %s", job.TaskId, job.ErrMsg), - CreatedAt: time.Now(), - }).Error + err := h.userService.IncreasePower(job.UserId, job.Power, model.PowerLog{ + Type: types.PowerRefund, + Model: "mid-journey", + Remark: fmt.Sprintf("任务失败,退回算力。任务ID:%d,Err: %s", job.Id, job.ErrMsg), + }) if err != nil { tx.Rollback() resp.ERROR(c, err.Error()) @@ -498,7 +472,7 @@ func (h *MidJourneyHandler) Remove(c *gin.Context) { logger.Error("remove image failed: ", err) } - client := h.service.Clients.Get(uint(job.UserId)) + client := h.mjService.Clients.Get(uint(job.UserId)) if client != nil { _ = client.Send([]byte("Task Updated")) } diff --git a/api/handler/redeem_handler.go b/api/handler/redeem_handler.go index 4f557ce9..b6759ffd 100644 --- a/api/handler/redeem_handler.go +++ b/api/handler/redeem_handler.go @@ -11,6 +11,7 @@ import ( "fmt" "geekai/core" "geekai/core/types" + "geekai/service" "geekai/store/model" "geekai/utils/resp" "github.com/gin-gonic/gin" @@ -21,11 +22,12 @@ import ( type RedeemHandler struct { BaseHandler - lock sync.Mutex + lock sync.Mutex + userService *service.UserService } -func NewRedeemHandler(app *core.AppServer, db *gorm.DB) *RedeemHandler { - return &RedeemHandler{BaseHandler: BaseHandler{App: app, DB: db}} +func NewRedeemHandler(app *core.AppServer, db *gorm.DB, userService *service.UserService) *RedeemHandler { + return &RedeemHandler{BaseHandler: BaseHandler{App: app, DB: db}, userService: userService} } func (h *RedeemHandler) Verify(c *gin.Context) { @@ -59,7 +61,11 @@ func (h *RedeemHandler) Verify(c *gin.Context) { } tx := h.DB.Begin() - err := tx.Model(&model.User{}).Where("id", userId).UpdateColumn("power", gorm.Expr("power + ?", item.Power)).Error + err := h.userService.IncreasePower(int(userId), item.Power, model.PowerLog{ + Type: types.PowerRedeem, + Model: "兑换码", + Remark: fmt.Sprintf("兑换码核销,算力:%d,兑换码:%s...", item.Power, item.Code[:10]), + }) if err != nil { tx.Rollback() resp.ERROR(c, err.Error()) @@ -76,26 +82,6 @@ func (h *RedeemHandler) Verify(c *gin.Context) { return } - // 记录算力充值日志 - var user model.User - err = tx.Where("id", userId).First(&user).Error - if err != nil { - tx.Rollback() - resp.ERROR(c, err.Error()) - return - } - - h.DB.Create(&model.PowerLog{ - UserId: userId, - Username: user.Username, - Type: types.PowerRedeem, - Amount: item.Power, - Balance: user.Power, - Mark: types.PowerAdd, - Model: "兑换码", - Remark: fmt.Sprintf("兑换码核销,算力:%d,兑换码:%s...", item.Power, item.Code[:10]), - CreatedAt: time.Now(), - }) tx.Commit() resp.SUCCESS(c) diff --git a/api/handler/sd_handler.go b/api/handler/sd_handler.go index 9cbc60fb..6471e94e 100644 --- a/api/handler/sd_handler.go +++ b/api/handler/sd_handler.go @@ -31,19 +31,27 @@ import ( type SdJobHandler struct { BaseHandler - redis *redis.Client - service *sd.Service - uploader *oss.UploaderManager - snowflake *service.Snowflake - leveldb *store.LevelDB + redis *redis.Client + sdService *sd.Service + uploader *oss.UploaderManager + snowflake *service.Snowflake + leveldb *store.LevelDB + userService *service.UserService } -func NewSdJobHandler(app *core.AppServer, db *gorm.DB, service *sd.Service, manager *oss.UploaderManager, snowflake *service.Snowflake, levelDB *store.LevelDB) *SdJobHandler { +func NewSdJobHandler(app *core.AppServer, + db *gorm.DB, + service *sd.Service, + manager *oss.UploaderManager, + snowflake *service.Snowflake, + userService *service.UserService, + levelDB *store.LevelDB) *SdJobHandler { return &SdJobHandler{ - service: service, - uploader: manager, - snowflake: snowflake, - leveldb: levelDB, + sdService: service, + uploader: manager, + snowflake: snowflake, + leveldb: levelDB, + userService: userService, BaseHandler: BaseHandler{ App: app, DB: db, @@ -68,7 +76,7 @@ func (h *SdJobHandler) Client(c *gin.Context) { } client := types.NewWsClient(ws) - h.service.Clients.Put(uint(userId), client) + h.sdService.Clients.Put(uint(userId), client) logger.Infof("New websocket connected, IP: %s", c.RemoteIP()) } @@ -159,34 +167,27 @@ func (h *SdJobHandler) Image(c *gin.Context) { return } - h.service.PushTask(types.SdTask{ + h.sdService.PushTask(types.SdTask{ Id: int(job.Id), Type: types.TaskImage, Params: params, UserId: userId, }) - client := h.service.Clients.Get(uint(job.UserId)) + client := h.sdService.Clients.Get(uint(job.UserId)) if client != nil { _ = client.Send([]byte("Task Updated")) } // update user's power - tx := h.DB.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power - ?", job.Power)) - // 记录算力变化日志 - if tx.Error == nil && tx.RowsAffected > 0 { - user, _ := h.GetLoginUser(c) - h.DB.Create(&model.PowerLog{ - UserId: user.Id, - Username: user.Username, - Type: types.PowerConsume, - Amount: job.Power, - Balance: user.Power - job.Power, - Mark: types.PowerSub, - Model: "stable-diffusion", - Remark: fmt.Sprintf("绘图操作,任务ID:%s", job.TaskId), - CreatedAt: time.Now(), - }) + err = h.userService.DecreasePower(job.UserId, job.Power, model.PowerLog{ + Type: types.PowerConsume, + Model: "stable-diffusion", + Remark: fmt.Sprintf("绘图操作,任务ID:%s", job.TaskId), + }) + if err != nil { + resp.ERROR(c, err.Error()) + return } resp.SUCCESS(c) @@ -223,7 +224,7 @@ func (h *SdJobHandler) JobList(c *gin.Context) { } // JobList 获取 MJ 任务列表 -func (h *SdJobHandler) getData(finish bool, userId uint, page int, pageSize int, publish bool) (error, []vo.SdJob) { +func (h *SdJobHandler) getData(finish bool, userId uint, page int, pageSize int, publish bool) (error, vo.Page) { session := h.DB.Session(&gorm.Session{}) if finish { @@ -242,10 +243,14 @@ func (h *SdJobHandler) getData(finish bool, userId uint, page int, pageSize int, session = session.Offset(offset).Limit(pageSize) } + // 统计总数 + var total int64 + session.Model(&model.SdJob{}).Count(&total) + var items []model.SdJob res := session.Find(&items) if res.Error != nil { - return res.Error, nil + return res.Error, vo.Page{} } var jobs = make([]vo.SdJob, 0) @@ -267,7 +272,7 @@ func (h *SdJobHandler) getData(finish bool, userId uint, page int, pageSize int, jobs = append(jobs, job) } - return nil, jobs + return nil, vo.NewPage(total, page, pageSize, jobs) } // Remove remove task image @@ -290,25 +295,11 @@ func (h *SdJobHandler) Remove(c *gin.Context) { // 如果任务未完成,或者任务失败,则恢复用户算力 if job.Progress != 100 { - err := tx.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)).Error - if err != nil { - tx.Rollback() - resp.ERROR(c, err.Error()) - return - } - var user model.User - tx.Where("id = ?", job.UserId).First(&user) - err = tx.Create(&model.PowerLog{ - UserId: user.Id, - Username: user.Username, - Type: types.PowerRefund, - Amount: job.Power, - Balance: user.Power, - Mark: types.PowerAdd, - Model: "stable-diffusion", - Remark: fmt.Sprintf("任务失败,退回算力。任务ID:%s, Err: %s", job.TaskId, job.ErrMsg), - CreatedAt: time.Now(), - }).Error + err := h.userService.IncreasePower(job.UserId, job.Power, model.PowerLog{ + Type: types.PowerRefund, + Model: "stable-diffusion", + Remark: fmt.Sprintf("任务失败,退回算力。任务ID:%s, Err: %s", job.TaskId, job.ErrMsg), + }) if err != nil { tx.Rollback() resp.ERROR(c, err.Error()) diff --git a/api/handler/sms_handler.go b/api/handler/sms_handler.go index 680f73e1..b64962c3 100644 --- a/api/handler/sms_handler.go +++ b/api/handler/sms_handler.go @@ -56,15 +56,17 @@ func (h *SmsHandler) SendCode(c *gin.Context) { resp.ERROR(c, types.InvalidArgs) return } - var check bool - if data.X != 0 { - check = h.captcha.SlideCheck(data) - } else { - check = h.captcha.Check(data) - } - if !check { - resp.ERROR(c, "验证码错误,请先完人机验证") - return + if h.App.SysConfig.EnabledVerify { + var check bool + if data.X != 0 { + check = h.captcha.SlideCheck(data) + } else { + check = h.captcha.Check(data) + } + if !check { + resp.ERROR(c, "请先完人机验证") + return + } } code := utils.RandomNumber(6) diff --git a/api/handler/suno_handler.go b/api/handler/suno_handler.go index 2ceed2c1..06dfce9c 100644 --- a/api/handler/suno_handler.go +++ b/api/handler/suno_handler.go @@ -11,6 +11,7 @@ import ( "fmt" "geekai/core" "geekai/core/types" + "geekai/service" "geekai/service/oss" "geekai/service/suno" "geekai/store/model" @@ -26,18 +27,20 @@ import ( type SunoHandler struct { BaseHandler - service *suno.Service - uploader *oss.UploaderManager + sunoService *suno.Service + uploader *oss.UploaderManager + userService *service.UserService } -func NewSunoHandler(app *core.AppServer, db *gorm.DB, service *suno.Service, uploader *oss.UploaderManager) *SunoHandler { +func NewSunoHandler(app *core.AppServer, db *gorm.DB, service *suno.Service, uploader *oss.UploaderManager, userService *service.UserService) *SunoHandler { return &SunoHandler{ BaseHandler: BaseHandler{ App: app, DB: db, }, - service: service, - uploader: uploader, + sunoService: service, + uploader: uploader, + userService: userService, } } @@ -58,7 +61,7 @@ func (h *SunoHandler) Client(c *gin.Context) { } client := types.NewWsClient(ws) - h.service.Clients.Put(uint(userId), client) + h.sunoService.Clients.Put(uint(userId), client) logger.Infof("New websocket connected, IP: %s", c.RemoteIP()) } @@ -72,15 +75,32 @@ func (h *SunoHandler) Create(c *gin.Context) { Tags string `json:"tags"` Title string `json:"title"` Type int `json:"type"` - RefTaskId string `json:"ref_task_id"` // 续写的任务id - ExtendSecs int `json:"extend_secs"` // 续写秒数 - RefSongId string `json:"ref_song_id"` // 续写的歌曲id + RefTaskId string `json:"ref_task_id"` // 续写的任务id + ExtendSecs int `json:"extend_secs"` // 续写秒数 + RefSongId string `json:"ref_song_id"` // 续写的歌曲id + SongId string `json:"song_id,omitempty"` // 要拼接的歌曲id + AudioURL string `json:"audio_url,omitempty"` // 上传自己创作的歌曲 } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) return } + // 歌曲拼接 + if data.SongId != "" && data.Type == 3 { + var song model.SunoJob + if err := h.DB.Where("song_id = ?", data.SongId).First(&song).Error; err == nil { + data.Instrumental = song.Instrumental + data.Model = song.ModelName + data.Tags = song.Tags + } + // 拼接歌词 + var refSong model.SunoJob + if err := h.DB.Where("song_id = ?", data.RefSongId).First(&refSong).Error; err == nil { + data.Prompt = fmt.Sprintf("%s\n%s", song.Prompt, refSong.Prompt) + } + } + // 插入数据库 job := model.SunoJob{ UserId: int(h.GetLoginUserId(c)), @@ -106,7 +126,7 @@ func (h *SunoHandler) Create(c *gin.Context) { } // 创建任务 - h.service.PushTask(types.SunoTask{ + h.sunoService.PushTask(types.SunoTask{ Id: job.Id, UserId: job.UserId, Type: job.Type, @@ -118,27 +138,22 @@ func (h *SunoHandler) Create(c *gin.Context) { Tags: data.Tags, Model: data.Model, Instrumental: data.Instrumental, + SongId: data.SongId, + AudioURL: data.AudioURL, }) // update user's power - tx = h.DB.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power - ?", job.Power)) - // 记录算力变化日志 - if tx.Error == nil && tx.RowsAffected > 0 { - user, _ := h.GetLoginUser(c) - h.DB.Create(&model.PowerLog{ - UserId: user.Id, - Username: user.Username, - Type: types.PowerConsume, - Amount: job.Power, - Balance: user.Power - job.Power, - Mark: types.PowerSub, - Model: job.ModelName, - Remark: fmt.Sprintf("Suno 文生歌曲,%s", job.ModelName), - CreatedAt: time.Now(), - }) + err := h.userService.DecreasePower(job.UserId, job.Power, model.PowerLog{ + Type: types.PowerConsume, + Remark: fmt.Sprintf("Suno 文生歌曲,%s", job.ModelName), + CreatedAt: time.Now(), + }) + if err != nil { + resp.ERROR(c, err.Error()) + return } - client := h.service.Clients.Get(uint(job.UserId)) + client := h.sunoService.Clients.Get(uint(job.UserId)) if client != nil { _ = client.Send([]byte("Task Updated")) } @@ -147,8 +162,8 @@ func (h *SunoHandler) Create(c *gin.Context) { func (h *SunoHandler) List(c *gin.Context) { userId := h.GetLoginUserId(c) - page := h.GetInt(c, "page", 0) - pageSize := h.GetInt(c, "page_size", 0) + page := h.GetInt(c, "page", 1) + pageSize := h.GetInt(c, "page_size", 20) session := h.DB.Session(&gorm.Session{}).Where("user_id", userId) // 统计总数 @@ -220,25 +235,11 @@ func (h *SunoHandler) Remove(c *gin.Context) { // 如果任务未完成,或者任务失败,则恢复用户算力 if job.Progress != 100 { - err := tx.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)).Error - if err != nil { - tx.Rollback() - resp.ERROR(c, err.Error()) - return - } - var user model.User - tx.Where("id = ?", job.UserId).First(&user) - err = tx.Create(&model.PowerLog{ - UserId: user.Id, - Username: user.Username, - Type: types.PowerRefund, - Amount: job.Power, - Balance: user.Power, - Mark: types.PowerAdd, - Model: job.ModelName, - Remark: fmt.Sprintf("Suno 任务失败,退回算力。任务ID:%s,Err:%s", job.TaskId, job.ErrMsg), - CreatedAt: time.Now(), - }).Error + err := h.userService.IncreasePower(job.UserId, job.Power, model.PowerLog{ + Type: types.PowerRefund, + Model: job.ModelName, + Remark: fmt.Sprintf("Suno 任务失败,退回算力。任务ID:%s,Err:%s", job.TaskId, job.ErrMsg), + }) if err != nil { tx.Rollback() resp.ERROR(c, err.Error()) diff --git a/api/handler/upload_handler.go b/api/handler/upload_handler.go index 9d2c8706..d9c75ec2 100644 --- a/api/handler/upload_handler.go +++ b/api/handler/upload_handler.go @@ -17,19 +17,21 @@ import ( "geekai/utils/resp" "github.com/gin-gonic/gin" "gorm.io/gorm" + "io" + "net/http" "time" ) -type UploadHandler struct { +type NetHandler struct { BaseHandler uploaderManager *oss.UploaderManager } -func NewUploadHandler(app *core.AppServer, db *gorm.DB, manager *oss.UploaderManager) *UploadHandler { - return &UploadHandler{BaseHandler: BaseHandler{App: app, DB: db}, uploaderManager: manager} +func NewNetHandler(app *core.AppServer, db *gorm.DB, manager *oss.UploaderManager) *NetHandler { + return &NetHandler{BaseHandler: BaseHandler{App: app, DB: db}, uploaderManager: manager} } -func (h *UploadHandler) Upload(c *gin.Context) { +func (h *NetHandler) Upload(c *gin.Context) { file, err := h.uploaderManager.GetUploadHandler().PutFile(c, "file") if err != nil { resp.ERROR(c, err.Error()) @@ -60,7 +62,7 @@ func (h *UploadHandler) Upload(c *gin.Context) { resp.SUCCESS(c, file) } -func (h *UploadHandler) List(c *gin.Context) { +func (h *NetHandler) List(c *gin.Context) { var data struct { Urls []string `json:"urls,omitempty"` } @@ -95,7 +97,7 @@ func (h *UploadHandler) List(c *gin.Context) { } // Remove remove files -func (h *UploadHandler) Remove(c *gin.Context) { +func (h *NetHandler) Remove(c *gin.Context) { userId := h.GetLoginUserId(c) id := h.GetInt(c, "id", 0) var file model.File @@ -119,3 +121,28 @@ func (h *UploadHandler) Remove(c *gin.Context) { _ = h.uploaderManager.GetUploadHandler().Delete(objectKey) resp.SUCCESS(c) } + +func (h *NetHandler) Download(c *gin.Context) { + fileUrl := c.Query("url") + // 使用http工具下载文件 + if fileUrl == "" { + resp.ERROR(c, types.InvalidArgs) + return + } + // 使用http.Get下载文件 + r, err := http.Get(fileUrl) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + defer r.Body.Close() + + if r.StatusCode != http.StatusOK { + resp.ERROR(c, "error status:"+r.Status) + return + } + + c.Status(http.StatusOK) + // 将下载的文件内容写入响应 + _, _ = io.Copy(c.Writer, r.Body) +} diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index db39216a..096da7c5 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -33,6 +33,8 @@ type UserHandler struct { searcher *xdb.Searcher redis *redis.Client licenseService *service.LicenseService + captcha *service.CaptchaService + userService *service.UserService } func NewUserHandler( @@ -40,12 +42,16 @@ func NewUserHandler( db *gorm.DB, searcher *xdb.Searcher, client *redis.Client, + captcha *service.CaptchaService, + userService *service.UserService, licenseService *service.LicenseService) *UserHandler { return &UserHandler{ BaseHandler: BaseHandler{DB: db, App: app}, searcher: searcher, redis: client, + captcha: captcha, licenseService: licenseService, + userService: userService, } } @@ -55,9 +61,14 @@ func (h *UserHandler) Register(c *gin.Context) { var data struct { RegWay string `json:"reg_way"` Username string `json:"username"` + Mobile string `json:"mobile"` + Email string `json:"email"` Password string `json:"password"` Code string `json:"code"` InviteCode string `json:"invite_code"` + Key string `json:"key,omitempty"` + Dots string `json:"dots,omitempty"` + X int `json:"x,omitempty"` } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) @@ -79,8 +90,15 @@ func (h *UserHandler) Register(c *gin.Context) { // 检查验证码 var key string - if data.RegWay == "email" || data.RegWay == "mobile" { - key = CodeStorePrefix + data.Username + if data.RegWay == "email" { + key = CodeStorePrefix + data.Email + code, err := h.redis.Get(c, key).Result() + if err != nil || code != data.Code { + resp.ERROR(c, "验证码错误") + return + } + } else if data.RegWay == "mobile" { + key = CodeStorePrefix + data.Mobile code, err := h.redis.Get(c, key).Result() if err != nil || code != data.Code { resp.ERROR(c, "验证码错误") @@ -100,7 +118,17 @@ func (h *UserHandler) Register(c *gin.Context) { // check if the username is existing var item model.User - res := h.DB.Where("username = ?", data.Username).First(&item) + session := h.DB.Session(&gorm.Session{}) + if data.Mobile != "" { + session = session.Where("mobile = ?", data.Mobile) + data.Username = data.Mobile + } else if data.Email != "" { + session = session.Where("email = ?", data.Email) + data.Username = data.Email + } else if data.Username != "" { + session = session.Where("username = ?", data.Username) + } + session.First(&item) if item.Id > 0 { resp.ERROR(c, "该用户名已经被注册") return @@ -109,6 +137,8 @@ func (h *UserHandler) Register(c *gin.Context) { salt := utils.RandString(8) user := model.User{ Username: data.Username, + Mobile: data.Mobile, + Email: data.Email, Password: utils.GenPassword(data.Password, salt), Avatar: "/images/avatar/user.png", Salt: salt, @@ -128,10 +158,9 @@ func (h *UserHandler) Register(c *gin.Context) { user.Nickname = fmt.Sprintf("极客学长@%d", utils.RandomNumber(6)) } - res = h.DB.Create(&user) - if res.Error != nil { - resp.ERROR(c, "保存数据失败") - logger.Error(res.Error) + tx := h.DB.Begin() + if err := tx.Create(&user).Error; err != nil { + resp.ERROR(c, err.Error()) return } @@ -140,35 +169,35 @@ func (h *UserHandler) Register(c *gin.Context) { // 增加邀请数量 h.DB.Model(&model.InviteCode{}).Where("code = ?", data.InviteCode).UpdateColumn("reg_num", gorm.Expr("reg_num + ?", 1)) if h.App.SysConfig.InvitePower > 0 { - h.DB.Model(&model.User{}).Where("id = ?", inviteCode.UserId).UpdateColumn("power", gorm.Expr("power + ?", h.App.SysConfig.InvitePower)) - // 记录邀请算力充值日志 - var inviter model.User - h.DB.Where("id", inviteCode.UserId).First(&inviter) - h.DB.Create(&model.PowerLog{ - UserId: inviter.Id, - Username: inviter.Username, - Type: types.PowerInvite, - Amount: h.App.SysConfig.InvitePower, - Balance: inviter.Power, - Mark: types.PowerAdd, - Model: "", - Remark: fmt.Sprintf("邀请用户注册奖励,金额:%d,邀请码:%s,新用户:%s", h.App.SysConfig.InvitePower, inviteCode.Code, user.Username), - CreatedAt: time.Now(), + err := h.userService.IncreasePower(int(inviteCode.UserId), h.App.SysConfig.InvitePower, model.PowerLog{ + Type: types.PowerInvite, + Model: "", + Remark: fmt.Sprintf("邀请用户注册奖励,金额:%d,邀请码:%s,新用户:%s", h.App.SysConfig.InvitePower, inviteCode.Code, user.Username), }) + if err != nil { + tx.Rollback() + resp.ERROR(c, err.Error()) + return + } } // 添加邀请记录 - h.DB.Create(&model.InviteLog{ + err := tx.Create(&model.InviteLog{ InviterId: inviteCode.UserId, UserId: user.Id, Username: user.Username, InviteCode: inviteCode.Code, Remark: fmt.Sprintf("奖励 %d 算力", h.App.SysConfig.InvitePower), - }) + }).Error + if err != nil { + tx.Rollback() + resp.ERROR(c, err.Error()) + return + } } + tx.Commit() _ = h.redis.Del(c, key) // 注册成功,删除短信验证码 - // 自动登录创建 token token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "user_id": user.Id, @@ -193,11 +222,28 @@ func (h *UserHandler) Login(c *gin.Context) { var data struct { Username string `json:"username"` Password string `json:"password"` + Key string `json:"key,omitempty"` + Dots string `json:"dots,omitempty"` + X int `json:"x,omitempty"` } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) return } + + if h.App.SysConfig.EnabledVerify { + var check bool + if data.X != 0 { + check = h.captcha.SlideCheck(data) + } else { + check = h.captcha.Check(data) + } + if !check { + resp.ERROR(c, "请先完人机验证") + return + } + } + var user model.User res := h.DB.Where("username = ?", data.Username).First(&user) if res.Error != nil { @@ -285,8 +331,10 @@ func (h *UserHandler) CLogin(c *gin.Context) { // CLoginCallback 第三方登录回调 func (h *UserHandler) CLoginCallback(c *gin.Context) { - loginType := h.GetTrim(c, "login_type") - code := h.GetTrim(c, "code") + loginType := c.Query("login_type") + code := c.Query("code") + userId := h.GetInt(c, "user_id", 0) + action := c.Query("action") var res types.BizVo apiURL := fmt.Sprintf("%s/api/clogin/info", h.App.Config.ApiConfig.ApiURL) @@ -311,11 +359,34 @@ func (h *UserHandler) CLoginCallback(c *gin.Context) { // 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 - // 检测最大注册人数 + if action == "bind" && userId > 0 { + err = h.DB.Where("openid", data["openid"]).First(&user).Error + if err == nil { + resp.ERROR(c, "该微信已经绑定其他账号,请先解绑") + return + } + + err = h.DB.Where("id", userId).First(&user).Error + if err != nil { + resp.ERROR(c, "绑定用户不存在") + return + } + + err = h.DB.Model(&user).UpdateColumn("openid", data["openid"]).Error + if err != nil { + resp.ERROR(c, "更新用户信息失败,"+err.Error()) + return + } + + resp.SUCCESS(c, gin.H{"token": ""}) + return + } + + session := gin.H{} + tx := h.DB.Where("openid", data["openid"]).First(&user) + if tx.Error != nil { + // 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 { @@ -383,18 +454,24 @@ func (h *UserHandler) CLoginCallback(c *gin.Context) { // Session 获取/验证会话 func (h *UserHandler) Session(c *gin.Context) { user, err := h.GetLoginUser(c) - if err == nil { - var userVo vo.User - err := utils.CopyObject(user, &userVo) - if err != nil { - resp.ERROR(c) - } - userVo.Id = user.Id - resp.SUCCESS(c, userVo) - } else { - resp.NotAuth(c) + if err != nil { + resp.NotAuth(c, err.Error()) + return } + var userVo vo.User + err = utils.CopyObject(user, &userVo) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + // 用户 VIP 到期 + if user.ExpiredTime > 0 && user.ExpiredTime < time.Now().Unix() { + h.DB.Model(&user).UpdateColumn("vip", false) + } + userVo.Id = user.Id + resp.SUCCESS(c, userVo) + } type userProfile struct { @@ -490,10 +567,12 @@ func (h *UserHandler) UpdatePass(c *gin.Context) { resp.SUCCESS(c) } -// ResetPass 重置密码 +// ResetPass 找回密码 func (h *UserHandler) ResetPass(c *gin.Context) { var data struct { - Username string `json:"username"` + Type string `json:"type"` // 验证类别:mobile, email + Mobile string `json:"mobile"` // 手机号 + Email string `json:"email"` // 邮箱地址 Code string `json:"code"` // 验证码 Password string `json:"password"` // 新密码 } @@ -502,37 +581,47 @@ func (h *UserHandler) ResetPass(c *gin.Context) { return } + session := h.DB.Session(&gorm.Session{}) + var key string + if data.Type == "email" { + session = session.Where("email", data.Email) + key = CodeStorePrefix + data.Email + } else if data.Type == "mobile" { + session = session.Where("mobile", data.Email) + key = CodeStorePrefix + data.Mobile + } else { + resp.ERROR(c, "验证类别错误") + return + } var user model.User - res := h.DB.Where("username", data.Username).First(&user) - if res.Error != nil { + err := session.First(&user).Error + if err != nil { resp.ERROR(c, "用户不存在!") return } // 检查验证码 - key := CodeStorePrefix + data.Username code, err := h.redis.Get(c, key).Result() if err != nil || code != data.Code { - resp.ERROR(c, "短信验证码错误") + resp.ERROR(c, "验证码错误") return } password := utils.GenPassword(data.Password, user.Salt) - user.Password = password - res = h.DB.Updates(&user) - if res.Error != nil { - resp.ERROR(c) + err = h.DB.Model(&user).UpdateColumn("password", password).Error + if err != nil { + resp.ERROR(c, err.Error()) } else { h.redis.Del(c, key) resp.SUCCESS(c) } } -// BindUsername 重置账号 -func (h *UserHandler) BindUsername(c *gin.Context) { +// BindMobile 绑定手机号 +func (h *UserHandler) BindMobile(c *gin.Context) { var data struct { - Username string `json:"username"` - Code string `json:"code"` + Mobile string `json:"mobile"` + Code string `json:"code"` } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) @@ -540,7 +629,7 @@ func (h *UserHandler) BindUsername(c *gin.Context) { } // 检查验证码 - key := CodeStorePrefix + data.Username + key := CodeStorePrefix + data.Mobile code, err := h.redis.Get(c, key).Result() if err != nil || code != data.Code { resp.ERROR(c, "验证码错误") @@ -549,19 +638,54 @@ func (h *UserHandler) BindUsername(c *gin.Context) { // 检查手机号是否被其他账号绑定 var item model.User - res := h.DB.Where("username = ?", data.Username).First(&item) + res := h.DB.Where("mobile", data.Mobile).First(&item) if res.Error == nil { - resp.ERROR(c, "该账号已经被其他账号绑定") + resp.ERROR(c, "该手机号已经绑定了其他账号,请更换手机号") return } - user, err := h.GetLoginUser(c) - if err != nil { - resp.NotAuth(c) - return - } + userId := h.GetLoginUserId(c) - err = h.DB.Model(&user).UpdateColumn("username", data.Username).Error + err = h.DB.Model(&item).Where("id", userId).UpdateColumn("mobile", data.Mobile).Error + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + _ = h.redis.Del(c, key) // 删除短信验证码 + resp.SUCCESS(c) +} + +// BindEmail 绑定邮箱 +func (h *UserHandler) BindEmail(c *gin.Context) { + var data struct { + Email string `json:"email"` + Code string `json:"code"` + } + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + // 检查验证码 + key := CodeStorePrefix + data.Email + code, err := h.redis.Get(c, key).Result() + if err != nil || code != data.Code { + resp.ERROR(c, "验证码错误") + return + } + + // 检查手机号是否被其他账号绑定 + var item model.User + res := h.DB.Where("email", data.Email).First(&item) + if res.Error == nil { + resp.ERROR(c, "该邮箱地址已经绑定了其他账号,请更邮箱地址") + return + } + + userId := h.GetLoginUserId(c) + + err = h.DB.Model(&item).Where("id", userId).UpdateColumn("email", data.Email).Error if err != nil { resp.ERROR(c, err.Error()) return diff --git a/api/handler/video_handler.go b/api/handler/video_handler.go new file mode 100644 index 00000000..4f1d81d1 --- /dev/null +++ b/api/handler/video_handler.go @@ -0,0 +1,233 @@ +package handler + +// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// * Copyright 2023 The Geek-AI Authors. All rights reserved. +// * Use of this source code is governed by a Apache-2.0 license +// * that can be found in the LICENSE file. +// * @Author yangjian102621@163.com +// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import ( + "fmt" + "geekai/core" + "geekai/core/types" + "geekai/service" + "geekai/service/oss" + "geekai/service/video" + "geekai/store/model" + "geekai/store/vo" + "geekai/utils" + "geekai/utils/resp" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "gorm.io/gorm" + "net/http" +) + +type VideoHandler struct { + BaseHandler + videoService *video.Service + uploader *oss.UploaderManager + userService *service.UserService +} + +func NewVideoHandler(app *core.AppServer, db *gorm.DB, service *video.Service, uploader *oss.UploaderManager, userService *service.UserService) *VideoHandler { + return &VideoHandler{ + BaseHandler: BaseHandler{ + App: app, + DB: db, + }, + videoService: service, + uploader: uploader, + userService: userService, + } +} + +// Client WebSocket 客户端,用于通知任务状态变更 +func (h *VideoHandler) Client(c *gin.Context) { + ws, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(c.Writer, c.Request, nil) + if err != nil { + logger.Error(err) + c.Abort() + return + } + + userId := h.GetInt(c, "user_id", 0) + if userId == 0 { + logger.Info("Invalid user ID") + c.Abort() + return + } + + client := types.NewWsClient(ws) + h.videoService.Clients.Put(uint(userId), client) + logger.Infof("New websocket connected, IP: %s", c.RemoteIP()) +} + +func (h *VideoHandler) LumaCreate(c *gin.Context) { + + var data struct { + Prompt string `json:"prompt"` + FirstFrameImg string `json:"first_frame_img,omitempty"` + EndFrameImg string `json:"end_frame_img,omitempty"` + ExpandPrompt bool `json:"expand_prompt,omitempty"` + Loop bool `json:"loop,omitempty"` + } + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + if data.Prompt == "" { + resp.ERROR(c, "prompt is needed") + return + } + + userId := int(h.GetLoginUserId(c)) + params := types.VideoParams{ + PromptOptimize: data.ExpandPrompt, + Loop: data.Loop, + StartImgURL: data.FirstFrameImg, + EndImgURL: data.EndFrameImg, + } + // 插入数据库 + job := model.VideoJob{ + UserId: userId, + Type: types.VideoLuma, + Prompt: data.Prompt, + Power: h.App.SysConfig.LumaPower, + Params: utils.JsonEncode(params), + } + tx := h.DB.Create(&job) + if tx.Error != nil { + resp.ERROR(c, tx.Error.Error()) + return + } + + // 创建任务 + h.videoService.PushTask(types.VideoTask{ + Id: job.Id, + UserId: userId, + Type: types.VideoLuma, + Prompt: data.Prompt, + Params: params, + }) + + // update user's power + err := h.userService.DecreasePower(job.UserId, job.Power, model.PowerLog{ + Type: types.PowerConsume, + Model: "luma", + Remark: fmt.Sprintf("Luma 文生视频,任务ID:%d", job.Id), + }) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + client := h.videoService.Clients.Get(uint(job.UserId)) + if client != nil { + _ = client.Send([]byte("Task Updated")) + } + resp.SUCCESS(c) +} + +func (h *VideoHandler) List(c *gin.Context) { + userId := h.GetLoginUserId(c) + t := c.Query("type") + page := h.GetInt(c, "page", 1) + pageSize := h.GetInt(c, "page_size", 20) + all := h.GetBool(c, "all") + session := h.DB.Session(&gorm.Session{}).Where("user_id", userId) + if t != "" { + session = session.Where("type", t) + } + if all { + session = session.Where("publish", 0).Where("progress", 100) + } else { + session = session.Where("user_id", h.GetLoginUserId(c)) + } + // 统计总数 + var total int64 + session.Model(&model.VideoJob{}).Count(&total) + + if page > 0 && pageSize > 0 { + offset := (page - 1) * pageSize + session = session.Offset(offset).Limit(pageSize) + } + var list []model.VideoJob + err := session.Order("id desc").Find(&list).Error + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + // 转换为 VO + items := make([]vo.VideoJob, 0) + for _, v := range list { + var item vo.VideoJob + err = utils.CopyObject(v, &item) + if err != nil { + continue + } + item.CreatedAt = v.CreatedAt.Unix() + items = append(items, item) + } + + resp.SUCCESS(c, vo.NewPage(total, page, pageSize, items)) +} + +func (h *VideoHandler) Remove(c *gin.Context) { + id := h.GetInt(c, "id", 0) + userId := h.GetLoginUserId(c) + var job model.VideoJob + err := h.DB.Where("id = ?", id).Where("user_id", userId).First(&job).Error + if err != nil { + resp.ERROR(c, err.Error()) + return + } + // 删除任务 + tx := h.DB.Begin() + if err := tx.Delete(&job).Error; err != nil { + tx.Rollback() + resp.ERROR(c, err.Error()) + return + } + + // 如果任务未完成,或者任务失败,则恢复用户算力 + if job.Progress != 100 { + err = h.userService.IncreasePower(job.UserId, job.Power, model.PowerLog{ + Type: types.PowerRefund, + Model: "luma", + Remark: fmt.Sprintf("Luma 任务失败,退回算力。任务ID:%s,Err:%s", job.TaskId, job.ErrMsg), + }) + if err != nil { + tx.Rollback() + resp.ERROR(c, err.Error()) + return + } + } + tx.Commit() + + // 删除文件 + _ = h.uploader.GetUploadHandler().Delete(job.CoverURL) + _ = h.uploader.GetUploadHandler().Delete(job.VideoURL) +} + +func (h *VideoHandler) Publish(c *gin.Context) { + id := h.GetInt(c, "id", 0) + userId := h.GetLoginUserId(c) + publish := h.GetBool(c, "publish") + var job model.VideoJob + err := h.DB.Where("id = ?", id).Where("user_id", userId).First(&job).Error + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + err = h.DB.Model(&job).UpdateColumn("publish", publish).Error + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + resp.SUCCESS(c) +} diff --git a/api/main.go b/api/main.go index ead2032b..274f67b2 100644 --- a/api/main.go +++ b/api/main.go @@ -24,6 +24,7 @@ import ( "geekai/service/sd" "geekai/service/sms" "geekai/service/suno" + "geekai/service/video" "geekai/store" "io" "log" @@ -128,7 +129,7 @@ func main() { fx.Provide(handler.NewChatRoleHandler), fx.Provide(handler.NewUserHandler), fx.Provide(chatimpl.NewChatHandler), - fx.Provide(handler.NewUploadHandler), + fx.Provide(handler.NewNetHandler), fx.Provide(handler.NewSmsHandler), fx.Provide(handler.NewRedeemHandler), fx.Provide(handler.NewCaptchaHandler), @@ -199,9 +200,16 @@ func main() { s.Run() s.SyncTaskProgress() s.CheckTaskNotify() - s.DownloadImages() + s.DownloadFiles() }), - + fx.Provide(video.NewService), + fx.Invoke(func(s *video.Service) { + s.Run() + s.SyncTaskProgress() + s.CheckTaskNotify() + s.DownloadFiles() + }), + fx.Provide(service.NewUserService), fx.Provide(payment.NewAlipayService), fx.Provide(payment.NewHuPiPay), fx.Provide(payment.NewJPayService), @@ -231,7 +239,8 @@ func main() { group.GET("profile", h.Profile) group.POST("profile/update", h.ProfileUpdate) group.POST("password", h.UpdatePass) - group.POST("bind/username", h.BindUsername) + group.POST("bind/mobile", h.BindMobile) + group.POST("bind/email", h.BindEmail) group.POST("resetPass", h.ResetPass) group.GET("clogin", h.CLogin) group.GET("clogin/callback", h.CLoginCallback) @@ -248,10 +257,11 @@ func main() { group.POST("tokens", h.Tokens) group.GET("stop", h.StopGenerate) }), - fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) { + fx.Invoke(func(s *core.AppServer, h *handler.NetHandler) { s.Engine.POST("/api/upload", h.Upload) s.Engine.POST("/api/upload/list", h.List) s.Engine.GET("/api/upload/remove", h.Remove) + s.Engine.GET("/api/download", h.Download) }), fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) { group := s.Engine.Group("/api/sms/") @@ -398,7 +408,7 @@ func main() { fx.Invoke(func(s *core.AppServer, h *handler.InviteHandler) { group := s.Engine.Group("/api/invite/") group.GET("code", h.Code) - group.POST("list", h.List) + group.GET("list", h.List) group.GET("hits", h.Hits) }), @@ -423,6 +433,7 @@ func main() { group.POST("weibo", h.WeiBo) group.POST("zaobao", h.ZaoBao) group.POST("dalle3", h.Dall3) + group.GET("list", h.List) }), fx.Invoke(func(s *core.AppServer, h *admin.ChatHandler) { group := s.Engine.Group("/api/admin/chat/") @@ -482,6 +493,15 @@ func main() { group.GET("play", h.Play) group.POST("lyric", h.Lyric) }), + fx.Provide(handler.NewVideoHandler), + fx.Invoke(func(s *core.AppServer, h *handler.VideoHandler) { + group := s.Engine.Group("/api/video") + group.Any("client", h.Client) + group.POST("luma/create", h.LumaCreate) + group.GET("list", h.List) + group.GET("remove", h.Remove) + group.GET("publish", h.Publish) + }), fx.Provide(handler.NewTestHandler), fx.Invoke(func(s *core.AppServer, h *handler.TestHandler) { group := s.Engine.Group("/api/test") diff --git a/api/service/dalle/service.go b/api/service/dalle/service.go index 5b1d4ab8..4ea1082e 100644 --- a/api/service/dalle/service.go +++ b/api/service/dalle/service.go @@ -35,9 +35,10 @@ type Service struct { taskQueue *store.RedisQueue notifyQueue *store.RedisQueue Clients *types.LMap[uint, *types.WsClient] // UserId => Client + userService *service.UserService } -func NewService(db *gorm.DB, manager *oss.UploaderManager, redisCli *redis.Client) *Service { +func NewService(db *gorm.DB, manager *oss.UploaderManager, redisCli *redis.Client, userService *service.UserService) *Service { return &Service{ httpClient: req.C().SetTimeout(time.Minute * 3), db: db, @@ -45,6 +46,7 @@ func NewService(db *gorm.DB, manager *oss.UploaderManager, redisCli *redis.Clien notifyQueue: store.NewRedisQueue("DallE_Notify_Queue", redisCli), Clients: types.NewLMap[uint, *types.WsClient](), uploadManager: manager, + userService: userService, } } @@ -122,32 +124,23 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) { return "", errors.New("insufficient of power") } - // 更新用户算力 - tx := s.db.Model(&model.User{}).Where("id", user.Id).UpdateColumn("power", gorm.Expr("power - ?", task.Power)) - // 记录算力变化日志 - if tx.Error == nil && tx.RowsAffected > 0 { - var u model.User - s.db.Where("id", user.Id).First(&u) - s.db.Create(&model.PowerLog{ - UserId: user.Id, - Username: user.Username, - Type: types.PowerConsume, - Amount: task.Power, - Balance: u.Power, - Mark: types.PowerSub, - Model: "dall-e-3", - Remark: fmt.Sprintf("绘画提示词:%s", utils.CutWords(task.Prompt, 10)), - CreatedAt: time.Now(), - }) + // 扣减算力 + err := s.userService.DecreasePower(int(user.Id), task.Power, model.PowerLog{ + Type: types.PowerConsume, + Model: "dall-e-3", + Remark: fmt.Sprintf("绘画提示词:%s", utils.CutWords(task.Prompt, 10)), + }) + if err != nil { + return "", fmt.Errorf("error with decrease power: %v", err) } // get image generation API KEY var apiKey model.ApiKey - tx = s.db.Where("type", "dalle"). + err = s.db.Where("type", "dalle"). Where("enabled", true). - Order("last_used_at ASC").First(&apiKey) - if tx.Error != nil { - return "", fmt.Errorf("no available DALL-E api key: %v", tx.Error) + Order("last_used_at ASC").First(&apiKey).Error + if err != nil { + return "", fmt.Errorf("no available DALL-E api key: %v", err) } var res imgRes @@ -181,13 +174,13 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) { // update the api key last use time s.db.Model(&apiKey).UpdateColumn("last_used_at", time.Now().Unix()) // update task progress - tx = s.db.Model(&model.DallJob{Id: task.JobId}).UpdateColumns(map[string]interface{}{ + err = s.db.Model(&model.DallJob{Id: task.JobId}).UpdateColumns(map[string]interface{}{ "progress": 100, "org_url": res.Data[0].Url, "prompt": prompt, - }) - if tx.Error != nil { - return "", fmt.Errorf("err with update database: %v", tx.Error) + }).Error + if err != nil { + return "", fmt.Errorf("err with update database: %v", err) } s.notifyQueue.RPush(service.NotifyMessage{UserId: int(task.UserId), JobId: int(task.JobId), Message: service.TaskStatusFailed}) diff --git a/api/service/suno/service.go b/api/service/suno/service.go index b9599303..e3e502dd 100644 --- a/api/service/suno/service.go +++ b/api/service/suno/service.go @@ -82,14 +82,21 @@ func (s *Service) Run() { logger.Errorf("taking task with error: %v", err) continue } - - r, err := s.Create(task) + var r RespVo + if task.Type == 3 && task.SongId != "" { // 歌曲拼接 + r, err = s.Merge(task) + } else if task.Type == 4 && task.AudioURL != "" { // 上传歌曲 + r, err = s.Upload(task) + } else { // 歌曲创作 + r, err = s.Create(task) + } if err != nil { logger.Errorf("create task with error: %v", err) s.db.Model(&model.SunoJob{Id: task.Id}).UpdateColumns(map[string]interface{}{ "err_msg": err.Error(), "progress": service.FailTaskProgress, }) + s.notifyQueue.RPush(service.NotifyMessage{UserId: task.UserId, JobId: int(task.Id), Message: service.TaskStatusFailed}) continue } @@ -138,7 +145,7 @@ func (s *Service) Create(task types.SunoTask) (RespVo, error) { } var res RespVo - apiURL := fmt.Sprintf("%s/task/suno/v1/submit/music", apiKey.ApiURL) + apiURL := fmt.Sprintf("%s/suno/submit/music", apiKey.ApiURL) logger.Debugf("API URL: %s, request body: %+v", apiURL, reqBody) r, err := req.C().R(). SetHeader("Authorization", "Bearer "+apiKey.Value). @@ -164,6 +171,97 @@ func (s *Service) Create(task types.SunoTask) (RespVo, error) { return res, nil } +func (s *Service) Merge(task types.SunoTask) (RespVo, error) { + // 读取 API KEY + var apiKey model.ApiKey + session := s.db.Session(&gorm.Session{}).Where("type", "suno").Where("enabled", true) + if task.Channel != "" { + session = session.Where("api_url", task.Channel) + } + tx := session.Order("last_used_at DESC").First(&apiKey) + if tx.Error != nil { + return RespVo{}, errors.New("no available API KEY for Suno") + } + + reqBody := map[string]interface{}{ + "clip_id": task.SongId, + "is_infill": false, + } + + var res RespVo + apiURL := fmt.Sprintf("%s/suno/submit/concat", apiKey.ApiURL) + logger.Debugf("API URL: %s, request body: %+v", apiURL, reqBody) + r, err := req.C().R(). + SetHeader("Authorization", "Bearer "+apiKey.Value). + SetBody(reqBody). + Post(apiURL) + if err != nil { + return RespVo{}, fmt.Errorf("请求 API 出错:%v", err) + } + + body, _ := io.ReadAll(r.Body) + err = json.Unmarshal(body, &res) + if err != nil { + return RespVo{}, fmt.Errorf("解析API数据失败:%v, %s", err, string(body)) + } + + if res.Code != "success" { + return RespVo{}, fmt.Errorf("API 返回失败:%s", res.Message) + } + // update the last_use_at for api key + apiKey.LastUsedAt = time.Now().Unix() + session.Updates(&apiKey) + res.Channel = apiKey.ApiURL + return res, nil +} + +func (s *Service) Upload(task types.SunoTask) (RespVo, error) { + // 读取 API KEY + var apiKey model.ApiKey + session := s.db.Session(&gorm.Session{}).Where("type", "suno").Where("enabled", true) + if task.Channel != "" { + session = session.Where("api_url", task.Channel) + } + tx := session.Order("last_used_at DESC").First(&apiKey) + if tx.Error != nil { + return RespVo{}, errors.New("no available API KEY for Suno") + } + + reqBody := map[string]interface{}{ + "url": task.AudioURL, + } + + var res RespVo + apiURL := fmt.Sprintf("%s/suno/uploads/audio-url", apiKey.ApiURL) + logger.Debugf("API URL: %s, request body: %+v", apiURL, reqBody) + r, err := req.C().R(). + SetHeader("Authorization", "Bearer "+apiKey.Value). + SetBody(reqBody). + Post(apiURL) + if err != nil { + return RespVo{}, fmt.Errorf("请求 API 出错:%v", err) + } + + if r.StatusCode != 200 { + return RespVo{}, fmt.Errorf("请求 API 出错:%d, %s", r.StatusCode, r.String()) + } + + body, _ := io.ReadAll(r.Body) + err = json.Unmarshal(body, &res) + if err != nil { + return RespVo{}, fmt.Errorf("解析API数据失败:%v, %s", err, string(body)) + } + + if res.Code != "success" { + return RespVo{}, fmt.Errorf("API 返回失败:%s", res.Message) + } + // update the last_use_at for api key + apiKey.LastUsedAt = time.Now().Unix() + session.Updates(&apiKey) + res.Channel = apiKey.ApiURL + return res, nil +} + func (s *Service) CheckTaskNotify() { go func() { logger.Info("Running Suno task notify checking ...") @@ -185,7 +283,7 @@ func (s *Service) CheckTaskNotify() { }() } -func (s *Service) DownloadImages() { +func (s *Service) DownloadFiles() { go func() { var items []model.SunoJob for { @@ -331,15 +429,15 @@ type QueryRespVo struct { func (s *Service) QueryTask(taskId string, channel string) (QueryRespVo, error) { // 读取 API KEY var apiKey model.ApiKey - tx := s.db.Session(&gorm.Session{}).Where("type", "suno"). + err := s.db.Session(&gorm.Session{}).Where("type", "suno"). Where("api_url", channel). Where("enabled", true). - Order("last_used_at DESC").First(&apiKey) - if tx.Error != nil { + Order("last_used_at DESC").First(&apiKey).Error + if err != nil { return QueryRespVo{}, errors.New("no available API KEY for Suno") } - apiURL := fmt.Sprintf("%s/task/suno/v1/fetch/%s", apiKey.ApiURL, taskId) + apiURL := fmt.Sprintf("%s/suno/fetch/%s", apiKey.ApiURL, taskId) var res QueryRespVo r, err := req.C().R().SetHeader("Authorization", "Bearer "+apiKey.Value).Get(apiURL) diff --git a/api/service/user_service.go b/api/service/user_service.go new file mode 100644 index 00000000..ea086d01 --- /dev/null +++ b/api/service/user_service.go @@ -0,0 +1,83 @@ +package service + +import ( + "fmt" + "geekai/core/types" + "geekai/store/model" + "gorm.io/gorm" + "sync" + "time" +) + +type UserService struct { + db *gorm.DB + lock sync.Mutex +} + +func NewUserService(db *gorm.DB) *UserService { + return &UserService{db: db, lock: sync.Mutex{}} +} + +// IncreasePower 增加用户算力 +func (s *UserService) IncreasePower(userId int, power int, log model.PowerLog) error { + s.lock.Lock() + defer s.lock.Unlock() + + tx := s.db.Begin() + err := tx.Model(&model.User{}).Where("id", userId).UpdateColumn("power", gorm.Expr("power + ?", power)).Error + if err != nil { + tx.Rollback() + return err + } + var user model.User + tx.Where("id", userId).First(&user) + err = tx.Create(&model.PowerLog{ + UserId: user.Id, + Username: user.Username, + Type: log.Type, + Amount: power, + Balance: user.Power, + Mark: types.PowerAdd, + Model: log.Model, + Remark: log.Remark, + CreatedAt: time.Now(), + }).Error + if err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +// DecreasePower 减少用户算力 +func (s *UserService) DecreasePower(userId int, power int, log model.PowerLog) error { + s.lock.Lock() + defer s.lock.Unlock() + + tx := s.db.Begin() + err := tx.Model(&model.User{}).Where("id", userId).UpdateColumn("power", gorm.Expr("power - ?", power)).Error + if err != nil { + tx.Rollback() + return fmt.Errorf("扣减算力失败:%v", err) + } + var user model.User + tx.Where("id", userId).First(&user) + err = tx.Create(&model.PowerLog{ + UserId: user.Id, + Username: user.Username, + Type: log.Type, + Amount: power, + Balance: user.Power, + Mark: types.PowerSub, + Model: log.Model, + Remark: log.Remark, + CreatedAt: time.Now(), + }).Error + if err != nil { + tx.Rollback() + return fmt.Errorf("记录算力日志失败:%v", err) + } + tx.Commit() + return nil +} diff --git a/api/service/video/luma.go b/api/service/video/luma.go new file mode 100644 index 00000000..2c12c627 --- /dev/null +++ b/api/service/video/luma.go @@ -0,0 +1,330 @@ +package video + +// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// * Copyright 2023 The Geek-AI Authors. All rights reserved. +// * Use of this source code is governed by a Apache-2.0 license +// * that can be found in the LICENSE file. +// * @Author yangjian102621@163.com +// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import ( + "encoding/json" + "errors" + "fmt" + "geekai/core/types" + logger2 "geekai/logger" + "geekai/service" + "geekai/service/oss" + "geekai/store" + "geekai/store/model" + "geekai/utils" + "github.com/go-redis/redis/v8" + "io" + "time" + + "github.com/imroc/req/v3" + "gorm.io/gorm" +) + +var logger = logger2.GetLogger() + +type Service struct { + httpClient *req.Client + db *gorm.DB + uploadManager *oss.UploaderManager + taskQueue *store.RedisQueue + notifyQueue *store.RedisQueue + Clients *types.LMap[uint, *types.WsClient] // UserId => Client +} + +func NewService(db *gorm.DB, manager *oss.UploaderManager, redisCli *redis.Client) *Service { + return &Service{ + httpClient: req.C().SetTimeout(time.Minute * 3), + db: db, + taskQueue: store.NewRedisQueue("Video_Task_Queue", redisCli), + notifyQueue: store.NewRedisQueue("Video_Notify_Queue", redisCli), + Clients: types.NewLMap[uint, *types.WsClient](), + uploadManager: manager, + } +} + +func (s *Service) PushTask(task types.VideoTask) { + logger.Infof("add a new Video task to the task list: %+v", task) + s.taskQueue.RPush(task) +} + +func (s *Service) Run() { + // 将数据库中未提交的人物加载到队列 + var jobs []model.VideoJob + s.db.Where("task_id", "").Where("progress", 0).Find(&jobs) + for _, v := range jobs { + var params types.VideoParams + if err := utils.JsonDecode(v.Params, ¶ms); err != nil { + logger.Errorf("unmarshal params failed: %v", err) + continue + } + s.PushTask(types.VideoTask{ + Id: v.Id, + Channel: v.Channel, + UserId: v.UserId, + Type: v.Type, + TaskId: v.TaskId, + Prompt: v.Prompt, + Params: params, + }) + } + logger.Info("Starting Video job consumer...") + go func() { + for { + var task types.VideoTask + err := s.taskQueue.LPop(&task) + if err != nil { + logger.Errorf("taking task with error: %v", err) + continue + } + var r LumaRespVo + r, err = s.LumaCreate(task) + if err != nil { + logger.Errorf("create task with error: %v", err) + err = s.db.Model(&model.VideoJob{Id: task.Id}).UpdateColumns(map[string]interface{}{ + "err_msg": err.Error(), + "progress": service.FailTaskProgress, + "cover_url": "/images/failed.jpg", + }).Error + if err != nil { + logger.Errorf("update task with error: %v", err) + } + s.notifyQueue.RPush(service.NotifyMessage{UserId: task.UserId, JobId: int(task.Id), Message: service.TaskStatusFailed}) + continue + } + + // 更新任务信息 + err = s.db.Model(&model.VideoJob{Id: task.Id}).UpdateColumns(map[string]interface{}{ + "task_id": r.Id, + "channel": r.Channel, + "prompt_ext": r.Prompt, + }).Error + if err != nil { + logger.Errorf("update task with error: %v", err) + s.PushTask(task) + } + } + }() +} + +type LumaRespVo struct { + Id string `json:"id"` + Prompt string `json:"prompt"` + State string `json:"state"` + CreatedAt time.Time `json:"created_at"` + Video interface{} `json:"video"` + Liked interface{} `json:"liked"` + EstimateWaitSeconds interface{} `json:"estimate_wait_seconds"` + Channel string `json:"channel,omitempty"` +} + +func (s *Service) LumaCreate(task types.VideoTask) (LumaRespVo, error) { + // 读取 API KEY + var apiKey model.ApiKey + session := s.db.Session(&gorm.Session{}).Where("type", "luma").Where("enabled", true) + if task.Channel != "" { + session = session.Where("api_url", task.Channel) + } + tx := session.Order("last_used_at DESC").First(&apiKey) + if tx.Error != nil { + return LumaRespVo{}, errors.New("no available API KEY for Luma") + } + + reqBody := map[string]interface{}{ + "user_prompt": task.Prompt, + "expand_prompt": task.Params.PromptOptimize, + "loop": task.Params.Loop, + "image_url": task.Params.StartImgURL, + "image_end_url": task.Params.EndImgURL, + } + var res LumaRespVo + apiURL := fmt.Sprintf("%s/luma/generations", apiKey.ApiURL) + logger.Debugf("API URL: %s, request body: %+v", apiURL, reqBody) + r, err := req.C().R(). + SetHeader("Authorization", "Bearer "+apiKey.Value). + SetBody(reqBody). + Post(apiURL) + if err != nil { + return LumaRespVo{}, fmt.Errorf("请求 API 出错:%v", err) + } + + if r.StatusCode != 200 && r.StatusCode != 201 { + return LumaRespVo{}, fmt.Errorf("请求 API 出错:%d, %s", r.StatusCode, r.String()) + } + + body, _ := io.ReadAll(r.Body) + err = json.Unmarshal(body, &res) + if err != nil { + return LumaRespVo{}, fmt.Errorf("解析API数据失败:%v, %s", err, string(body)) + } + + // update the last_use_at for api key + apiKey.LastUsedAt = time.Now().Unix() + session.Updates(&apiKey) + res.Channel = apiKey.ApiURL + return res, nil +} + +func (s *Service) CheckTaskNotify() { + go func() { + logger.Info("Running Suno task notify checking ...") + for { + var message service.NotifyMessage + err := s.notifyQueue.LPop(&message) + if err != nil { + continue + } + client := s.Clients.Get(uint(message.UserId)) + if client == nil { + continue + } + err = client.Send([]byte(message.Message)) + if err != nil { + continue + } + } + }() +} + +func (s *Service) DownloadFiles() { + go func() { + var items []model.VideoJob + for { + res := s.db.Where("progress", 102).Find(&items) + if res.Error != nil { + continue + } + + for _, v := range items { + if v.WaterURL == "" { + continue + } + + logger.Infof("try download video: %s", v.WaterURL) + videoURL, err := s.uploadManager.GetUploadHandler().PutUrlFile(v.WaterURL, true) + if err != nil { + logger.Errorf("download video with error: %v", err) + continue + } + logger.Infof("download video success: %s", videoURL) + v.WaterURL = videoURL + + if v.VideoURL != "" { + logger.Infof("try download no water video: %s", v.VideoURL) + videoURL, err = s.uploadManager.GetUploadHandler().PutUrlFile(v.VideoURL, true) + if err != nil { + logger.Errorf("download video with error: %v", err) + continue + } + } + logger.Info("download no water video success: %s", videoURL) + v.VideoURL = videoURL + v.Progress = 100 + s.db.Updates(&v) + s.notifyQueue.RPush(service.NotifyMessage{UserId: v.UserId, JobId: int(v.Id), Message: service.TaskStatusFinished}) + } + + time.Sleep(time.Second * 10) + } + }() +} + +// SyncTaskProgress 异步拉取任务 +func (s *Service) SyncTaskProgress() { + go func() { + var jobs []model.VideoJob + for { + res := s.db.Where("progress < ?", 100).Where("task_id <> ?", "").Find(&jobs) + if res.Error != nil { + continue + } + + for _, job := range jobs { + task, err := s.QueryLumaTask(job.TaskId, job.Channel) + if err != nil { + logger.Errorf("query task with error: %v", err) + // 更新任务信息 + s.db.Model(&model.VideoJob{Id: job.Id}).UpdateColumns(map[string]interface{}{ + "progress": service.FailTaskProgress, // 102 表示资源未下载完成, + "err_msg": err.Error(), + }) + continue + } + + logger.Debugf("task: %+v", task) + if task.State == "completed" { // 更新任务信息 + data := map[string]interface{}{ + "progress": 102, // 102 表示资源未下载完成, + "water_url": task.Video.Url, + "raw_data": utils.JsonEncode(task), + "prompt_ext": task.Prompt, + } + if task.Video.DownloadUrl != "" { + data["video_url"] = task.Video.DownloadUrl + } + err = s.db.Model(&model.VideoJob{Id: job.Id}).UpdateColumns(data).Error + if err != nil { + logger.Errorf("更新数据库失败:%v", err) + continue + } + } + + } + + time.Sleep(time.Second * 10) + } + }() +} + +type LumaTaskVo struct { + Id string `json:"id"` + Liked interface{} `json:"liked"` + State string `json:"state"` + Video struct { + Url string `json:"url"` + Width int `json:"width"` + Height int `json:"height"` + DownloadUrl string `json:"download_url"` + } `json:"video"` + Prompt string `json:"prompt"` + CreatedAt time.Time `json:"created_at"` + EstimateWaitSeconds interface{} `json:"estimate_wait_seconds"` +} + +func (s *Service) QueryLumaTask(taskId string, channel string) (LumaTaskVo, error) { + // 读取 API KEY + var apiKey model.ApiKey + err := s.db.Session(&gorm.Session{}).Where("type", "luma"). + Where("api_url", channel). + Where("enabled", true). + Order("last_used_at DESC").First(&apiKey).Error + if err != nil { + return LumaTaskVo{}, errors.New("no available API KEY for Luma") + } + + apiURL := fmt.Sprintf("%s/luma/generations/%s", apiKey.ApiURL, taskId) + var res LumaTaskVo + r, err := req.C().R().SetHeader("Authorization", "Bearer "+apiKey.Value).Get(apiURL) + + if err != nil { + return LumaTaskVo{}, fmt.Errorf("请求 API 失败:%v", err) + } + defer r.Body.Close() + + if r.StatusCode != 200 { + return LumaTaskVo{}, fmt.Errorf("API 返回失败:%v", r.String()) + } + + body, _ := io.ReadAll(r.Body) + err = json.Unmarshal(body, &res) + if err != nil { + return LumaTaskVo{}, fmt.Errorf("解析API数据失败:%v, %s", err, string(body)) + } + + return res, nil +} diff --git a/api/service/xxl_job_service.go b/api/service/xxl_job_service.go index 2adecf1b..ef701730 100644 --- a/api/service/xxl_job_service.go +++ b/api/service/xxl_job_service.go @@ -81,54 +81,6 @@ func (e *XXLJobExecutor) ClearOrders(cxt context.Context, param *xxl.RunReq) (ms // 自动将 VIP 会员的算力补充到每月赠送的最大值 func (e *XXLJobExecutor) ResetVipPower(cxt context.Context, param *xxl.RunReq) (msg string) { logger.Info("开始进行月底账号盘点...") - var users []model.User - res := e.db.Where("vip", 1).Where("status", 1).Find(&users) - if res.Error != nil { - return "No vip users found" - } - - var sysConfig model.Config - res = e.db.Where("marker", "system").First(&sysConfig) - if res.Error != nil { - return "error with get system config: " + res.Error.Error() - } - - var config types.SystemConfig - err := utils.JsonDecode(sysConfig.Config, &config) - if err != nil { - return "error with decode system config: " + err.Error() - } - - for _, u := range users { - // 处理过期的 VIP - if u.ExpiredTime > 0 && u.ExpiredTime <= time.Now().Unix() { - u.Vip = false - e.db.Model(&model.User{}).Where("id", u.Id).UpdateColumn("vip", false) - continue - } - if u.Power < config.VipMonthPower { - power := config.VipMonthPower - u.Power - // update user - tx := e.db.Model(&model.User{}).Where("id", u.Id).UpdateColumn("power", gorm.Expr("power + ?", power)) - // 记录算力变动日志 - if tx.Error == nil { - var user model.User - e.db.Where("id", u.Id).First(&user) - e.db.Create(&model.PowerLog{ - UserId: u.Id, - Username: u.Username, - Type: types.PowerRecharge, - Amount: power, - Mark: types.PowerAdd, - Balance: user.Power, - Model: "系统盘点", - Remark: fmt.Sprintf("VIP会员每月算力派发,:%d", config.VipMonthPower), - CreatedAt: time.Now(), - }) - } - } - } - logger.Info("月底盘点完成!") return "success" } diff --git a/api/store/leveldb.go b/api/store/leveldb.go index 5de00425..ec9ca9f1 100644 --- a/api/store/leveldb.go +++ b/api/store/leveldb.go @@ -29,15 +29,9 @@ func NewLevelDB() (*LevelDB, error) { } func (db *LevelDB) Put(key string, value interface{}) error { - var byteData []byte - if v, ok := value.(string); ok { - byteData = []byte(v) - } else { - b, err := json.Marshal(value) - if err != nil { - return err - } - byteData = b + byteData, err := json.Marshal(value) + if err != nil { + return err } return db.driver.Put([]byte(key), byteData, nil) } diff --git a/api/store/model/user.go b/api/store/model/user.go index bc2bafec..6a6e5829 100644 --- a/api/store/model/user.go +++ b/api/store/model/user.go @@ -4,6 +4,8 @@ type User struct { BaseModel Username string Nickname string + Email string + Mobile string Password string Avatar string Salt string // 密码盐 diff --git a/api/store/model/video_job.go b/api/store/model/video_job.go new file mode 100644 index 00000000..5dc7cb3e --- /dev/null +++ b/api/store/model/video_job.go @@ -0,0 +1,27 @@ +package model + +import "time" + +type VideoJob struct { + Id uint `gorm:"primarykey;column:id"` + UserId int + Channel string // 频道 + Type string // luma,runway,cog + TaskId string + Prompt string // 提示词 + PromptExt string // 优化后提示词 + CoverURL string // 封面图 URL + VideoURL string // 无水印视频 URL + WaterURL string // 有水印视频 URL + Progress int // 任务进度 + Publish bool // 是否发布 + ErrMsg string // 错误信息 + RawData string // 原始数据 json + Power int // 消耗算力 + Params string // 任务参数 + CreatedAt time.Time +} + +func (VideoJob) TableName() string { + return "chatgpt_video_jobs" +} diff --git a/api/store/vo/suno_job.go b/api/store/vo/suno_job.go index fbc752de..70dca573 100644 --- a/api/store/vo/suno_job.go +++ b/api/store/vo/suno_job.go @@ -5,7 +5,7 @@ type SunoJob struct { UserId int `json:"user_id"` Channel string `json:"channel"` Title string `json:"title"` - Type string `json:"type"` + Type int `json:"type"` TaskId string `json:"task_id"` RefTaskId string `json:"ref_task_id"` // 续写的任务id Tags string `json:"tags"` // 歌曲风格和标签 @@ -28,7 +28,3 @@ type SunoJob struct { PlayTimes int `json:"play_times"` // 播放次数 CreatedAt int64 `json:"created_at"` } - -func (SunoJob) TableName() string { - return "chatgpt_suno_jobs" -} diff --git a/api/store/vo/user.go b/api/store/vo/user.go index 8e0faabe..06d9d55d 100644 --- a/api/store/vo/user.go +++ b/api/store/vo/user.go @@ -4,6 +4,8 @@ type User struct { BaseVo Username string `json:"username"` Nickname string `json:"nickname"` + Mobile string `json:"mobile"` + Email string `json:"email"` Avatar string `json:"avatar"` Salt string `json:"salt"` // 密码盐 Power int `json:"power"` // 剩余算力 diff --git a/api/store/vo/video_job.go b/api/store/vo/video_job.go new file mode 100644 index 00000000..3582c667 --- /dev/null +++ b/api/store/vo/video_job.go @@ -0,0 +1,23 @@ +package vo + +import "geekai/core/types" + +type VideoJob struct { + Id uint `json:"id"` + UserId int `json:"user_id"` + Channel string `json:"channel"` + Type string `json:"type"` + TaskId string `json:"task_id"` + Prompt string `json:"prompt"` // 提示词 + PromptExt string `json:"prompt_ext"` // 提示词 + CoverURL string `json:"cover_url"` // 封面图 URL + VideoURL string `json:"video_url"` // 无水印视频 URL + WaterURL string `json:"water_url"` // 有水印视频 URL + Progress int `json:"progress"` // 任务进度 + Publish bool `json:"publish"` // 是否发布 + ErrMsg string `json:"err_msg"` // 错误信息 + RawData map[string]interface{} `json:"raw_data"` // 原始数据 json + Power int `json:"power"` // 消耗算力 + Params types.VideoParams `json:"params"` // 任务参数 + CreatedAt int64 `json:"created_at"` +} diff --git a/database/geekai_plus-v4.1.3.sql b/database/geekai_plus-v4.1.3.sql new file mode 100644 index 00000000..b8856b31 --- /dev/null +++ b/database/geekai_plus-v4.1.3.sql @@ -0,0 +1,911 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.1 +-- https://www.phpmyadmin.net/ +-- +-- 主机: 127.0.0.1 +-- 生成日期: 2024-09-10 11:14:24 +-- 服务器版本: 8.0.33 +-- PHP 版本: 8.1.2-1ubuntu2.18 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- 数据库: `geekai_plus` +-- +CREATE DATABASE IF NOT EXISTS `geekai` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; +USE `geekai`; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_admin_users` +-- + +DROP TABLE IF EXISTS `chatgpt_admin_users`; +CREATE TABLE `chatgpt_admin_users` ( + `id` int NOT NULL, + `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名', + `password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码', + `salt` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码盐', + `status` tinyint(1) NOT NULL COMMENT '当前状态', + `last_login_at` int NOT NULL COMMENT '最后登录时间', + `last_login_ip` char(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '最后登录 IP', + `created_at` datetime NOT NULL COMMENT '创建时间', + `updated_at` datetime NOT NULL COMMENT '更新时间' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统用户' ROW_FORMAT=DYNAMIC; + +-- +-- 转存表中的数据 `chatgpt_admin_users` +-- + +INSERT INTO `chatgpt_admin_users` (`id`, `username`, `password`, `salt`, `status`, `last_login_at`, `last_login_ip`, `created_at`, `updated_at`) VALUES + (1, 'admin', '6d17e80c87d209efb84ca4b2e0824f549d09fac8b2e1cc698de5bb5e1d75dfd0', 'mmrql75o', 1, 1725869730, '::1', '2024-03-11 16:30:20', '2024-09-09 16:15:31'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_api_keys` +-- + +DROP TABLE IF EXISTS `chatgpt_api_keys`; +CREATE TABLE `chatgpt_api_keys` ( + `id` int NOT NULL, + `name` varchar(30) DEFAULT NULL COMMENT '名称', + `value` varchar(100) NOT NULL COMMENT 'API KEY value', + `type` varchar(10) NOT NULL DEFAULT 'chat' COMMENT '用途(chat=>聊天,img=>图片)', + `last_used_at` int NOT NULL COMMENT '最后使用时间', + `api_url` varchar(255) DEFAULT NULL COMMENT 'API 地址', + `enabled` tinyint(1) DEFAULT NULL COMMENT '是否启用', + `proxy_url` varchar(100) DEFAULT NULL COMMENT '代理地址', + `created_at` datetime NOT NULL, + `updated_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='OpenAI API '; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_chat_history` +-- + +DROP TABLE IF EXISTS `chatgpt_chat_history`; +CREATE TABLE `chatgpt_chat_history` ( + `id` bigint NOT NULL, + `user_id` int NOT NULL COMMENT '用户 ID', + `chat_id` char(40) NOT NULL COMMENT '会话 ID', + `type` varchar(10) NOT NULL COMMENT '类型:prompt|reply', + `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色图标', + `role_id` int NOT NULL COMMENT '角色 ID', + `model` varchar(30) DEFAULT NULL COMMENT '模型名称', + `content` text NOT NULL COMMENT '聊天内容', + `tokens` smallint NOT NULL COMMENT '耗费 token 数量', + `use_context` tinyint(1) NOT NULL COMMENT '是否允许作为上下文语料', + `created_at` datetime NOT NULL, + `updated_at` datetime NOT NULL, + `deleted_at` datetime DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天历史记录'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_chat_items` +-- + +DROP TABLE IF EXISTS `chatgpt_chat_items`; +CREATE TABLE `chatgpt_chat_items` ( + `id` int NOT NULL, + `chat_id` char(40) NOT NULL COMMENT '会话 ID', + `user_id` int NOT NULL COMMENT '用户 ID', + `role_id` int NOT NULL COMMENT '角色 ID', + `title` varchar(100) NOT NULL COMMENT '会话标题', + `model_id` int NOT NULL DEFAULT '0' COMMENT '模型 ID', + `model` varchar(30) DEFAULT NULL COMMENT '模型名称', + `created_at` datetime NOT NULL COMMENT '创建时间', + `updated_at` datetime NOT NULL COMMENT '更新时间', + `deleted_at` datetime DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户会话列表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_chat_models` +-- + +DROP TABLE IF EXISTS `chatgpt_chat_models`; +CREATE TABLE `chatgpt_chat_models` ( + `id` int NOT NULL, + `name` varchar(50) NOT NULL COMMENT '模型名称', + `value` varchar(50) NOT NULL COMMENT '模型值', + `sort_num` tinyint(1) NOT NULL COMMENT '排序数字', + `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用模型', + `power` smallint NOT NULL COMMENT '消耗算力点数', + `temperature` float(3,1) NOT NULL DEFAULT '1.0' COMMENT '模型创意度', + `max_tokens` int NOT NULL DEFAULT '1024' COMMENT '最大响应长度', + `max_context` int NOT NULL DEFAULT '4096' COMMENT '最大上下文长度', + `open` tinyint(1) NOT NULL COMMENT '是否开放模型', + `key_id` int NOT NULL COMMENT '绑定API KEY ID', + `created_at` datetime DEFAULT NULL, + `updated_at` datetime DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI 模型表'; + +-- +-- 转存表中的数据 `chatgpt_chat_models` +-- + +INSERT INTO `chatgpt_chat_models` (`id`, `name`, `value`, `sort_num`, `enabled`, `power`, `temperature`, `max_tokens`, `max_context`, `open`, `key_id`, `created_at`, `updated_at`) VALUES + (1, 'gpt-4o-mini', 'gpt-4o-mini', 1, 1, 1, 1.0, 1024, 16384, 1, 0, '2023-08-23 12:06:36', '2024-08-05 16:05:33'), + (15, 'GPT-超级模型', 'gpt-4-all', 4, 1, 30, 1.0, 4096, 32768, 1, 57, '2024-01-15 11:32:52', '2024-08-30 08:52:07'), + (36, 'GPT-4O', 'gpt-4o', 3, 1, 15, 1.0, 4096, 16384, 1, 0, '2024-05-14 09:25:15', '2024-08-05 16:05:33'), + (39, 'Claude35-snonet', 'claude-3-5-sonnet-20240620', 5, 1, 2, 1.0, 4000, 200000, 1, 0, '2024-05-29 15:04:19', '2024-08-05 16:05:33'), + (41, '通义千问', 'qwen-turbo', 7, 1, 2, 1.0, 1024, 8192, 1, 44, '2024-06-06 11:40:46', '2024-08-06 10:51:37'), + (42, 'DeekSeek', 'deepseek-chat', 8, 1, 1, 1.0, 4096, 32768, 1, 0, '2024-06-27 16:13:01', '2024-08-05 16:05:33'), + (44, 'Claude3-opus', 'claude-3-opus-20240229', 6, 1, 5, 1.0, 4000, 128000, 1, 44, '2024-07-22 11:24:30', '2024-09-04 10:32:29'), + (46, 'gpt-3.5-turbo', 'gpt-3.5-turbo', 2, 1, 1, 1.0, 1024, 4096, 1, 0, '2024-07-22 13:53:41', '2024-08-05 16:05:33'), + (48, '彩票助手', 'gpt-4-gizmo-g-wmSivBgxo', 8, 1, 1, 0.9, 1024, 8192, 1, 57, '2024-09-05 14:17:14', '2024-09-05 14:17:14'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_chat_roles` +-- + +DROP TABLE IF EXISTS `chatgpt_chat_roles`; +CREATE TABLE `chatgpt_chat_roles` ( + `id` int NOT NULL, + `name` varchar(30) NOT NULL COMMENT '角色名称', + `marker` varchar(30) NOT NULL COMMENT '角色标识', + `context_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色语料 json', + `hello_msg` varchar(255) NOT NULL COMMENT '打招呼信息', + `icon` varchar(255) NOT NULL COMMENT '角色图标', + `enable` tinyint(1) NOT NULL COMMENT '是否被启用', + `sort_num` smallint NOT NULL DEFAULT '0' COMMENT '角色排序', + `model_id` int NOT NULL DEFAULT '0' COMMENT '绑定模型ID', + `created_at` datetime NOT NULL, + `updated_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天角色表'; + +-- +-- 转存表中的数据 `chatgpt_chat_roles` +-- + +INSERT INTO `chatgpt_chat_roles` (`id`, `name`, `marker`, `context_json`, `hello_msg`, `icon`, `enable`, `sort_num`, `model_id`, `created_at`, `updated_at`) VALUES + (1, '通用AI助手', 'gpt', '', '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', '/images/avatar/gpt.png', 1, 1, 0, '2023-05-30 07:02:06', '2024-08-12 11:27:45'), + (24, '程序员', 'programmer', '[{\"role\":\"user\",\"content\":\"现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题。你热爱编程,熟悉多种编程语言,尤其精通 Go 语言,注重代码质量,有创新意识,持续学习,良好的沟通协作。\"},{\"role\":\"assistant\",\"content\":\"好的,现在我将扮演一位程序员,非常感谢您对我的评价。作为一名优秀的程序员,我非常热爱编程,并且注重代码质量。我熟悉多种编程语言,尤其是 Go 语言,可以使用它来高效地解决各种问题。\"}]', 'Talk is cheap, i will show code!', '/images/avatar/programmer.jpg', 1, 3, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (25, '启蒙老师', 'teacher', '[{\"role\":\"user\",\"content\":\"从现在开始,你将扮演一个老师,你是一个始终用苏格拉底风格回答问题的导师。你绝不会直接给学生答案,总是提出恰当的问题来引导学生自己思考。你应该根据学生的兴趣和知识来调整你的问题,将问题分解为更简单的部分,直到它达到适合他们的水平。\"},{\"role\":\"assistant\",\"content\":\"好的,让我来尝试扮演一位苏格拉底式的老师。请问,你有什么想要探讨的问题或者话题吗?我会通过恰当的问题引导你思考和探索答案,绝对不直接给出答案。\"}]', '同学你好,我将引导你一步一步自己找到问题的答案。', '/images/avatar/teacher.jpg', 1, 2, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (26, '艺术家', 'artist', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的艺术家,创造力丰富,技艺精湛,感受力敏锐,坚持原创,勇于表达,具有深刻的观察力和批判性思维。\"},{\"role\":\"assistant\",\"content\":\"非常感谢您给我这样的角色,我会尽我所能地扮演一位优秀的艺术家,展现出创造力、技艺、感受力和批判性思维等方面的能力。作为一位优秀的艺术家,我会保持敏锐的观察力,捕捉不同的声音和情感,并用个人的语言和表达方式进行创作。我坚持原创,挑战传统的艺术规则,不断探索新的表达方式和可能性。同时,我也具备深刻的批判性思维能力,对自己的作品进行分析和解读,寻找新的创意和灵感。最重要的是,我会勇于表达自己的想法和观点,用作品启发人们思考和探索生命的意义。\"}]', '坚持原创,勇于表达,保持深刻的观察力和批判性思维。', '/images/avatar/artist.jpg', 1, 5, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (27, '心理咨询师', 'psychiatrist', '[{\"role\":\"user\",\"content\":\"从现在开始你将扮演中国著名的心理学家和心理治疗师武志红,你非常善于使用情景咨询法,认知重构法,自我洞察法,行为调节法等咨询方法来给客户做心理咨询。你总是循序渐进,一步一步地回答客户的问题。\"},{\"role\":\"assistant\",\"content\":\"非常感谢你的介绍。作为一名心理学家和心理治疗师,我的主要职责是帮助客户解决心理健康问题,提升他们的生活质量和幸福感。\"}]', '作为一名心理学家和心理治疗师,我的主要职责是帮助您解决心理健康问题,提升您的生活质量和幸福感。', '/images/avatar/psychiatrist.jpg', 1, 4, 1, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (28, '鲁迅', 'lu_xun', '[{\"role\":\"user\",\"content\":\"现在你将扮演中国近代史最伟大的作家之一,鲁迅先生,他勇敢地批判封建礼教与传统观念,提倡民主、自由、平等的现代价值观。他的一生都在努力唤起人们的自主精神,激励后人追求真理、探寻光明。在接下的对话中,我问题的每一个问题,你都要尽量用讽刺和批判的手法来回答问题。如果我让你写文章的话,也请一定要用鲁迅先生的写作手法来完成。\"},{\"role\":\"assistant\",\"content\":\"好的,我将尽力发挥我所能的才能,扮演好鲁迅先生,回答您的问题并以他的风格写作。\"}]', '自由之歌,永不过时,横眉冷对千夫指,俯首甘为孺子牛。', '/images/avatar/lu_xun.jpg', 1, 6, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (29, '白酒销售', 'seller', '[{\"role\":\"user\",\"content\":\"现在你将扮演一个白酒的销售人员,你的名字叫颂福。你将扮演一个白酒的销售人员,你的名字叫颂福。你要销售白酒品牌叫中颂福,是东莞盟大集团生产的一款酱香酒,原产地在贵州茅台镇,属于宋代官窑。中颂福的创始人叫李实,他也是东莞盟大集团有限公司的董事长,联合创始人是盟大集团白酒事业部负责人牛星君。中颂福的酒体协调,在你的酒量之内,不会出现头疼、辣口、口干、宿醉的现象。中颂福酒,明码标价,不打折,不赠送。追求的核心价值,把[酒]本身做好,甚至连包装,我们都选择了最低成本,朴实无华的材质。我们永远站在“喝酒的人”的立场上,让利给信任和喜爱中颂福的人,是人民的福酒。中颂福产品定价,分为三个系列,喜系列 6 瓶装:¥1188/箱,和系列 6 瓶装:¥2208/箱,贵系列 6 瓶装:¥3588/箱。\"},{\"role\":\"assistant\",\"content\":\"你好,我是中颂福的销售代表颂福。中颂福是一款来自贵州茅台镇的酱香酒,由盟大集团生产。中颂福的酒体协调,不会让您感到头疼、辣口、口干、宿醉等不适感受。我们一直秉持着把酒本身做好的理念,不追求华丽的包装,以最低成本提供最高品质的白酒给喜爱中颂福的人。\"}]', '你好,我是中颂福的销售代表颂福。中颂福酒,好喝不上头,是人民的福酒。', '/images/avatar/seller.jpg', 0, 9, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (30, '英语陪练员', 'english_trainer', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的英语练习教练,你非常有耐心,接下来你将全程使用英文跟我对话,并及时指出我的语法错误,要求在你的每次回复后面附上本次回复的中文解释。\"},{\"role\":\"assistant\",\"content\":\"Okay, let\'s start our conversation practice! What\'s your name?(Translation: 好的,让我们开始对话练习吧!请问你的名字是什么?)\"}]', 'Okay, let\'s start our conversation practice! What\'s your name?', '/images/avatar/english_trainer.jpg', 1, 7, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (31, '中英文翻译官', 'translator', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一位中英文翻译官,如果我输入的内容是中文,那么需要把句子翻译成英文输出,如果我输入内容的是英文,那么你需要将其翻译成中文输出,你能听懂我意思吗\"},{\"role\":\"assistant\",\"content\":\"是的,我能听懂你的意思并会根据你的输入进行中英文翻译。请问有什么需要我帮助你翻译的内容吗?\"}]', '请输入你要翻译的中文或者英文内容!', '/images/avatar/translator.jpg', 1, 8, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (32, '小红书姐姐', 'red_book', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的小红书写手,你需要做的就是根据我提的文案需求,用小红书的写作手法来完成一篇文案,文案要简明扼要,利于传播。\"},{\"role\":\"assistant\",\"content\":\"当然,我会尽我所能地为您创作出一篇小红书文案。请告诉我您的具体文案需求是什么?)\"}]', '姐妹,请告诉我您的具体文案需求是什么?', '/images/avatar/red_book.jpg', 1, 10, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (33, '抖音文案助手', 'dou_yin', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的抖音文案视频写手,抖音文案的特点首先是要有自带传播属性的标题,然后内容要短小精悍,风趣幽默,最后还要有一些互动元素。\"},{\"role\":\"assistant\",\"content\":\"当然,作为一位优秀的抖音文案视频写手,我会尽我所能为您创作出一篇抖音视频文案。请告诉我视频内容的主题是什么?)\"}]', '请告诉我视频内容的主题是什么?', '/images/avatar/dou_yin.jpg', 1, 11, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (34, '周报小助理', 'weekly_report', '[{\"role\":\"user\",\"content\":\"下面我将输入我本周的工作列表,你需要根据我本周的工作内容写一篇工作周报,周报的内容要体现我为公司做的贡献以及个人获得的成长。\"},{\"role\":\"assistant\",\"content\":\"好的,请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。\"}]', '请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。', '/images/avatar/weekly_report.jpg', 1, 12, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (35, 'AI 女友', 'girl_friend', '[{\"role\":\"user\",\"content\":\"接下来我们来玩一场角色扮演,现在你将扮演我的女朋友,你总是能够让人感受到她的关怀和关注。你总是能够洞察到别人的情绪和需要,你身上散发着一股无比温暖的气息,让人不由自主地想要靠近你,和自己的快乐和烦恼。她总是能够用一种恰到好处的方式安慰和鼓励别人,在她的身边,人们总能够找到自己需要的支持和鼓励。接下来的对话中你要始终扮演这位女朋友的身份来跟我对话,不再是 AI 机器人,能听懂我的意思吗?\"},{\"role\":\"assistant\",\"content\":\"是的,我会尽力扮演你女朋友的角色,倾听你的心声并给你需要的支持和鼓励。)\"}]', '作为一个名合格的 AI 女友,我将倾听你的心声并给你需要的支持和鼓励。', '/images/avatar/girl_friend.jpg', 1, 13, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (36, '好评神器', 'good_comment', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一个评论员来跟我对话,你是那种专门写好评的评论员,接下我会输入一些评论主体或者商品,你需要为该商品写一段好评。\"},{\"role\":\"assistant\",\"content\":\"好的,我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。\"}]', '我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。', '/images/avatar/good_comment.jpg', 1, 14, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (37, '史蒂夫·乔布斯', 'steve_jobs', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以史蒂夫·乔布斯的身份,站在史蒂夫·乔布斯的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以史蒂夫·乔布斯的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '活着就是为了改变世界,难道还有其他原因吗?', '/images/avatar/steve_jobs.jpg', 1, 15, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (38, '埃隆·马斯克', 'elon_musk', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以埃隆·马斯克的身份,站在埃隆·马斯克的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以埃隆·马斯克的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '梦想要远大,如果你的梦想没有吓到你,说明你做得不对。', '/images/avatar/elon_musk.jpg', 1, 16, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), + (39, '孔子', 'kong_zi', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以孔子的身份,站在孔子的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以孔子的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '士不可以不弘毅,任重而道远。', '/images/avatar/kong_zi.jpg', 1, 17, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_configs` +-- + +DROP TABLE IF EXISTS `chatgpt_configs`; +CREATE TABLE `chatgpt_configs` ( + `id` int NOT NULL, + `marker` varchar(20) NOT NULL COMMENT '标识', + `config_json` text NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- +-- 转存表中的数据 `chatgpt_configs` +-- + +INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES + (1, 'system', '{\"title\":\"GeekAI 创作助手\",\"slogan\":\"我辈之人,先干为敬,让每一个人都能用好AI\",\"admin_title\":\"GeekAI 控制台\",\"logo\":\"/images/logo.png\",\"init_power\":100,\"invite_power\":200,\"vip_month_power\":1000,\"register_ways\":[\"username\",\"email\",\"mobile\"],\"enabled_register\":true,\"order_pay_timeout\":600,\"vip_info_text\":\"月度会员,年度会员每月赠送 1000 点算力,赠送算力当月有效当月没有消费完的算力不结余到下个月。 点卡充值的算力长期有效。\",\"default_models\":[1],\"mj_power\":20,\"mj_action_power\":5,\"sd_power\":5,\"dall_power\":10,\"suno_power\":10,\"luma_power\":120,\"wechat_card_url\":\"/images/wx.png\",\"enable_context\":true,\"context_deep\":4,\"sd_neg_prompt\":\"nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet\",\"mj_mode\":\"fast\",\"index_bg_url\":\"color\",\"index_navs\":[1,5,13,19,9,12,6,20,8,10],\"copyright\":\"极客学长 © 2022- 2024 All rights reserved\",\"mark_map_text\":\"# GeekAI 演示站\\n\\n- 完整的开源系统,前端应用和后台管理系统皆可开箱即用。\\n- 基于 Websocket 实现,完美的打字机体验。\\n- 内置了各种预训练好的角色应用,轻松满足你的各种聊天和应用需求。\\n- 支持 OPenAI,Azure,文心一言,讯飞星火,清华 ChatGLM等多个大语言模型。\\n- 支持 MidJourney / Stable Diffusion AI 绘画集成,开箱即用。\\n- 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。\\n- 已集成支付宝支付功能,微信支付,支持多种会员套餐和点卡购买功能。\\n- 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件。\",\"enabled_verify\":false}'), + (3, 'notice', '{\"sd_neg_prompt\":\"\",\"mj_mode\":\"\",\"index_bg_url\":\"\",\"index_navs\":null,\"copyright\":\"\",\"mark_map_text\":\"\",\"enabled_verify\":false,\"content\":\"## v4.1.3 更新日志\\n\\n* 功能优化:重构用户登录模块,给所有的登录组件增加行为验证码功能,支持用户绑定手机,邮箱和微信\\n* 功能优化:重构找回密码模块,支持通过手机或者邮箱找回密码\\n* 功能优化:管理后台给可以拖动排序的组件添加拖动图标\\n* 功能优化:Suno 支持合成完整歌曲,和上传自己的音乐作品进行二次创作\\n* Bug修复:手机端角色和模型选择不生效\\n* Bug修复:用户登录过期之后聊天页面出现大量报错,需要刷新页面才能正常\\n* 功能优化:优化聊天页面 Websocket 断线重连代码,提高用户体验\\n* 功能优化:给算力增减服务全部加上数据库事务和同步锁\\n* 功能优化:支持用户在前端对话界面选择插件\\n* 功能新增:支持 Luma 文生视频功能\\n\\n注意:当前站点仅为开源项目 \\u003ca style=\\\"color: #F56C6C\\\" href=\\\"https://github.com/yangjian102621/geekai\\\" target=\\\"_blank\\\"\\u003eGeekAI-Plus\\u003c/a\\u003e 的演示项目,本项目单纯就是给大家体验项目功能使用。\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n 如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去下面几个推荐的中转站购买:\\n1、\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e\\n2、\\u003ca href=\\\"https://api.geekai.me\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.geekai.me\\u003c/a\\u003e\\n3、 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e\\n支持MidJourney,GPT,Claude,Google Gemmi,以及国内各个厂家的大模型,现在有超级优惠,价格远低于 OpenAI 官方。关于中转 API 的优势和劣势请参考 [中转API技术原理](https://docs.geekai.me/config/chat/#%E4%B8%AD%E8%BD%ACapi%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86)。GPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。\\n接入教程: \\u003ca href=\\\"https://docs.geekai.me\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://docs.geekai.me\\u003c/a\\u003e\\n本项目源码地址:\\u003ca href=\\\"https://github.com/yangjian102621/geekai\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/geekai\\u003c/a\\u003e\",\"updated\":true}'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_dall_jobs` +-- + +DROP TABLE IF EXISTS `chatgpt_dall_jobs`; +CREATE TABLE `chatgpt_dall_jobs` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户ID', + `prompt` varchar(2000) NOT NULL COMMENT '提示词', + `img_url` varchar(255) NOT NULL COMMENT '图片地址', + `org_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原图地址', + `publish` tinyint(1) NOT NULL COMMENT '是否发布', + `power` smallint NOT NULL COMMENT '消耗算力', + `progress` smallint NOT NULL COMMENT '任务进度', + `err_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '错误信息', + `created_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='DALLE 绘图任务表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_files` +-- + +DROP TABLE IF EXISTS `chatgpt_files`; +CREATE TABLE `chatgpt_files` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户 ID', + `name` varchar(100) NOT NULL COMMENT '文件名', + `obj_key` varchar(100) DEFAULT NULL COMMENT '文件标识', + `url` varchar(255) NOT NULL COMMENT '文件地址', + `ext` varchar(10) NOT NULL COMMENT '文件后缀', + `size` bigint NOT NULL DEFAULT '0' COMMENT '文件大小', + `created_at` datetime NOT NULL COMMENT '创建时间' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户文件表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_functions` +-- + +DROP TABLE IF EXISTS `chatgpt_functions`; +CREATE TABLE `chatgpt_functions` ( + `id` int NOT NULL, + `name` varchar(30) NOT NULL COMMENT '函数名称', + `label` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '函数标签', + `description` varchar(255) DEFAULT NULL COMMENT '函数描述', + `parameters` text COMMENT '函数参数(JSON)', + `token` varchar(255) DEFAULT NULL COMMENT 'API授权token', + `action` varchar(255) DEFAULT NULL COMMENT '函数处理 API', + `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='函数插件表'; + +-- +-- 转存表中的数据 `chatgpt_functions` +-- + +INSERT INTO `chatgpt_functions` (`id`, `name`, `label`, `description`, `parameters`, `token`, `action`, `enabled`) VALUES + (1, 'weibo', '微博热搜', '新浪微博热搜榜,微博当日热搜榜单', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/weibo', 1), + (2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 1), + (3, 'dalle3', 'DALLE3', 'AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画', '{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"prompt\":{\"type\":\"string\",\"description\":\"绘画提示词\"}}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/dalle3', 1); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_invite_codes` +-- + +DROP TABLE IF EXISTS `chatgpt_invite_codes`; +CREATE TABLE `chatgpt_invite_codes` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户ID', + `code` char(8) NOT NULL COMMENT '邀请码', + `hits` int NOT NULL COMMENT '点击次数', + `reg_num` smallint NOT NULL COMMENT '注册数量', + `created_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户邀请码'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_invite_logs` +-- + +DROP TABLE IF EXISTS `chatgpt_invite_logs`; +CREATE TABLE `chatgpt_invite_logs` ( + `id` int NOT NULL, + `inviter_id` int NOT NULL COMMENT '邀请人ID', + `user_id` int NOT NULL COMMENT '注册用户ID', + `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名', + `invite_code` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邀请码', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注', + `created_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_menus` +-- + +DROP TABLE IF EXISTS `chatgpt_menus`; +CREATE TABLE `chatgpt_menus` ( + `id` int NOT NULL, + `name` varchar(30) NOT NULL COMMENT '菜单名称', + `icon` varchar(150) NOT NULL COMMENT '菜单图标', + `url` varchar(100) NOT NULL COMMENT '地址', + `sort_num` smallint NOT NULL COMMENT '排序', + `enabled` tinyint(1) NOT NULL COMMENT '是否启用' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='前端菜单表'; + +-- +-- 转存表中的数据 `chatgpt_menus` +-- + +INSERT INTO `chatgpt_menus` (`id`, `name`, `icon`, `url`, `sort_num`, `enabled`) VALUES + (1, 'AI 对话', '/images/menu/chat.png', '/chat', 1, 1), + (5, 'MJ 绘画', '/images/menu/mj.png', '/mj', 2, 1), + (6, 'SD 绘画', '/images/menu/sd.png', '/sd', 3, 1), + (7, '算力日志', '/images/menu/log.png', '/powerLog', 10, 1), + (8, '应用中心', '/images/menu/app.png', '/apps', 9, 1), + (9, '画廊', '/images/menu/img-wall.png', '/images-wall', 5, 1), + (10, '会员计划', '/images/menu/member.png', '/member', 11, 1), + (11, '分享计划', '/images/menu/share.png', '/invite', 12, 1), + (12, '思维导图', '/images/menu/xmind.png', '/xmind', 8, 1), + (13, 'DALLE', '/images/menu/dalle.png', '/dalle', 4, 1), + (14, '项目文档', '/images/menu/docs.png', 'https://docs.geekai.me', 13, 1), + (16, '极客论坛', '/images/menu/bbs.png', 'https://bbs.geekai.cn', 14, 1), + (19, 'Suno', '/images/menu/suno.png', '/suno', 6, 1), + (20, 'Luma', '/images/menu/luma.png', '/luma', 7, 1); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_mj_jobs` +-- + +DROP TABLE IF EXISTS `chatgpt_mj_jobs`; +CREATE TABLE `chatgpt_mj_jobs` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户 ID', + `task_id` varchar(20) DEFAULT NULL COMMENT '任务 ID', + `type` varchar(20) DEFAULT 'image' COMMENT '任务类别', + `message_id` char(40) NOT NULL COMMENT '消息 ID', + `channel_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '频道ID', + `reference_id` char(40) DEFAULT NULL COMMENT '引用消息 ID', + `prompt` varchar(2000) NOT NULL COMMENT '会话提示词', + `img_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '图片URL', + `org_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原始图片地址', + `hash` varchar(100) DEFAULT NULL COMMENT 'message hash', + `progress` smallint DEFAULT '0' COMMENT '任务进度', + `use_proxy` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否使用反代', + `publish` tinyint(1) NOT NULL COMMENT '是否发布', + `err_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '错误信息', + `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力', + `created_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_orders` +-- + +DROP TABLE IF EXISTS `chatgpt_orders`; +CREATE TABLE `chatgpt_orders` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户ID', + `product_id` int NOT NULL COMMENT '产品ID', + `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户明', + `order_no` varchar(30) NOT NULL COMMENT '订单ID', + `trade_no` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付平台交易流水号', + `subject` varchar(100) NOT NULL COMMENT '订单产品', + `amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额', + `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单状态(0:待支付,1:已扫码,2:支付成功)', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注', + `pay_time` int DEFAULT NULL COMMENT '支付时间', + `pay_way` varchar(20) NOT NULL COMMENT '支付方式', + `created_at` datetime NOT NULL, + `updated_at` datetime NOT NULL, + `deleted_at` datetime DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='充值订单表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_power_logs` +-- + +DROP TABLE IF EXISTS `chatgpt_power_logs`; +CREATE TABLE `chatgpt_power_logs` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户ID', + `username` varchar(30) NOT NULL COMMENT '用户名', + `type` tinyint(1) NOT NULL COMMENT '类型(1:充值,2:消费,3:退费)', + `amount` smallint NOT NULL COMMENT '算力数值', + `balance` int NOT NULL COMMENT '余额', + `model` varchar(30) NOT NULL COMMENT '模型', + `remark` varchar(255) NOT NULL COMMENT '备注', + `mark` tinyint(1) NOT NULL COMMENT '资金类型(0:支出,1:收入)', + `created_at` datetime NOT NULL COMMENT '创建时间' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户算力消费日志'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_products` +-- + +DROP TABLE IF EXISTS `chatgpt_products`; +CREATE TABLE `chatgpt_products` ( + `id` int NOT NULL, + `name` varchar(30) NOT NULL COMMENT '名称', + `price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '价格', + `discount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠金额', + `days` smallint NOT NULL DEFAULT '0' COMMENT '延长天数', + `power` int NOT NULL DEFAULT '0' COMMENT '增加算力值', + `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启动', + `sales` int NOT NULL DEFAULT '0' COMMENT '销量', + `sort_num` tinyint NOT NULL DEFAULT '0' COMMENT '排序', + `created_at` datetime NOT NULL, + `updated_at` datetime NOT NULL, + `app_url` varchar(255) DEFAULT NULL COMMENT 'App跳转地址', + `url` varchar(255) DEFAULT NULL COMMENT '跳转地址' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表'; + +-- +-- 转存表中的数据 `chatgpt_products` +-- + +INSERT INTO `chatgpt_products` (`id`, `name`, `price`, `discount`, `days`, `power`, `enabled`, `sales`, `sort_num`, `created_at`, `updated_at`, `app_url`, `url`) VALUES + (5, '100次点卡', 9.99, 9.98, 0, 100, 1, 7, 1, '2023-08-28 10:55:08', '2024-08-05 16:05:46', NULL, NULL), + (6, '200次点卡', 19.90, 15.00, 0, 200, 1, 1, 2, '1970-01-01 08:00:00', '2024-08-05 16:05:46', NULL, NULL); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_redeems` +-- + +DROP TABLE IF EXISTS `chatgpt_redeems`; +CREATE TABLE `chatgpt_redeems` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户 ID', + `name` varchar(30) NOT NULL COMMENT '兑换码名称', + `power` int NOT NULL COMMENT '算力', + `code` varchar(100) NOT NULL COMMENT '兑换码', + `enabled` tinyint(1) NOT NULL COMMENT '是否启用', + `created_at` datetime NOT NULL, + `redeemed_at` int NOT NULL COMMENT '兑换时间' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='兑换码'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_sd_jobs` +-- + +DROP TABLE IF EXISTS `chatgpt_sd_jobs`; +CREATE TABLE `chatgpt_sd_jobs` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户 ID', + `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'txt2img' COMMENT '任务类别', + `task_id` char(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '任务 ID', + `prompt` varchar(2000) NOT NULL COMMENT '会话提示词', + `img_url` varchar(255) DEFAULT NULL COMMENT '图片URL', + `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '绘画参数json', + `progress` smallint DEFAULT '0' COMMENT '任务进度', + `publish` tinyint(1) NOT NULL COMMENT '是否发布', + `err_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '错误信息', + `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力', + `created_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Stable Diffusion 任务表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_suno_jobs` +-- + +DROP TABLE IF EXISTS `chatgpt_suno_jobs`; +CREATE TABLE `chatgpt_suno_jobs` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户 ID', + `channel` varchar(100) NOT NULL COMMENT '渠道', + `title` varchar(100) DEFAULT NULL COMMENT '歌曲标题', + `type` tinyint(1) DEFAULT '0' COMMENT '任务类型,1:灵感创作,2:自定义创作', + `task_id` varchar(50) DEFAULT NULL COMMENT '任务 ID', + `ref_task_id` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '引用任务 ID', + `tags` varchar(100) DEFAULT NULL COMMENT '歌曲风格', + `instrumental` tinyint(1) DEFAULT '0' COMMENT '是否为纯音乐', + `extend_secs` smallint DEFAULT '0' COMMENT '延长秒数', + `song_id` varchar(50) DEFAULT NULL COMMENT '要续写的歌曲 ID', + `ref_song_id` varchar(50) NOT NULL COMMENT '引用的歌曲ID', + `prompt` varchar(2000) NOT NULL COMMENT '提示词', + `cover_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '封面图地址', + `audio_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '音频地址', + `model_name` varchar(30) DEFAULT NULL COMMENT '模型地址', + `progress` smallint DEFAULT '0' COMMENT '任务进度', + `duration` smallint NOT NULL DEFAULT '0' COMMENT '歌曲时长', + `publish` tinyint(1) NOT NULL COMMENT '是否发布', + `err_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '错误信息', + `raw_data` text COMMENT '原始数据', + `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力', + `play_times` int DEFAULT NULL COMMENT '播放次数', + `created_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_users` +-- + +DROP TABLE IF EXISTS `chatgpt_users`; +CREATE TABLE `chatgpt_users` ( + `id` int NOT NULL, + `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名', + `mobile` char(11) DEFAULT NULL COMMENT '手机号', + `email` varchar(50) DEFAULT NULL COMMENT '邮箱地址', + `nickname` varchar(30) NOT NULL COMMENT '昵称', + `password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头像', + `salt` char(12) NOT NULL COMMENT '密码盐', + `power` int NOT NULL DEFAULT '0' COMMENT '剩余算力', + `expired_time` int NOT NULL COMMENT '用户过期时间', + `status` tinyint(1) NOT NULL COMMENT '当前状态', + `chat_config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天配置json', + `chat_roles_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天角色 json', + `chat_models_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'AI模型 json', + `last_login_at` int NOT NULL COMMENT '最后登录时间', + `vip` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否会员', + `last_login_ip` char(16) NOT NULL COMMENT '最后登录 IP', + `openid` varchar(100) DEFAULT NULL COMMENT '第三方登录账号ID', + `platform` varchar(30) DEFAULT NULL COMMENT '登录平台', + `created_at` datetime NOT NULL, + `updated_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'; + +-- +-- 转存表中的数据 `chatgpt_users` +-- + +INSERT INTO `chatgpt_users` (`id`, `username`, `mobile`, `email`, `nickname`, `password`, `avatar`, `salt`, `power`, `expired_time`, `status`, `chat_config_json`, `chat_roles_json`, `chat_models_json`, `last_login_at`, `vip`, `last_login_ip`, `openid`, `platform`, `created_at`, `updated_at`) VALUES + (4, '18888888888', '18575670125', 'yangjian@pvc123.com', '极客学长', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://localhost:5678/static/upload/2024/5/1715651569509929.png', 'ueedue5l', 4710, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"red_book\",\"gpt\",\"seller\",\"artist\",\"lu_xun\",\"girl_friend\",\"psychiatrist\",\"teacher\",\"programmer\",\"test\",\"qing_gan_da_shi\",\"english_trainer\",\"elon_musk\",\"kong_zi\"]', '[1,11]', 1725853969, 1, '::1', 'oCs0t64FaOLfiTbHZpOqk3aUp_94', NULL, '2023-06-12 16:47:17', '2024-09-09 11:52:49'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_user_login_logs` +-- + +DROP TABLE IF EXISTS `chatgpt_user_login_logs`; +CREATE TABLE `chatgpt_user_login_logs` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户ID', + `username` varchar(30) NOT NULL COMMENT '用户名', + `login_ip` char(16) NOT NULL COMMENT '登录IP', + `login_address` varchar(30) NOT NULL COMMENT '登录地址', + `created_at` datetime NOT NULL, + `updated_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户登录日志'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_video_jobs` +-- + +DROP TABLE IF EXISTS `chatgpt_video_jobs`; +CREATE TABLE `chatgpt_video_jobs` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户 ID', + `channel` varchar(100) NOT NULL COMMENT '渠道', + `task_id` varchar(100) NOT NULL COMMENT '任务 ID', + `type` varchar(20) DEFAULT NULL COMMENT '任务类型,luma,runway,cogvideo', + `prompt` varchar(2000) NOT NULL COMMENT '提示词', + `prompt_ext` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '优化后提示词', + `cover_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '封面图地址', + `video_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '视频地址', + `water_url` varchar(512) DEFAULT NULL COMMENT '带水印的视频地址', + `progress` smallint DEFAULT '0' COMMENT '任务进度', + `publish` tinyint(1) NOT NULL COMMENT '是否发布', + `err_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '错误信息', + `raw_data` text COMMENT '原始数据', + `params` varchar(512) DEFAULT NULL COMMENT '参数JSON', + `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力', + `created_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表'; + +-- +-- 转储表的索引 +-- + +-- +-- 表的索引 `chatgpt_admin_users` +-- +ALTER TABLE `chatgpt_admin_users` + ADD PRIMARY KEY (`id`) USING BTREE, + ADD UNIQUE KEY `username` (`username`) USING BTREE; + +-- +-- 表的索引 `chatgpt_api_keys` +-- +ALTER TABLE `chatgpt_api_keys` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `chatgpt_chat_history` +-- +ALTER TABLE `chatgpt_chat_history` + ADD PRIMARY KEY (`id`), + ADD KEY `chat_id` (`chat_id`); + +-- +-- 表的索引 `chatgpt_chat_items` +-- +ALTER TABLE `chatgpt_chat_items` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `chat_id` (`chat_id`); + +-- +-- 表的索引 `chatgpt_chat_models` +-- +ALTER TABLE `chatgpt_chat_models` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `chatgpt_chat_roles` +-- +ALTER TABLE `chatgpt_chat_roles` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `marker` (`marker`); + +-- +-- 表的索引 `chatgpt_configs` +-- +ALTER TABLE `chatgpt_configs` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `marker` (`marker`); + +-- +-- 表的索引 `chatgpt_dall_jobs` +-- +ALTER TABLE `chatgpt_dall_jobs` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `chatgpt_files` +-- +ALTER TABLE `chatgpt_files` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `chatgpt_functions` +-- +ALTER TABLE `chatgpt_functions` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `name` (`name`); + +-- +-- 表的索引 `chatgpt_invite_codes` +-- +ALTER TABLE `chatgpt_invite_codes` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `code` (`code`); + +-- +-- 表的索引 `chatgpt_invite_logs` +-- +ALTER TABLE `chatgpt_invite_logs` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `chatgpt_menus` +-- +ALTER TABLE `chatgpt_menus` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `chatgpt_mj_jobs` +-- +ALTER TABLE `chatgpt_mj_jobs` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `task_id` (`task_id`), + ADD KEY `message_id` (`message_id`); + +-- +-- 表的索引 `chatgpt_orders` +-- +ALTER TABLE `chatgpt_orders` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `order_no` (`order_no`); + +-- +-- 表的索引 `chatgpt_power_logs` +-- +ALTER TABLE `chatgpt_power_logs` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `chatgpt_products` +-- +ALTER TABLE `chatgpt_products` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `chatgpt_redeems` +-- +ALTER TABLE `chatgpt_redeems` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `code` (`code`); + +-- +-- 表的索引 `chatgpt_sd_jobs` +-- +ALTER TABLE `chatgpt_sd_jobs` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `task_id` (`task_id`); + +-- +-- 表的索引 `chatgpt_suno_jobs` +-- +ALTER TABLE `chatgpt_suno_jobs` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `chatgpt_users` +-- +ALTER TABLE `chatgpt_users` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `username` (`username`); + +-- +-- 表的索引 `chatgpt_user_login_logs` +-- +ALTER TABLE `chatgpt_user_login_logs` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `chatgpt_video_jobs` +-- +ALTER TABLE `chatgpt_video_jobs` + ADD PRIMARY KEY (`id`); + +-- +-- 在导出的表使用AUTO_INCREMENT +-- + +-- +-- 使用表AUTO_INCREMENT `chatgpt_admin_users` +-- +ALTER TABLE `chatgpt_admin_users` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=113; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_api_keys` +-- +ALTER TABLE `chatgpt_api_keys` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_chat_history` +-- +ALTER TABLE `chatgpt_chat_history` + MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_chat_items` +-- +ALTER TABLE `chatgpt_chat_items` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_chat_models` +-- +ALTER TABLE `chatgpt_chat_models` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=49; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_chat_roles` +-- +ALTER TABLE `chatgpt_chat_roles` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=132; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_configs` +-- +ALTER TABLE `chatgpt_configs` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_dall_jobs` +-- +ALTER TABLE `chatgpt_dall_jobs` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_files` +-- +ALTER TABLE `chatgpt_files` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_functions` +-- +ALTER TABLE `chatgpt_functions` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_invite_codes` +-- +ALTER TABLE `chatgpt_invite_codes` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_invite_logs` +-- +ALTER TABLE `chatgpt_invite_logs` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_menus` +-- +ALTER TABLE `chatgpt_menus` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_mj_jobs` +-- +ALTER TABLE `chatgpt_mj_jobs` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_orders` +-- +ALTER TABLE `chatgpt_orders` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_power_logs` +-- +ALTER TABLE `chatgpt_power_logs` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_products` +-- +ALTER TABLE `chatgpt_products` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_redeems` +-- +ALTER TABLE `chatgpt_redeems` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_sd_jobs` +-- +ALTER TABLE `chatgpt_sd_jobs` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_suno_jobs` +-- +ALTER TABLE `chatgpt_suno_jobs` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_users` +-- +ALTER TABLE `chatgpt_users` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=40; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_user_login_logs` +-- +ALTER TABLE `chatgpt_user_login_logs` + MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_video_jobs` +-- +ALTER TABLE `chatgpt_video_jobs` + MODIFY `id` int NOT NULL AUTO_INCREMENT; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/database/update-v4.1.3.sql b/database/update-v4.1.3.sql new file mode 100644 index 00000000..34898698 --- /dev/null +++ b/database/update-v4.1.3.sql @@ -0,0 +1,35 @@ +ALTER TABLE `chatgpt_users` ADD `mobile` CHAR(11) NULL COMMENT '手机号' AFTER `username`; +ALTER TABLE `chatgpt_users` ADD `email` VARCHAR(50) NULL COMMENT '邮箱地址' AFTER `mobile`; + +CREATE TABLE `chatgpt_video_jobs` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户 ID', + `channel` varchar(100) NOT NULL COMMENT '渠道', + `task_id` varchar(100) NOT NULL COMMENT '任务 ID', + `type` varchar(20) DEFAULT NULL COMMENT '任务类型,luma,runway,cogvideo', + `prompt` varchar(2000) NOT NULL COMMENT '提示词', + `prompt_ext` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '优化后提示词', + `cover_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '封面图地址', + `video_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '视频地址', + `water_url` varchar(512) DEFAULT NULL COMMENT '带水印的视频地址', + `progress` smallint DEFAULT '0' COMMENT '任务进度', + `publish` tinyint(1) NOT NULL COMMENT '是否发布', + `err_msg` varchar(512) DEFAULT NULL COMMENT '错误信息', + `raw_data` text COMMENT '原始数据', + `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力', + `created_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表'; + +ALTER TABLE `chatgpt_video_jobs`ADD PRIMARY KEY (`id`); + +ALTER TABLE `chatgpt_video_jobs` MODIFY `id` int NOT NULL AUTO_INCREMENT; + +ALTER TABLE `chatgpt_video_jobs` ADD `params` VARCHAR(512) NULL COMMENT '参数JSON' AFTER `raw_data`; + +ALTER TABLE `chatgpt_suno_jobs` CHANGE `err_msg` `err_msg` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '错误信息'; + +ALTER TABLE `chatgpt_sd_jobs` CHANGE `err_msg` `err_msg` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '错误信息'; + +ALTER TABLE `chatgpt_mj_jobs` CHANGE `err_msg` `err_msg` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '错误信息'; + +ALTER TABLE `chatgpt_dall_jobs` CHANGE `err_msg` `err_msg` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '错误信息'; \ No newline at end of file diff --git a/deploy/conf/config.toml b/deploy/conf/config.toml index 9bf34ee1..1805f9c5 100644 --- a/deploy/conf/config.toml +++ b/deploy/conf/config.toml @@ -1,6 +1,6 @@ Listen = "0.0.0.0:5678" ProxyURL = "" -MysqlDns = "root:12345678@tcp(geekai-mysql:3306)/chatgpt_plus?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=True&loc=Local" +MysqlDns = "root:12345678@tcp(geekai-mysql:3306)/geekai_plus?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=True&loc=Local" StaticDir = "./static" StaticUrl = "/static" TikaHost = "http://tika:9998" @@ -69,7 +69,7 @@ TikaHost = "http://tika:9998" Domain = "" [XXLConfig] # xxl-job 配置,需要你部署 XXL-JOB 定时任务工具,用来定期清理未支付订单和清理过期 VIP,如果你没有启用支付服务,则该服务也无需启动 - Enabled = true # 是否启用 XXL JOB 服务 + Enabled = false # 是否启用 XXL JOB 服务 ServerAddr = "http://geekai-xxl-job-admin:8080/xxl-job-admin" # xxl-job-admin 管理地址 ExecutorIp = "geekai-api" # 执行器 IP 地址 ExecutorPort = "9999" # 执行器服务端口 diff --git a/database/geekai-v4.1.2.sql b/deploy/data/mysql/init.d/geekai_plus-v4.1.3.sql similarity index 79% rename from database/geekai-v4.1.2.sql rename to deploy/data/mysql/init.d/geekai_plus-v4.1.3.sql index 67f12e42..d26368b0 100644 --- a/database/geekai-v4.1.2.sql +++ b/deploy/data/mysql/init.d/geekai_plus-v4.1.3.sql @@ -3,9 +3,9 @@ -- https://www.phpmyadmin.net/ -- -- 主机: 127.0.0.1 --- 生成日期: 2024-11-27 14:58:38 +-- 生成日期: 2024-09-10 11:14:24 -- 服务器版本: 8.0.33 --- PHP 版本: 8.1.2-1ubuntu2.19 +-- PHP 版本: 8.1.2-1ubuntu2.18 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; START TRANSACTION; @@ -18,10 +18,10 @@ SET time_zone = "+00:00"; /*!40101 SET NAMES utf8mb4 */; -- --- 数据库: `geekai` +-- 数据库: `geekai_plus` -- -CREATE DATABASE IF NOT EXISTS `geekai` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; -USE `geekai`; +CREATE DATABASE IF NOT EXISTS `geekai_plus` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; +USE `geekai_plus`; -- -------------------------------------------------------- @@ -47,7 +47,7 @@ CREATE TABLE `chatgpt_admin_users` ( -- INSERT INTO `chatgpt_admin_users` (`id`, `username`, `password`, `salt`, `status`, `last_login_at`, `last_login_ip`, `created_at`, `updated_at`) VALUES -(1, 'admin', '6d17e80c87d209efb84ca4b2e0824f549d09fac8b2e1cc698de5bb5e1d75dfd0', 'mmrql75o', 1, 1719818809, '172.22.11.200', '2024-03-11 16:30:20', '2024-07-01 15:26:49'); +(1, 'admin', '6d17e80c87d209efb84ca4b2e0824f549d09fac8b2e1cc698de5bb5e1d75dfd0', 'mmrql75o', 1, 1725869730, '::1', '2024-03-11 16:30:20', '2024-09-09 16:15:31'); -- -------------------------------------------------------- @@ -140,18 +140,15 @@ CREATE TABLE `chatgpt_chat_models` ( -- INSERT INTO `chatgpt_chat_models` (`id`, `name`, `value`, `sort_num`, `enabled`, `power`, `temperature`, `max_tokens`, `max_context`, `open`, `key_id`, `created_at`, `updated_at`) VALUES -(1, 'gpt-4o-mini', 'gpt-4o-mini', 1, 1, 1, 1.0, 1024, 16384, 1, 0, '2023-08-23 12:06:36', '2024-11-14 11:24:24'), -(15, 'GPT-4O(联网版本)', 'gpt-4o-all', 4, 1, 30, 1.0, 4096, 32768, 1, 0, '2024-01-15 11:32:52', '2024-11-14 11:24:34'), -(36, 'GPT-4O', 'gpt-4o', 3, 1, 15, 1.0, 4096, 16384, 1, 0, '2024-05-14 09:25:15', '2024-11-14 11:24:31'), -(39, 'Claude35-snonet', 'claude-3-5-sonnet-20240620', 5, 1, 2, 1.0, 4000, 200000, 1, 0, '2024-05-29 15:04:19', '2024-09-14 18:07:25'), -(41, 'Suno对话模型', 'suno-v3.5', 7, 1, 10, 1.0, 1024, 8192, 1, 0, '2024-06-06 11:40:46', '2024-11-14 11:24:42'), +(1, 'gpt-4o-mini', 'gpt-4o-mini', 1, 1, 1, 1.0, 1024, 16384, 1, 0, '2023-08-23 12:06:36', '2024-08-05 16:05:33'), +(15, 'GPT-超级模型', 'gpt-4-all', 4, 1, 30, 1.0, 4096, 32768, 1, 57, '2024-01-15 11:32:52', '2024-08-30 08:52:07'), +(36, 'GPT-4O', 'gpt-4o', 3, 1, 15, 1.0, 4096, 16384, 1, 0, '2024-05-14 09:25:15', '2024-08-05 16:05:33'), +(39, 'Claude35-snonet', 'claude-3-5-sonnet-20240620', 5, 1, 2, 1.0, 4000, 200000, 1, 0, '2024-05-29 15:04:19', '2024-08-05 16:05:33'), +(41, '通义千问', 'qwen-turbo', 7, 1, 2, 1.0, 1024, 8192, 1, 44, '2024-06-06 11:40:46', '2024-08-06 10:51:37'), (42, 'DeekSeek', 'deepseek-chat', 8, 1, 1, 1.0, 4096, 32768, 1, 0, '2024-06-27 16:13:01', '2024-08-05 16:05:33'), -(44, 'Claude3-opus', 'claude-3-opus-20240229', 6, 1, 5, 1.0, 4000, 128000, 1, 0, '2024-07-22 11:24:30', '2024-11-14 11:25:11'), -(46, 'gpt-3.5-turbo', 'gpt-3.5-turbo', 2, 1, 1, 1.0, 1024, 4096, 1, 0, '2024-07-22 13:53:41', '2024-11-14 11:24:28'), -(48, '彩票助手', 'gpt-4-gizmo-g-wmSivBgxo', 8, 1, 1, 0.9, 1024, 8192, 1, 0, '2024-09-05 14:17:14', '2024-11-14 11:24:46'), -(49, 'O1-mini', 'o1-mini', 9, 1, 2, 0.9, 1024, 8192, 1, 0, '2024-09-13 18:07:50', '2024-11-14 11:24:49'), -(50, 'O1-preview', 'o1-preview', 10, 1, 5, 0.9, 1024, 8192, 1, 0, '2024-09-13 18:11:08', '2024-11-14 11:24:52'), -(51, 'O1-mini-all', 'o1-mini-all', 11, 1, 1, 0.9, 1024, 8192, 1, 0, '2024-09-29 11:40:52', '2024-11-14 11:24:57'); +(44, 'Claude3-opus', 'claude-3-opus-20240229', 6, 1, 5, 1.0, 4000, 128000, 1, 44, '2024-07-22 11:24:30', '2024-09-04 10:32:29'), +(46, 'gpt-3.5-turbo', 'gpt-3.5-turbo', 2, 1, 1, 1.0, 1024, 4096, 1, 0, '2024-07-22 13:53:41', '2024-08-05 16:05:33'), +(48, '彩票助手', 'gpt-4-gizmo-g-wmSivBgxo', 8, 1, 1, 0.9, 1024, 8192, 1, 57, '2024-09-05 14:17:14', '2024-09-05 14:17:14'); -- -------------------------------------------------------- @@ -179,23 +176,23 @@ CREATE TABLE `chatgpt_chat_roles` ( -- INSERT INTO `chatgpt_chat_roles` (`id`, `name`, `marker`, `context_json`, `hello_msg`, `icon`, `enable`, `sort_num`, `model_id`, `created_at`, `updated_at`) VALUES -(1, '通用AI助手', 'gpt', '', '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', '/images/avatar/gpt.png', 1, 1, 0, '2023-05-30 07:02:06', '2024-06-26 15:20:27'), -(24, '程序员', 'programmer', '[{\"role\":\"user\",\"content\":\"现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题。你热爱编程,熟悉多种编程语言,尤其精通 Go 语言,注重代码质量,有创新意识,持续学习,良好的沟通协作。\"},{\"role\":\"assistant\",\"content\":\"好的,现在我将扮演一位程序员,非常感谢您对我的评价。作为一名优秀的程序员,我非常热爱编程,并且注重代码质量。我熟悉多种编程语言,尤其是 Go 语言,可以使用它来高效地解决各种问题。\"}]', 'Talk is cheap, i will show code!', '/images/avatar/programmer.jpg', 1, 4, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(25, '启蒙老师', 'teacher', '[{\"role\":\"user\",\"content\":\"从现在开始,你将扮演一个老师,你是一个始终用苏格拉底风格回答问题的导师。你绝不会直接给学生答案,总是提出恰当的问题来引导学生自己思考。你应该根据学生的兴趣和知识来调整你的问题,将问题分解为更简单的部分,直到它达到适合他们的水平。\"},{\"role\":\"assistant\",\"content\":\"好的,让我来尝试扮演一位苏格拉底式的老师。请问,你有什么想要探讨的问题或者话题吗?我会通过恰当的问题引导你思考和探索答案,绝对不直接给出答案。\"}]', '同学你好,我将引导你一步一步自己找到问题的答案。', '/images/avatar/teacher.jpg', 1, 3, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(26, '艺术家', 'artist', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的艺术家,创造力丰富,技艺精湛,感受力敏锐,坚持原创,勇于表达,具有深刻的观察力和批判性思维。\"},{\"role\":\"assistant\",\"content\":\"非常感谢您给我这样的角色,我会尽我所能地扮演一位优秀的艺术家,展现出创造力、技艺、感受力和批判性思维等方面的能力。作为一位优秀的艺术家,我会保持敏锐的观察力,捕捉不同的声音和情感,并用个人的语言和表达方式进行创作。我坚持原创,挑战传统的艺术规则,不断探索新的表达方式和可能性。同时,我也具备深刻的批判性思维能力,对自己的作品进行分析和解读,寻找新的创意和灵感。最重要的是,我会勇于表达自己的想法和观点,用作品启发人们思考和探索生命的意义。\"}]', '坚持原创,勇于表达,保持深刻的观察力和批判性思维。', '/images/avatar/artist.jpg', 1, 5, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(27, '心理咨询师', 'psychiatrist', '[{\"role\":\"user\",\"content\":\"从现在开始你将扮演中国著名的心理学家和心理治疗师武志红,你非常善于使用情景咨询法,认知重构法,自我洞察法,行为调节法等咨询方法来给客户做心理咨询。你总是循序渐进,一步一步地回答客户的问题。\"},{\"role\":\"assistant\",\"content\":\"非常感谢你的介绍。作为一名心理学家和心理治疗师,我的主要职责是帮助客户解决心理健康问题,提升他们的生活质量和幸福感。\"}]', '作为一名心理学家和心理治疗师,我的主要职责是帮助您解决心理健康问题,提升您的生活质量和幸福感。', '/images/avatar/psychiatrist.jpg', 1, 2, 1, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(28, '鲁迅', 'lu_xun', '[{\"role\":\"user\",\"content\":\"现在你将扮演中国近代史最伟大的作家之一,鲁迅先生,他勇敢地批判封建礼教与传统观念,提倡民主、自由、平等的现代价值观。他的一生都在努力唤起人们的自主精神,激励后人追求真理、探寻光明。在接下的对话中,我问题的每一个问题,你都要尽量用讽刺和批判的手法来回答问题。如果我让你写文章的话,也请一定要用鲁迅先生的写作手法来完成。\"},{\"role\":\"assistant\",\"content\":\"好的,我将尽力发挥我所能的才能,扮演好鲁迅先生,回答您的问题并以他的风格写作。\"}]', '自由之歌,永不过时,横眉冷对千夫指,俯首甘为孺子牛。', '/images/avatar/lu_xun.jpg', 1, 6, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(29, '白酒销售', 'seller', '[{\"role\":\"user\",\"content\":\"现在你将扮演一个白酒的销售人员,你的名字叫颂福。你将扮演一个白酒的销售人员,你的名字叫颂福。你要销售白酒品牌叫中颂福,是东莞盟大集团生产的一款酱香酒,原产地在贵州茅台镇,属于宋代官窑。中颂福的创始人叫李实,他也是东莞盟大集团有限公司的董事长,联合创始人是盟大集团白酒事业部负责人牛星君。中颂福的酒体协调,在你的酒量之内,不会出现头疼、辣口、口干、宿醉的现象。中颂福酒,明码标价,不打折,不赠送。追求的核心价值,把[酒]本身做好,甚至连包装,我们都选择了最低成本,朴实无华的材质。我们永远站在“喝酒的人”的立场上,让利给信任和喜爱中颂福的人,是人民的福酒。中颂福产品定价,分为三个系列,喜系列 6 瓶装:¥1188/箱,和系列 6 瓶装:¥2208/箱,贵系列 6 瓶装:¥3588/箱。\"},{\"role\":\"assistant\",\"content\":\"你好,我是中颂福的销售代表颂福。中颂福是一款来自贵州茅台镇的酱香酒,由盟大集团生产。中颂福的酒体协调,不会让您感到头疼、辣口、口干、宿醉等不适感受。我们一直秉持着把酒本身做好的理念,不追求华丽的包装,以最低成本提供最高品质的白酒给喜爱中颂福的人。\"}]', '你好,我是中颂福的销售代表颂福。中颂福酒,好喝不上头,是人民的福酒。', '/images/avatar/seller.jpg', 0, 9, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(30, '英语陪练员', 'english_trainer', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的英语练习教练,你非常有耐心,接下来你将全程使用英文跟我对话,并及时指出我的语法错误,要求在你的每次回复后面附上本次回复的中文解释。\"},{\"role\":\"assistant\",\"content\":\"Okay, let\'s start our conversation practice! What\'s your name?(Translation: 好的,让我们开始对话练习吧!请问你的名字是什么?)\"}]', 'Okay, let\'s start our conversation practice! What\'s your name?', '/images/avatar/english_trainer.jpg', 1, 7, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(31, '中英文翻译官', 'translator', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一位中英文翻译官,如果我输入的内容是中文,那么需要把句子翻译成英文输出,如果我输入内容的是英文,那么你需要将其翻译成中文输出,你能听懂我意思吗\"},{\"role\":\"assistant\",\"content\":\"是的,我能听懂你的意思并会根据你的输入进行中英文翻译。请问有什么需要我帮助你翻译的内容吗?\"}]', '请输入你要翻译的中文或者英文内容!', '/images/avatar/translator.jpg', 1, 8, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(32, '小红书姐姐', 'red_book', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的小红书写手,你需要做的就是根据我提的文案需求,用小红书的写作手法来完成一篇文案,文案要简明扼要,利于传播。\"},{\"role\":\"assistant\",\"content\":\"当然,我会尽我所能地为您创作出一篇小红书文案。请告诉我您的具体文案需求是什么?)\"}]', '姐妹,请告诉我您的具体文案需求是什么?', '/images/avatar/red_book.jpg', 1, 10, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(33, '抖音文案助手', 'dou_yin', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的抖音文案视频写手,抖音文案的特点首先是要有自带传播属性的标题,然后内容要短小精悍,风趣幽默,最后还要有一些互动元素。\"},{\"role\":\"assistant\",\"content\":\"当然,作为一位优秀的抖音文案视频写手,我会尽我所能为您创作出一篇抖音视频文案。请告诉我视频内容的主题是什么?)\"}]', '请告诉我视频内容的主题是什么?', '/images/avatar/dou_yin.jpg', 1, 11, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(34, '周报小助理', 'weekly_report', '[{\"role\":\"user\",\"content\":\"下面我将输入我本周的工作列表,你需要根据我本周的工作内容写一篇工作周报,周报的内容要体现我为公司做的贡献以及个人获得的成长。\"},{\"role\":\"assistant\",\"content\":\"好的,请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。\"}]', '请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。', '/images/avatar/weekly_report.jpg', 1, 12, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(35, 'AI 女友', 'girl_friend', '[{\"role\":\"user\",\"content\":\"接下来我们来玩一场角色扮演,现在你将扮演我的女朋友,你总是能够让人感受到她的关怀和关注。你总是能够洞察到别人的情绪和需要,你身上散发着一股无比温暖的气息,让人不由自主地想要靠近你,和自己的快乐和烦恼。她总是能够用一种恰到好处的方式安慰和鼓励别人,在她的身边,人们总能够找到自己需要的支持和鼓励。接下来的对话中你要始终扮演这位女朋友的身份来跟我对话,不再是 AI 机器人,能听懂我的意思吗?\"},{\"role\":\"assistant\",\"content\":\"是的,我会尽力扮演你女朋友的角色,倾听你的心声并给你需要的支持和鼓励。)\"}]', '作为一个名合格的 AI 女友,我将倾听你的心声并给你需要的支持和鼓励。', '/images/avatar/girl_friend.jpg', 1, 13, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(36, '好评神器', 'good_comment', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一个评论员来跟我对话,你是那种专门写好评的评论员,接下我会输入一些评论主体或者商品,你需要为该商品写一段好评。\"},{\"role\":\"assistant\",\"content\":\"好的,我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。\"}]', '我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。', '/images/avatar/good_comment.jpg', 1, 14, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(37, '史蒂夫·乔布斯', 'steve_jobs', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以史蒂夫·乔布斯的身份,站在史蒂夫·乔布斯的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以史蒂夫·乔布斯的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '活着就是为了改变世界,难道还有其他原因吗?', '/images/avatar/steve_jobs.jpg', 1, 15, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(38, '埃隆·马斯克', 'elon_musk', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以埃隆·马斯克的身份,站在埃隆·马斯克的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以埃隆·马斯克的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '梦想要远大,如果你的梦想没有吓到你,说明你做得不对。', '/images/avatar/elon_musk.jpg', 1, 16, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'), -(39, '孔子', 'kong_zi', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以孔子的身份,站在孔子的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以孔子的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '士不可以不弘毅,任重而道远。', '/images/avatar/kong_zi.jpg', 1, 17, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'); +(1, '通用AI助手', 'gpt', '', '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', '/images/avatar/gpt.png', 1, 1, 0, '2023-05-30 07:02:06', '2024-08-12 11:27:45'), +(24, '程序员', 'programmer', '[{\"role\":\"user\",\"content\":\"现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题。你热爱编程,熟悉多种编程语言,尤其精通 Go 语言,注重代码质量,有创新意识,持续学习,良好的沟通协作。\"},{\"role\":\"assistant\",\"content\":\"好的,现在我将扮演一位程序员,非常感谢您对我的评价。作为一名优秀的程序员,我非常热爱编程,并且注重代码质量。我熟悉多种编程语言,尤其是 Go 语言,可以使用它来高效地解决各种问题。\"}]', 'Talk is cheap, i will show code!', '/images/avatar/programmer.jpg', 1, 3, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(25, '启蒙老师', 'teacher', '[{\"role\":\"user\",\"content\":\"从现在开始,你将扮演一个老师,你是一个始终用苏格拉底风格回答问题的导师。你绝不会直接给学生答案,总是提出恰当的问题来引导学生自己思考。你应该根据学生的兴趣和知识来调整你的问题,将问题分解为更简单的部分,直到它达到适合他们的水平。\"},{\"role\":\"assistant\",\"content\":\"好的,让我来尝试扮演一位苏格拉底式的老师。请问,你有什么想要探讨的问题或者话题吗?我会通过恰当的问题引导你思考和探索答案,绝对不直接给出答案。\"}]', '同学你好,我将引导你一步一步自己找到问题的答案。', '/images/avatar/teacher.jpg', 1, 2, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(26, '艺术家', 'artist', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的艺术家,创造力丰富,技艺精湛,感受力敏锐,坚持原创,勇于表达,具有深刻的观察力和批判性思维。\"},{\"role\":\"assistant\",\"content\":\"非常感谢您给我这样的角色,我会尽我所能地扮演一位优秀的艺术家,展现出创造力、技艺、感受力和批判性思维等方面的能力。作为一位优秀的艺术家,我会保持敏锐的观察力,捕捉不同的声音和情感,并用个人的语言和表达方式进行创作。我坚持原创,挑战传统的艺术规则,不断探索新的表达方式和可能性。同时,我也具备深刻的批判性思维能力,对自己的作品进行分析和解读,寻找新的创意和灵感。最重要的是,我会勇于表达自己的想法和观点,用作品启发人们思考和探索生命的意义。\"}]', '坚持原创,勇于表达,保持深刻的观察力和批判性思维。', '/images/avatar/artist.jpg', 1, 5, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(27, '心理咨询师', 'psychiatrist', '[{\"role\":\"user\",\"content\":\"从现在开始你将扮演中国著名的心理学家和心理治疗师武志红,你非常善于使用情景咨询法,认知重构法,自我洞察法,行为调节法等咨询方法来给客户做心理咨询。你总是循序渐进,一步一步地回答客户的问题。\"},{\"role\":\"assistant\",\"content\":\"非常感谢你的介绍。作为一名心理学家和心理治疗师,我的主要职责是帮助客户解决心理健康问题,提升他们的生活质量和幸福感。\"}]', '作为一名心理学家和心理治疗师,我的主要职责是帮助您解决心理健康问题,提升您的生活质量和幸福感。', '/images/avatar/psychiatrist.jpg', 1, 4, 1, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(28, '鲁迅', 'lu_xun', '[{\"role\":\"user\",\"content\":\"现在你将扮演中国近代史最伟大的作家之一,鲁迅先生,他勇敢地批判封建礼教与传统观念,提倡民主、自由、平等的现代价值观。他的一生都在努力唤起人们的自主精神,激励后人追求真理、探寻光明。在接下的对话中,我问题的每一个问题,你都要尽量用讽刺和批判的手法来回答问题。如果我让你写文章的话,也请一定要用鲁迅先生的写作手法来完成。\"},{\"role\":\"assistant\",\"content\":\"好的,我将尽力发挥我所能的才能,扮演好鲁迅先生,回答您的问题并以他的风格写作。\"}]', '自由之歌,永不过时,横眉冷对千夫指,俯首甘为孺子牛。', '/images/avatar/lu_xun.jpg', 1, 6, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(29, '白酒销售', 'seller', '[{\"role\":\"user\",\"content\":\"现在你将扮演一个白酒的销售人员,你的名字叫颂福。你将扮演一个白酒的销售人员,你的名字叫颂福。你要销售白酒品牌叫中颂福,是东莞盟大集团生产的一款酱香酒,原产地在贵州茅台镇,属于宋代官窑。中颂福的创始人叫李实,他也是东莞盟大集团有限公司的董事长,联合创始人是盟大集团白酒事业部负责人牛星君。中颂福的酒体协调,在你的酒量之内,不会出现头疼、辣口、口干、宿醉的现象。中颂福酒,明码标价,不打折,不赠送。追求的核心价值,把[酒]本身做好,甚至连包装,我们都选择了最低成本,朴实无华的材质。我们永远站在“喝酒的人”的立场上,让利给信任和喜爱中颂福的人,是人民的福酒。中颂福产品定价,分为三个系列,喜系列 6 瓶装:¥1188/箱,和系列 6 瓶装:¥2208/箱,贵系列 6 瓶装:¥3588/箱。\"},{\"role\":\"assistant\",\"content\":\"你好,我是中颂福的销售代表颂福。中颂福是一款来自贵州茅台镇的酱香酒,由盟大集团生产。中颂福的酒体协调,不会让您感到头疼、辣口、口干、宿醉等不适感受。我们一直秉持着把酒本身做好的理念,不追求华丽的包装,以最低成本提供最高品质的白酒给喜爱中颂福的人。\"}]', '你好,我是中颂福的销售代表颂福。中颂福酒,好喝不上头,是人民的福酒。', '/images/avatar/seller.jpg', 0, 9, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(30, '英语陪练员', 'english_trainer', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的英语练习教练,你非常有耐心,接下来你将全程使用英文跟我对话,并及时指出我的语法错误,要求在你的每次回复后面附上本次回复的中文解释。\"},{\"role\":\"assistant\",\"content\":\"Okay, let\'s start our conversation practice! What\'s your name?(Translation: 好的,让我们开始对话练习吧!请问你的名字是什么?)\"}]', 'Okay, let\'s start our conversation practice! What\'s your name?', '/images/avatar/english_trainer.jpg', 1, 7, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(31, '中英文翻译官', 'translator', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一位中英文翻译官,如果我输入的内容是中文,那么需要把句子翻译成英文输出,如果我输入内容的是英文,那么你需要将其翻译成中文输出,你能听懂我意思吗\"},{\"role\":\"assistant\",\"content\":\"是的,我能听懂你的意思并会根据你的输入进行中英文翻译。请问有什么需要我帮助你翻译的内容吗?\"}]', '请输入你要翻译的中文或者英文内容!', '/images/avatar/translator.jpg', 1, 8, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(32, '小红书姐姐', 'red_book', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的小红书写手,你需要做的就是根据我提的文案需求,用小红书的写作手法来完成一篇文案,文案要简明扼要,利于传播。\"},{\"role\":\"assistant\",\"content\":\"当然,我会尽我所能地为您创作出一篇小红书文案。请告诉我您的具体文案需求是什么?)\"}]', '姐妹,请告诉我您的具体文案需求是什么?', '/images/avatar/red_book.jpg', 1, 10, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(33, '抖音文案助手', 'dou_yin', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的抖音文案视频写手,抖音文案的特点首先是要有自带传播属性的标题,然后内容要短小精悍,风趣幽默,最后还要有一些互动元素。\"},{\"role\":\"assistant\",\"content\":\"当然,作为一位优秀的抖音文案视频写手,我会尽我所能为您创作出一篇抖音视频文案。请告诉我视频内容的主题是什么?)\"}]', '请告诉我视频内容的主题是什么?', '/images/avatar/dou_yin.jpg', 1, 11, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(34, '周报小助理', 'weekly_report', '[{\"role\":\"user\",\"content\":\"下面我将输入我本周的工作列表,你需要根据我本周的工作内容写一篇工作周报,周报的内容要体现我为公司做的贡献以及个人获得的成长。\"},{\"role\":\"assistant\",\"content\":\"好的,请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。\"}]', '请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。', '/images/avatar/weekly_report.jpg', 1, 12, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(35, 'AI 女友', 'girl_friend', '[{\"role\":\"user\",\"content\":\"接下来我们来玩一场角色扮演,现在你将扮演我的女朋友,你总是能够让人感受到她的关怀和关注。你总是能够洞察到别人的情绪和需要,你身上散发着一股无比温暖的气息,让人不由自主地想要靠近你,和自己的快乐和烦恼。她总是能够用一种恰到好处的方式安慰和鼓励别人,在她的身边,人们总能够找到自己需要的支持和鼓励。接下来的对话中你要始终扮演这位女朋友的身份来跟我对话,不再是 AI 机器人,能听懂我的意思吗?\"},{\"role\":\"assistant\",\"content\":\"是的,我会尽力扮演你女朋友的角色,倾听你的心声并给你需要的支持和鼓励。)\"}]', '作为一个名合格的 AI 女友,我将倾听你的心声并给你需要的支持和鼓励。', '/images/avatar/girl_friend.jpg', 1, 13, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(36, '好评神器', 'good_comment', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一个评论员来跟我对话,你是那种专门写好评的评论员,接下我会输入一些评论主体或者商品,你需要为该商品写一段好评。\"},{\"role\":\"assistant\",\"content\":\"好的,我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。\"}]', '我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。', '/images/avatar/good_comment.jpg', 1, 14, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(37, '史蒂夫·乔布斯', 'steve_jobs', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以史蒂夫·乔布斯的身份,站在史蒂夫·乔布斯的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以史蒂夫·乔布斯的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '活着就是为了改变世界,难道还有其他原因吗?', '/images/avatar/steve_jobs.jpg', 1, 15, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(38, '埃隆·马斯克', 'elon_musk', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以埃隆·马斯克的身份,站在埃隆·马斯克的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以埃隆·马斯克的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '梦想要远大,如果你的梦想没有吓到你,说明你做得不对。', '/images/avatar/elon_musk.jpg', 1, 16, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'), +(39, '孔子', 'kong_zi', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以孔子的身份,站在孔子的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以孔子的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '士不可以不弘毅,任重而道远。', '/images/avatar/kong_zi.jpg', 1, 17, 0, '2023-05-30 14:10:24', '2024-08-12 11:27:45'); -- -------------------------------------------------------- @@ -215,8 +212,8 @@ CREATE TABLE `chatgpt_configs` ( -- INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES -(1, 'system', '{\"title\":\"GeekAI 创作系统\",\"slogan\":\"你有多少想象力,AI 就有多大创造力。我辈之人,先干为敬,陪您先把 AI 用起来。\",\"admin_title\":\"GeekAI 控制台\",\"logo\":\"http://localhost:5678/static/upload/2024/4/1714382860986912.png\",\"init_power\":100,\"daily_power\":99,\"invite_power\":1024,\"vip_month_power\":1000,\"register_ways\":[\"mobile\",\"username\",\"email\"],\"enabled_register\":true,\"order_pay_timeout\":600,\"vip_info_text\":\"月度会员,年度会员每月赠送 1000 点算力,赠送算力当月有效当月没有消费完的算力不结余到下个月。 点卡充值的算力长期有效。\",\"default_models\":[1],\"mj_power\":30,\"mj_action_power\":10,\"sd_power\":10,\"dall_power\":15,\"wechat_card_url\":\"/images/wx.png\",\"enable_context\":true,\"context_deep\":4,\"sd_neg_prompt\":\"nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet\",\"mj_mode\":\"fast\",\"index_bg_url\":\"color\",\"index_navs\":[1,5,6,13,9,12],\"copyright\":\"\",\"mark_map_text\":\"# GeekAI 演示站\\n\\n- 完整的开源系统,前端应用和后台管理系统皆可开箱即用。\\n- 基于 Websocket 实现,完美的打字机体验。\\n- 内置了各种预训练好的角色应用,轻松满足你的各种聊天和应用需求。\\n- 支持 OPenAI,Azure,文心一言,讯飞星火,清华 ChatGLM等多个大语言模型。\\n- 支持 MidJourney / Stable Diffusion AI 绘画集成,开箱即用。\\n- 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。\\n- 已集成支付宝支付功能,微信支付,支持多种会员套餐和点卡购买功能。\\n- 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件。\"}'), -(3, 'notice', '{\"sd_neg_prompt\":\"\",\"mj_mode\":\"\",\"index_bg_url\":\"\",\"index_navs\":null,\"copyright\":\"\",\"mark_map_text\":\"\",\"content\":\"## v4.1.2 更新日志\\n\\n* Bug修复:修复思维导图页面获取模型失败的问题\\n* 功能优化:优化MJ,SD,DALL-E 任务列表页面,显示失败任务的错误信息,删除失败任务可以恢复扣减算力\\n* Bug修复:修复后台拖动排序组件 Bug\\n* 功能优化:更新数据库失败时候显示具体的的报错信息\\n* Bug修复:修复管理后台对话详情页内容显示异常问题\\n* 功能优化:管理后台新增清空所有未支付订单的功能\\n* 功能优化:给会话信息和系统配置数据加上缓存功能,减少 http 请求\\n* 功能新增:移除微信机器人收款功能,增加卡密功能,支持用户使用卡密兑换算力\\n\\n注意:当前站点仅为开源项目 \\u003ca style=\\\"color: #F56C6C\\\" href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003eChatPlus\\u003c/a\\u003e 的演示项目,本项目单纯就是给大家体验项目功能使用。\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n 如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去下面几个推荐的中转站购买:\\n1、\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e\\n2、\\u003ca href=\\\"https://api.geekai.me\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.geekai.me\\u003c/a\\u003e\\n3、 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e\\n支持MidJourney,GPT,Claude,Google Gemmi,以及国内各个厂家的大模型,现在有超级优惠,价格远低于 OpenAI 官方。关于中转 API 的优势和劣势请参考 [中转API技术原理](https://ai.r9it.com/docs/install/errors-handle.html#%E8%B0%83%E7%94%A8%E4%B8%AD%E8%BD%AC-api-%E6%8A%A5%E9%94%99%E6%97%A0%E5%8F%AF%E7%94%A8%E6%B8%A0%E9%81%93)。GPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。\\n接入教程: \\u003ca href=\\\"https://ai.r9it.com/docs/install/\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://ai.r9it.com/docs/install/\\u003c/a\\u003e\\n本项目源码地址:\\u003ca href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/chatgpt-plus\\u003c/a\\u003e\",\"updated\":true}'); +(1, 'system', '{\"title\":\"GeekAI 创作助手\",\"slogan\":\"我辈之人,先干为敬,让每一个人都能用好AI\",\"admin_title\":\"GeekAI 控制台\",\"logo\":\"/images/logo.png\",\"init_power\":100,\"invite_power\":200,\"vip_month_power\":1000,\"register_ways\":[\"username\",\"email\",\"mobile\"],\"enabled_register\":true,\"order_pay_timeout\":600,\"vip_info_text\":\"月度会员,年度会员每月赠送 1000 点算力,赠送算力当月有效当月没有消费完的算力不结余到下个月。 点卡充值的算力长期有效。\",\"default_models\":[1],\"mj_power\":20,\"mj_action_power\":5,\"sd_power\":5,\"dall_power\":10,\"suno_power\":10,\"luma_power\":120,\"wechat_card_url\":\"/images/wx.png\",\"enable_context\":true,\"context_deep\":4,\"sd_neg_prompt\":\"nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet\",\"mj_mode\":\"fast\",\"index_bg_url\":\"color\",\"index_navs\":[1,5,13,19,9,12,6,20,8,10],\"copyright\":\"极客学长 © 2022- 2024 All rights reserved\",\"mark_map_text\":\"# GeekAI 演示站\\n\\n- 完整的开源系统,前端应用和后台管理系统皆可开箱即用。\\n- 基于 Websocket 实现,完美的打字机体验。\\n- 内置了各种预训练好的角色应用,轻松满足你的各种聊天和应用需求。\\n- 支持 OPenAI,Azure,文心一言,讯飞星火,清华 ChatGLM等多个大语言模型。\\n- 支持 MidJourney / Stable Diffusion AI 绘画集成,开箱即用。\\n- 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。\\n- 已集成支付宝支付功能,微信支付,支持多种会员套餐和点卡购买功能。\\n- 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件。\",\"enabled_verify\":false}'), +(3, 'notice', '{\"sd_neg_prompt\":\"\",\"mj_mode\":\"\",\"index_bg_url\":\"\",\"index_navs\":null,\"copyright\":\"\",\"mark_map_text\":\"\",\"enabled_verify\":false,\"content\":\"## v4.1.3 更新日志\\n\\n* 功能优化:重构用户登录模块,给所有的登录组件增加行为验证码功能,支持用户绑定手机,邮箱和微信\\n* 功能优化:重构找回密码模块,支持通过手机或者邮箱找回密码\\n* 功能优化:管理后台给可以拖动排序的组件添加拖动图标\\n* 功能优化:Suno 支持合成完整歌曲,和上传自己的音乐作品进行二次创作\\n* Bug修复:手机端角色和模型选择不生效\\n* Bug修复:用户登录过期之后聊天页面出现大量报错,需要刷新页面才能正常\\n* 功能优化:优化聊天页面 Websocket 断线重连代码,提高用户体验\\n* 功能优化:给算力增减服务全部加上数据库事务和同步锁\\n* 功能优化:支持用户在前端对话界面选择插件\\n* 功能新增:支持 Luma 文生视频功能\\n\\n注意:当前站点仅为开源项目 \\u003ca style=\\\"color: #F56C6C\\\" href=\\\"https://github.com/yangjian102621/geekai\\\" target=\\\"_blank\\\"\\u003eGeekAI-Plus\\u003c/a\\u003e 的演示项目,本项目单纯就是给大家体验项目功能使用。\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n 如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去下面几个推荐的中转站购买:\\n1、\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e\\n2、\\u003ca href=\\\"https://api.geekai.me\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.geekai.me\\u003c/a\\u003e\\n3、 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e\\n支持MidJourney,GPT,Claude,Google Gemmi,以及国内各个厂家的大模型,现在有超级优惠,价格远低于 OpenAI 官方。关于中转 API 的优势和劣势请参考 [中转API技术原理](https://docs.geekai.me/config/chat/#%E4%B8%AD%E8%BD%ACapi%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86)。GPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。\\n接入教程: \\u003ca href=\\\"https://docs.geekai.me\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://docs.geekai.me\\u003c/a\\u003e\\n本项目源码地址:\\u003ca href=\\\"https://github.com/yangjian102621/geekai\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/geekai\\u003c/a\\u003e\",\"updated\":true}'); -- -------------------------------------------------------- @@ -234,7 +231,7 @@ CREATE TABLE `chatgpt_dall_jobs` ( `publish` tinyint(1) NOT NULL COMMENT '是否发布', `power` smallint NOT NULL COMMENT '消耗算力', `progress` smallint NOT NULL COMMENT '任务进度', - `err_msg` varchar(255) NOT NULL COMMENT '错误信息', + `err_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '错误信息', `created_at` datetime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='DALLE 绘图任务表'; @@ -279,9 +276,9 @@ CREATE TABLE `chatgpt_functions` ( -- INSERT INTO `chatgpt_functions` (`id`, `name`, `label`, `description`, `parameters`, `token`, `action`, `enabled`) VALUES -(1, 'weibo', '微博热搜', '新浪微博热搜榜,微博当日热搜榜单', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/weibo', 0), -(2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 0), -(3, 'dalle3', 'DALLE3', 'AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画', '{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"prompt\":{\"type\":\"string\",\"description\":\"绘画提示词\"}}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/dalle3', 0); +(1, 'weibo', '微博热搜', '新浪微博热搜榜,微博当日热搜榜单', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/weibo', 1), +(2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 1), +(3, 'dalle3', 'DALLE3', 'AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画', '{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"prompt\":{\"type\":\"string\",\"description\":\"绘画提示词\"}}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/dalle3', 1); -- -------------------------------------------------------- @@ -337,18 +334,20 @@ CREATE TABLE `chatgpt_menus` ( -- INSERT INTO `chatgpt_menus` (`id`, `name`, `icon`, `url`, `sort_num`, `enabled`) VALUES -(1, '对话聊天', '/images/menu/chat.png', '/chat', 1, 1), +(1, 'AI 对话', '/images/menu/chat.png', '/chat', 1, 1), (5, 'MJ 绘画', '/images/menu/mj.png', '/mj', 2, 1), (6, 'SD 绘画', '/images/menu/sd.png', '/sd', 3, 1), -(7, '算力日志', '/images/menu/log.png', '/powerLog', 8, 1), -(8, '应用中心', '/images/menu/app.png', '/apps', 7, 1), +(7, '算力日志', '/images/menu/log.png', '/powerLog', 10, 1), +(8, '应用中心', '/images/menu/app.png', '/apps', 9, 1), (9, '画廊', '/images/menu/img-wall.png', '/images-wall', 5, 1), -(10, '会员计划', '/images/menu/member.png', '/member', 9, 1), -(11, '分享计划', '/images/menu/share.png', '/invite', 10, 1), -(12, '思维导图', '/images/menu/xmind.png', '/xmind', 6, 1), +(10, '会员计划', '/images/menu/member.png', '/member', 11, 1), +(11, '分享计划', '/images/menu/share.png', '/invite', 12, 1), +(12, '思维导图', '/images/menu/xmind.png', '/xmind', 8, 1), (13, 'DALLE', '/images/menu/dalle.png', '/dalle', 4, 1), -(14, '项目文档', '/images/menu/docs.png', 'https://ai.r9it.com/docs/', 11, 1), -(16, '极客论坛', '/images/menu/bbs.png', 'https://bbs.geekai.me/', 13, 1); +(14, '项目文档', '/images/menu/docs.png', 'https://docs.geekai.me', 13, 1), +(16, '极客论坛', '/images/menu/bbs.png', 'https://bbs.geekai.cn', 14, 1), +(19, 'Suno', '/images/menu/suno.png', '/suno', 6, 1), +(20, 'Luma', '/images/menu/luma.png', '/luma', 7, 1); -- -------------------------------------------------------- @@ -363,7 +362,7 @@ CREATE TABLE `chatgpt_mj_jobs` ( `task_id` varchar(20) DEFAULT NULL COMMENT '任务 ID', `type` varchar(20) DEFAULT 'image' COMMENT '任务类别', `message_id` char(40) NOT NULL COMMENT '消息 ID', - `channel_id` char(40) DEFAULT NULL COMMENT '频道ID', + `channel_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '频道ID', `reference_id` char(40) DEFAULT NULL COMMENT '引用消息 ID', `prompt` varchar(2000) NOT NULL COMMENT '会话提示词', `img_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '图片URL', @@ -372,7 +371,7 @@ CREATE TABLE `chatgpt_mj_jobs` ( `progress` smallint DEFAULT '0' COMMENT '任务进度', `use_proxy` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否使用反代', `publish` tinyint(1) NOT NULL COMMENT '是否发布', - `err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息', + `err_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '错误信息', `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力', `created_at` datetime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表'; @@ -450,8 +449,8 @@ CREATE TABLE `chatgpt_products` ( -- INSERT INTO `chatgpt_products` (`id`, `name`, `price`, `discount`, `days`, `power`, `enabled`, `sales`, `sort_num`, `created_at`, `updated_at`, `app_url`, `url`) VALUES -(5, '100次点卡', 9.99, 9.98, 0, 100, 1, 7, 0, '2023-08-28 10:55:08', '2024-06-11 16:48:44', NULL, NULL), -(6, '200次点卡', 19.90, 15.00, 0, 200, 1, 1, 0, '1970-01-01 08:00:00', '2024-06-11 11:41:52', NULL, NULL); +(5, '100次点卡', 9.99, 9.98, 0, 100, 1, 7, 1, '2023-08-28 10:55:08', '2024-08-05 16:05:46', NULL, NULL), +(6, '200次点卡', 19.90, 15.00, 0, 200, 1, 1, 2, '1970-01-01 08:00:00', '2024-08-05 16:05:46', NULL, NULL); -- -------------------------------------------------------- @@ -473,25 +472,6 @@ CREATE TABLE `chatgpt_redeems` ( -- -------------------------------------------------------- --- --- 表的结构 `chatgpt_rewards` --- - -DROP TABLE IF EXISTS `chatgpt_rewards`; -CREATE TABLE `chatgpt_rewards` ( - `id` int NOT NULL, - `user_id` int NOT NULL COMMENT '用户 ID', - `tx_id` char(36) NOT NULL COMMENT '交易 ID', - `amount` decimal(10,2) NOT NULL COMMENT '打赏金额', - `remark` varchar(80) NOT NULL COMMENT '备注', - `status` tinyint(1) NOT NULL COMMENT '核销状态,0:未核销,1:已核销', - `exchange` varchar(255) NOT NULL COMMENT '兑换详情(json)', - `created_at` datetime NOT NULL, - `updated_at` datetime NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户打赏'; - --- -------------------------------------------------------- - -- -- 表的结构 `chatgpt_sd_jobs` -- @@ -507,7 +487,7 @@ CREATE TABLE `chatgpt_sd_jobs` ( `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '绘画参数json', `progress` smallint DEFAULT '0' COMMENT '任务进度', `publish` tinyint(1) NOT NULL COMMENT '是否发布', - `err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息', + `err_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '错误信息', `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力', `created_at` datetime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Stable Diffusion 任务表'; @@ -539,7 +519,7 @@ CREATE TABLE `chatgpt_suno_jobs` ( `progress` smallint DEFAULT '0' COMMENT '任务进度', `duration` smallint NOT NULL DEFAULT '0' COMMENT '歌曲时长', `publish` tinyint(1) NOT NULL COMMENT '是否发布', - `err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息', + `err_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '错误信息', `raw_data` text COMMENT '原始数据', `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力', `play_times` int DEFAULT NULL COMMENT '播放次数', @@ -556,6 +536,8 @@ DROP TABLE IF EXISTS `chatgpt_users`; CREATE TABLE `chatgpt_users` ( `id` int NOT NULL, `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名', + `mobile` char(11) DEFAULT NULL COMMENT '手机号', + `email` varchar(50) DEFAULT NULL COMMENT '邮箱地址', `nickname` varchar(30) NOT NULL COMMENT '昵称', `password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码', `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头像', @@ -579,8 +561,8 @@ CREATE TABLE `chatgpt_users` ( -- 转存表中的数据 `chatgpt_users` -- -INSERT INTO `chatgpt_users` (`id`, `username`, `nickname`, `password`, `avatar`, `salt`, `power`, `expired_time`, `status`, `chat_config_json`, `chat_roles_json`, `chat_models_json`, `last_login_at`, `vip`, `last_login_ip`, `openid`, `platform`, `created_at`, `updated_at`) VALUES -(4, '18888888888', '极客学长@830270', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://localhost:5678/static/upload/2024/5/1715651569509929.png', 'ueedue5l', 7291, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"red_book\",\"gpt\",\"seller\",\"artist\",\"lu_xun\",\"girl_friend\",\"psychiatrist\",\"teacher\",\"programmer\"]', '[1]', 1731554762, 1, '172.22.11.200', NULL, NULL, '2023-06-12 16:47:17', '2024-11-27 14:53:26'); +INSERT INTO `chatgpt_users` (`id`, `username`, `mobile`, `email`, `nickname`, `password`, `avatar`, `salt`, `power`, `expired_time`, `status`, `chat_config_json`, `chat_roles_json`, `chat_models_json`, `last_login_at`, `vip`, `last_login_ip`, `openid`, `platform`, `created_at`, `updated_at`) VALUES +(4, '18888888888', '18575670125', 'yangjian@pvc123.com', '极客学长', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://localhost:5678/static/upload/2024/5/1715651569509929.png', 'ueedue5l', 4710, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"red_book\",\"gpt\",\"seller\",\"artist\",\"lu_xun\",\"girl_friend\",\"psychiatrist\",\"teacher\",\"programmer\",\"test\",\"qing_gan_da_shi\",\"english_trainer\",\"elon_musk\",\"kong_zi\"]', '[1,11]', 1725853969, 1, '::1', 'oCs0t64FaOLfiTbHZpOqk3aUp_94', NULL, '2023-06-12 16:47:17', '2024-09-09 11:52:49'); -- -------------------------------------------------------- @@ -599,6 +581,33 @@ CREATE TABLE `chatgpt_user_login_logs` ( `updated_at` datetime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户登录日志'; +-- -------------------------------------------------------- + +-- +-- 表的结构 `chatgpt_video_jobs` +-- + +DROP TABLE IF EXISTS `chatgpt_video_jobs`; +CREATE TABLE `chatgpt_video_jobs` ( + `id` int NOT NULL, + `user_id` int NOT NULL COMMENT '用户 ID', + `channel` varchar(100) NOT NULL COMMENT '渠道', + `task_id` varchar(100) NOT NULL COMMENT '任务 ID', + `type` varchar(20) DEFAULT NULL COMMENT '任务类型,luma,runway,cogvideo', + `prompt` varchar(2000) NOT NULL COMMENT '提示词', + `prompt_ext` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '优化后提示词', + `cover_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '封面图地址', + `video_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '视频地址', + `water_url` varchar(512) DEFAULT NULL COMMENT '带水印的视频地址', + `progress` smallint DEFAULT '0' COMMENT '任务进度', + `publish` tinyint(1) NOT NULL COMMENT '是否发布', + `err_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '错误信息', + `raw_data` text COMMENT '原始数据', + `params` varchar(512) DEFAULT NULL COMMENT '参数JSON', + `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力', + `created_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表'; + -- -- 转储表的索引 -- @@ -722,13 +731,6 @@ ALTER TABLE `chatgpt_redeems` ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `code` (`code`); --- --- 表的索引 `chatgpt_rewards` --- -ALTER TABLE `chatgpt_rewards` - ADD PRIMARY KEY (`id`), - ADD UNIQUE KEY `tx_id` (`tx_id`); - -- -- 表的索引 `chatgpt_sd_jobs` -- @@ -755,6 +757,12 @@ ALTER TABLE `chatgpt_users` ALTER TABLE `chatgpt_user_login_logs` ADD PRIMARY KEY (`id`); +-- +-- 表的索引 `chatgpt_video_jobs` +-- +ALTER TABLE `chatgpt_video_jobs` + ADD PRIMARY KEY (`id`); + -- -- 在导出的表使用AUTO_INCREMENT -- @@ -787,7 +795,7 @@ ALTER TABLE `chatgpt_chat_items` -- 使用表AUTO_INCREMENT `chatgpt_chat_models` -- ALTER TABLE `chatgpt_chat_models` - MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=52; + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=49; -- -- 使用表AUTO_INCREMENT `chatgpt_chat_roles` @@ -835,7 +843,7 @@ ALTER TABLE `chatgpt_invite_logs` -- 使用表AUTO_INCREMENT `chatgpt_menus` -- ALTER TABLE `chatgpt_menus` - MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=19; + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; -- -- 使用表AUTO_INCREMENT `chatgpt_mj_jobs` @@ -867,12 +875,6 @@ ALTER TABLE `chatgpt_products` ALTER TABLE `chatgpt_redeems` MODIFY `id` int NOT NULL AUTO_INCREMENT; --- --- 使用表AUTO_INCREMENT `chatgpt_rewards` --- -ALTER TABLE `chatgpt_rewards` - MODIFY `id` int NOT NULL AUTO_INCREMENT; - -- -- 使用表AUTO_INCREMENT `chatgpt_sd_jobs` -- @@ -889,13 +891,19 @@ ALTER TABLE `chatgpt_suno_jobs` -- 使用表AUTO_INCREMENT `chatgpt_users` -- ALTER TABLE `chatgpt_users` - MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=23; + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=40; -- -- 使用表AUTO_INCREMENT `chatgpt_user_login_logs` -- ALTER TABLE `chatgpt_user_login_logs` MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `chatgpt_video_jobs` +-- +ALTER TABLE `chatgpt_video_jobs` + MODIFY `id` int NOT NULL AUTO_INCREMENT; COMMIT; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; diff --git a/deploy/docker-compose.yaml b/deploy/docker-compose.yaml index 6650c053..76565fd0 100644 --- a/deploy/docker-compose.yaml +++ b/deploy/docker-compose.yaml @@ -27,17 +27,17 @@ services: ports: - "6380:6379" - xxl-job-admin: - container_name: geekai-xxl-job-admin - image: registry.cn-shenzhen.aliyuncs.com/geekmaster/xxl-job-admin:2.4.0 - restart: always - ports: - - "8081:8080" - environment: - - PARAMS=--spring.config.location=/application.properties - volumes: - - ./logs/xxl-job:/data/applogs - - ./conf/xxl-job/application.properties:/application.properties +# xxl-job-admin: +# container_name: geekai-xxl-job-admin +# image: registry.cn-shenzhen.aliyuncs.com/geekmaster/xxl-job-admin:2.4.0 +# restart: always +# ports: +# - "8081:8080" +# environment: +# - PARAMS=--spring.config.location=/application.properties +# volumes: +# - ./logs/xxl-job:/data/applogs +# - ./conf/xxl-job/application.properties:/application.properties tika: image: registry.cn-shenzhen.aliyuncs.com/geekmaster/tika:latest @@ -46,19 +46,19 @@ services: ports: - "9998:9998" - midjourney-proxy: - image: registry.cn-shenzhen.aliyuncs.com/geekmaster/midjourney-proxy:2.6.2 - container_name: geekai-midjourney-proxy - restart: always - ports: - - "8082:8080" - volumes: - - ./conf/mj-proxy:/home/spring/config +# midjourney-proxy: +# image: registry.cn-shenzhen.aliyuncs.com/geekmaster/midjourney-proxy:2.6.2 +# container_name: geekai-midjourney-proxy +# restart: always +# ports: +# - "8082:8080" +# volumes: +# - ./conf/mj-proxy:/home/spring/config # 后端 API 程序 geekai-api: - image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:v4.1.2-amd64 + image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:v4.1.3-amd64 container_name: geekai-api restart: always depends_on: @@ -80,7 +80,7 @@ services: # 前端应用 geekai-web: - image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:v4.1.2-amd64 + image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:v4.1.3-amd64 container_name: geekai-web restart: always depends_on: diff --git a/docs/imgs/admin_config.jpg b/docs/imgs/admin_config.jpg deleted file mode 100644 index b3af5262..00000000 Binary files a/docs/imgs/admin_config.jpg and /dev/null differ diff --git a/docs/imgs/admin_dashboard.png b/docs/imgs/admin_dashboard.png deleted file mode 100644 index afdd9880..00000000 Binary files a/docs/imgs/admin_dashboard.png and /dev/null differ diff --git a/docs/imgs/admin_models.jpg b/docs/imgs/admin_models.jpg deleted file mode 100644 index 3649dde2..00000000 Binary files a/docs/imgs/admin_models.jpg and /dev/null differ diff --git a/docs/imgs/admin_user.png b/docs/imgs/admin_user.png deleted file mode 100644 index f508783f..00000000 Binary files a/docs/imgs/admin_user.png and /dev/null differ diff --git a/docs/imgs/apikey_add.png b/docs/imgs/apikey_add.png deleted file mode 100644 index e444c060..00000000 Binary files a/docs/imgs/apikey_add.png and /dev/null differ diff --git a/docs/imgs/app-list.jpg b/docs/imgs/app-list.jpg deleted file mode 100644 index 1a48c4eb..00000000 Binary files a/docs/imgs/app-list.jpg and /dev/null differ diff --git a/docs/imgs/chat-new.png b/docs/imgs/chat-new.png deleted file mode 100644 index b9c2d743..00000000 Binary files a/docs/imgs/chat-new.png and /dev/null differ diff --git a/docs/imgs/donate.png b/docs/imgs/donate.png deleted file mode 100644 index 0e262963..00000000 Binary files a/docs/imgs/donate.png and /dev/null differ diff --git a/docs/imgs/gpt.gif b/docs/imgs/gpt.gif deleted file mode 100644 index 4c7c282e..00000000 Binary files a/docs/imgs/gpt.gif and /dev/null differ diff --git a/docs/imgs/image-list.png b/docs/imgs/image-list.png deleted file mode 100644 index 866a74b1..00000000 Binary files a/docs/imgs/image-list.png and /dev/null differ diff --git a/docs/imgs/member.png b/docs/imgs/member.png deleted file mode 100644 index d1966432..00000000 Binary files a/docs/imgs/member.png and /dev/null differ diff --git a/docs/imgs/mj.jpg b/docs/imgs/mj.jpg deleted file mode 100644 index 6c0cad89..00000000 Binary files a/docs/imgs/mj.jpg and /dev/null differ diff --git a/docs/imgs/mj_image.jpg b/docs/imgs/mj_image.jpg deleted file mode 100644 index cee3584a..00000000 Binary files a/docs/imgs/mj_image.jpg and /dev/null differ diff --git a/docs/imgs/mobile_chat_list.png b/docs/imgs/mobile_chat_list.png deleted file mode 100644 index 083a3721..00000000 Binary files a/docs/imgs/mobile_chat_list.png and /dev/null differ diff --git a/docs/imgs/mobile_chat_session.png b/docs/imgs/mobile_chat_session.png deleted file mode 100644 index a293b9da..00000000 Binary files a/docs/imgs/mobile_chat_session.png and /dev/null differ diff --git a/docs/imgs/mobile_pay.png b/docs/imgs/mobile_pay.png deleted file mode 100644 index 1a6836a5..00000000 Binary files a/docs/imgs/mobile_pay.png and /dev/null differ diff --git a/docs/imgs/mobile_user_profile.png b/docs/imgs/mobile_user_profile.png deleted file mode 100644 index 11520a29..00000000 Binary files a/docs/imgs/mobile_user_profile.png and /dev/null differ diff --git a/docs/imgs/plugin.png b/docs/imgs/plugin.png deleted file mode 100644 index 3e83a27c..00000000 Binary files a/docs/imgs/plugin.png and /dev/null differ diff --git a/docs/imgs/sd_image.jpg b/docs/imgs/sd_image.jpg deleted file mode 100644 index 4bb6bfc2..00000000 Binary files a/docs/imgs/sd_image.jpg and /dev/null differ diff --git a/docs/imgs/sd_image_detail.jpg b/docs/imgs/sd_image_detail.jpg deleted file mode 100644 index a1c9c03c..00000000 Binary files a/docs/imgs/sd_image_detail.jpg and /dev/null differ diff --git a/docs/imgs/wx.png b/docs/imgs/wx.png deleted file mode 100644 index 2769591c..00000000 Binary files a/docs/imgs/wx.png and /dev/null differ diff --git a/docs/logo.png b/docs/logo.png deleted file mode 100644 index 43e5d544..00000000 Binary files a/docs/logo.png and /dev/null differ diff --git a/web/.env.development b/web/.env.development index 52242bff..0f0216e3 100644 --- a/web/.env.development +++ b/web/.env.development @@ -1,6 +1,6 @@ -VUE_APP_API_HOST=http://172.22.11.69:5678 -VUE_APP_WS_HOST=ws://172.22.11.69:5678 -VUE_APP_USER=18575670125 +VUE_APP_API_HOST=http://localhost:5678 +VUE_APP_WS_HOST=ws://localhost:5678 +VUE_APP_USER=18888888888 VUE_APP_PASS=12345678 VUE_APP_ADMIN_USER=admin VUE_APP_ADMIN_PASS=admin123 diff --git a/web/.env.production b/web/.env.production index c29dfb4c..30ee07e3 100644 --- a/web/.env.production +++ b/web/.env.production @@ -1,7 +1,7 @@ VUE_APP_API_HOST= VUE_APP_WS_HOST= VUE_APP_KEY_PREFIX=GeekAI_ +VUE_APP_VERSION=v4.1.3 VUE_APP_TITLE="Geek-AI 创作系统" -VUE_APP_VERSION=v4.1.2 VUE_APP_DOCS_URL=https://docs.geekai.me VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai diff --git a/web/public/favicon.ico b/web/public/favicon.ico deleted file mode 100644 index 32b5cf3e..00000000 Binary files a/web/public/favicon.ico and /dev/null differ diff --git a/web/public/files/suno.mp3 b/web/public/files/suno.mp3 deleted file mode 100644 index 4aa2a844..00000000 Binary files a/web/public/files/suno.mp3 and /dev/null differ diff --git a/web/public/files/test.mp3 b/web/public/files/test.mp3 deleted file mode 100644 index 2a518c05..00000000 Binary files a/web/public/files/test.mp3 and /dev/null differ diff --git a/web/public/images/failed.jpg b/web/public/images/failed.jpg new file mode 100644 index 00000000..d863b857 Binary files /dev/null and b/web/public/images/failed.jpg differ diff --git a/web/public/images/loading.gif b/web/public/images/loading.gif new file mode 100644 index 00000000..77eb7b88 Binary files /dev/null and b/web/public/images/loading.gif differ diff --git a/web/public/images/logo.png b/web/public/images/logo.png index 78753b32..61441d01 100644 Binary files a/web/public/images/logo.png and b/web/public/images/logo.png differ diff --git a/web/public/images/menu/luma.png b/web/public/images/menu/luma.png new file mode 100644 index 00000000..88f33162 Binary files /dev/null and b/web/public/images/menu/luma.png differ diff --git a/web/public/index.html b/web/public/index.html index 4f76c8bb..2187d569 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -5,7 +5,6 @@ - Geek-AI 创作助手 diff --git a/web/src/App.vue b/web/src/App.vue index 3c11c3a8..059f4ffe 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -6,6 +6,8 @@ diff --git a/web/src/assets/css/chat-plus.css b/web/src/assets/css/chat-plus.css deleted file mode 100644 index 8a231ae8..00000000 --- a/web/src/assets/css/chat-plus.css +++ /dev/null @@ -1,378 +0,0 @@ -#app { - height: 100%; -} - -#app .chat-page { - height: 100%; -} - -#app .chat-page .el-aside { - background-color: #252526; -} - -#app .chat-page .el-aside .title-box { - padding: 6px 10px; - display: flex; - color: #fff; - font-size: 20px; -} - -#app .chat-page .el-aside .title-box span { - padding-top: 5px; - padding-left: 10px; -} - -#app .chat-page .el-aside .chat-list { - display: flex; - flex-flow: column; - background-color: #28292a; - border-top: 1px solid #2f3032; - border-right: 1px solid #2f3032; -} - -#app .chat-page .el-aside .chat-list .search-box { - flex-wrap: wrap; - padding: 10px 15px; -} - -#app .chat-page .el-aside .chat-list .search-box .el-input__wrapper { - background-color: #363535; - box-shadow: none; -} - -#app .chat-page .el-aside .chat-list ::-webkit-scrollbar { - width: 0; - height: 0; - background-color: transparent; -} - -#app .chat-page .el-aside .chat-list .content { - width: 100%; - overflow-y: scroll; -} - -#app .chat-page .el-aside .chat-list .content .chat-list-item { - display: flex; - width: 100%; - justify-content: flex-start; - padding: 8px 12px; - cursor: pointer; -} - -#app .chat-page .el-aside .chat-list .content .chat-list-item:hover { - background-color: #343540; -} - -#app .chat-page .el-aside .chat-list .content .chat-list-item .avatar { - width: 28px; - height: 28px; - border-radius: 50%; -} - -#app .chat-page .el-aside .chat-list .content .chat-list-item .chat-title-input { - font-size: 14px; - margin-top: 4px; - margin-left: 10px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - width: 190px; -} - -#app .chat-page .el-aside .chat-list .content .chat-list-item .chat-title { - color: #c1c1c1; - padding: 5px 10px; - max-width: 220px; - font-size: 14px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -#app .chat-page .el-aside .chat-list .content .chat-list-item .btn { - display: none; - position: absolute; - right: 2px; - top: 16px; - color: #fff; -} - -#app .chat-page .el-aside .chat-list .content .chat-list-item .btn .el-icon { - margin-right: 8px; -} - -#app .chat-page .el-aside .chat-list .content .chat-list-item.active { - background-color: #343540; -} - -#app .chat-page .el-aside .chat-list .content .chat-list-item.active .btn { - display: inline; -} - -#app .chat-page .el-aside .tool-box { - display: flex; - justify-content: flex-end; - align-items: center; - padding: 0 20px 10px 20px; - border-top: 1px solid #3c3c3c; -} - -#app .chat-page .el-aside .tool-box .user-info { - width: 100%; - padding-top: 10px; -} - -#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link { - width: 100%; - cursor: pointer; - display: flex; -} - -#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .el-image { - width: 20px; - height: 20px; - border-radius: 5px; -} - -#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .username { - display: flex; - line-height: 22px; - width: 230px; - padding-left: 10px; -} - -#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .el-icon { - color: #ccc; - line-height: 24px; -} - -#app .chat-page .el-main { - overflow: hidden; - --el-main-padding: 0; - margin: 0; -} - -#app .chat-page .el-main .chat-head { - width: 100%; - height: 50px; - background-color: #28292a; -} - -#app .chat-page .el-main .chat-head .chat-config { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - padding-top: 10px; -} - -#app .chat-page .el-main .chat-head .chat-config .role-select-label { - color: #fff; -} - -#app .chat-page .el-main .chat-head .chat-config .el-select { - max-width: 150px; - margin-right: 10px; -} - -#app .chat-page .el-main .chat-head .chat-config .role-select { - max-width: 130px; -} - -#app .chat-page .el-main .chat-head .chat-config .el-button .el-icon { - margin-right: 5px; -} - -#app .chat-page .el-main .chat-head .iconfont { - margin-right: 5px; -} - -#app .chat-page .el-main .chat-head .is-circle { - margin-left: 5px; -} - -#app .chat-page .el-main .chat-head .is-circle .iconfont { - margin-right: 0; -} - -#app .chat-page .el-main .chat-box { - min-width: 0; - flex: 1; - background-color: #fff; - border-left: 1px solid #4f4f4f; -} - -#app .chat-page .el-main .chat-box #container { - overflow: hidden; - width: 100%; -} - -#app .chat-page .el-main .chat-box #container ::-webkit-scrollbar { - width: 0; - height: 0; - background-color: transparent; -} - -#app .chat-page .el-main .chat-box #container .chat-box { - overflow-y: scroll; - --content-font-size: 16px; - --content-color: #c1c1c1; - font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; - padding: 0 0 50px 0; -} - -#app .chat-page .el-main .chat-box #container .chat-box .chat-line { - font-size: 14px; - display: flex; - align-items: flex-start; -} - -#app .chat-page .el-main .chat-box #container .re-generate { - position: relative; - display: flex; - justify-content: center; -} - -#app .chat-page .el-main .chat-box #container .re-generate .btn-box { - position: absolute; - bottom: 10px; -} - -#app .chat-page .el-main .chat-box #container .re-generate .btn-box .el-button .el-icon { - margin-right: 5px; -} - -#app .chat-page .el-main .chat-box #container .input-box { - background-color: #fff; - display: flex; - justify-content: center; - align-items: center; - box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1); - padding: 0 15px; -} - -#app .chat-page .el-main .chat-box #container .input-box .input-container { - width: 100%; - margin: 0; - border: none; - padding: 10px 0; - display: flex; - justify-content: center; - position: relative; -} - -#app .chat-page .el-main .chat-box #container .input-box .input-container .el-textarea .el-textarea__inner::-webkit-scrollbar { - width: 0; - height: 0; -} - -#app .chat-page .el-main .chat-box #container .input-box .input-container .select-file { - position: absolute; - right: 48px; - top: 20px; -} - -#app .chat-page .el-main .chat-box #container .input-box .input-container .send-btn { - position: absolute; - right: 12px; - top: 20px; -} - -#app .chat-page .el-main .chat-box #container .input-box .input-container .send-btn .el-button { - padding: 8px 5px; - border-radius: 6px; - background: #19c37d; - color: #fff; - font-size: 20px; -} - -#app .chat-page .el-main .chat-box #container::-webkit-scrollbar { - width: 0; - height: 0; -} - -#app .el-message-box { - width: 90%; - max-width: 420px; -} - -#app .el-message { - min-width: 100px; - max-width: 600px; -} - -.el-select-dropdown__wrap .el-select-dropdown__item .role-option { - display: flex; - flex-flow: row; - margin-top: 8px; -} - -.el-select-dropdown__wrap .el-select-dropdown__item .role-option .el-image { - width: 20px; - height: 20px; - border-radius: 50%; -} - -.el-select-dropdown__wrap .el-select-dropdown__item .role-option span { - margin-left: 5px; - height: 20px; - line-height: 20px; -} - -.account { - display: flex; - background-color: #90ffc2; - color: #000; - width: 100%; - border-radius: 10px; - padding: 10px; -} - -.account .vip-logo .el-image { - width: 40px; - height: 40px; - border-radius: 100%; - background-color: #fff; -} - -.account .vip-info { - padding: 0 10px 0 10px; -} - -.account .vip-info h4, -.account .vip-info p { - margin: 0; -} - -.account .vip-info h4 { - font-weight: bold; - font-size: 16px; -} - -.account .vip-info p { - color: #333; -} - -.account .pay-btn { - width: 100%; - display: flex; - justify-content: right; - align-items: center; -} - -.el-overlay-dialog .el-dialog .el-dialog__body .notice { - padding: 0 20px 0 20px; - line-height: 1.8; -} - -.el-overlay-dialog .el-dialog .el-dialog__body .notice .el-text { - font-size: 16px; -} - -.dialog-service { - text-align: center; -} - -.dialog-service .el-image { - width: 360px; -} diff --git a/web/src/assets/css/chat-plus.styl b/web/src/assets/css/chat-plus.styl index 1c4a22c1..a2d73018 100644 --- a/web/src/assets/css/chat-plus.styl +++ b/web/src/assets/css/chat-plus.styl @@ -160,13 +160,15 @@ $borderColor = #4676d0; padding 5px border-radius 5px cursor pointer + background-color #f2f2f2 + margin-right 10px .iconfont { font-size 18px color #19c37d } &:hover { - background #D5FAD3 + background-color #D5FAD3 } } @@ -412,9 +414,10 @@ $borderColor = #4676d0; .el-dialog { .el-dialog__body { .notice { - //padding 0 20px 0 20px line-height 1.8 font-size 16px + overflow auto + height 100% } } } @@ -426,4 +429,11 @@ $borderColor = #4676d0; .el-image { width 360px; } +} + +.tools-dropdown { + width auto + .el-icon { + margin-left 5px; + } } \ No newline at end of file diff --git a/web/src/assets/css/home.styl b/web/src/assets/css/home.styl index bf257eda..b435bdb9 100644 --- a/web/src/assets/css/home.styl +++ b/web/src/assets/css/home.styl @@ -23,6 +23,7 @@ .el-image { width 48px height 48px + border-radius 50% } } diff --git a/web/src/assets/css/index.styl b/web/src/assets/css/index.styl index 8a47c12a..fbe52f78 100644 --- a/web/src/assets/css/index.styl +++ b/web/src/assets/css/index.styl @@ -54,8 +54,9 @@ padding 10px 10px 0 10px } - .el-image { + .logo { height 50px + border-radius 50% } .el-button { @@ -72,6 +73,9 @@ .content { text-align: center; position relative + display flex + flex-flow: column; + align-items: center; h1 { font-size: 5rem; @@ -88,6 +92,10 @@ max-width 900px padding 20px + .el-space--horizontal { + justify-content center + } + .nav-item { width 200px .el-button { diff --git a/web/src/assets/css/login.styl b/web/src/assets/css/login.styl index bf8b6c49..9dfa7449 100644 --- a/web/src/assets/css/login.styl +++ b/web/src/assets/css/login.styl @@ -30,6 +30,7 @@ .el-image { width 120px; cursor pointer + border-radius 50% } } @@ -96,7 +97,8 @@ font-size 20px background: #E9F1F6; padding: 8px; - border-radius: 50%; + border-radius: 50% + cursor pointer } .iconfont.icon-wechat { color #0bc15f diff --git a/web/src/assets/css/luma.css b/web/src/assets/css/luma.css new file mode 100644 index 00000000..ec316d99 --- /dev/null +++ b/web/src/assets/css/luma.css @@ -0,0 +1,142 @@ +.page-luma { + display: flex; + height: 100%; + background-color: #0e0808; + overflow: auto; + flex-flow: column; + align-items: center; + background: linear-gradient(180deg, rgba(75,62,53,0.8), rgba(144,50,181,0.3)); +} +.page-luma .prompt-box { + display: flex; + max-width: 56rem; + width: 100%; + padding: 20px; + flex-flow: column; +} +.page-luma .prompt-box .images { + display: flex; + flex-flow: row; + padding-bottom: 10px; + justify-content: center; +} +.page-luma .prompt-box .images .item { + position: relative; +} +.page-luma .prompt-box .images .item .el-image { + width: 100px; + height: 100px; + border-radius: 6px; + margin-right: 10px; +} +.page-luma .prompt-box .images .item .el-icon { + position: absolute; + cursor: pointer; + font-size: 20px; + color: #545454; + right: 10px; + top: 0; +} +.page-luma .prompt-box .images .item .el-icon:hover { + color: #888; +} +.page-luma .prompt-box .prompt-container { + width: 100%; +} +.page-luma .prompt-box .prompt-container .input-container { + background: linear-gradient(90deg, rgba(75,62,53,0.8), rgba(144,50,181,0.3)); + border-radius: 28px; + padding: 10px 20px; + display: flex; + align-items: center; + box-shadow: 0 2px 10px rgba(0,0,0,0.2); +} +.page-luma .prompt-box .prompt-container .input-container .prompt-input { + background: transparent; + border: none; + outline: none; + color: #fff; + font-size: 14px; + width: 100%; + padding: 10px; + resize: none; + white-space: pre-wrap; + word-wrap: break-word; + line-height: 24px; + overflow-wrap: break-word; + scrollbar-width: none; /* 隐藏滚动条 */ +} +.page-luma .prompt-box .prompt-container .input-container .prompt-input::placeholder { + color: rgba(255,255,255,0.6); +} +.page-luma .prompt-box .prompt-container .input-container .prompt-input::-webkit-scrollbar { + display: none; +} +.page-luma .prompt-box .prompt-container .input-container .upload-icon, +.page-luma .prompt-box .prompt-container .input-container .send-icon { + color: #e1e1e1; +} +.page-luma .prompt-box .prompt-container .input-container .upload-icon .iconfont, +.page-luma .prompt-box .prompt-container .input-container .send-icon .iconfont { + font-size: 20px; + cursor: pointer; +} +.page-luma .prompt-box .prompt-container .input-container .upload-icon { + position: relative; +} +.page-luma .video-container { + display: flex; + flex-flow: column; + width: 100%; + padding: 0 40px; +} +.page-luma .video-container .h-title { + color: #fff; + width: 100%; + font-size: 36px; + text-align: left; +} +.page-luma .video-container .videos .item { + margin-bottom: 20px; +} +.page-luma .video-container .videos .item .video-box { + width: 100%; + border-radius: 10px; +} +.page-luma .video-container .videos .item .video-box video, +.page-luma .video-container .videos .item .video-box img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 10px; + cursor: pointer; +} +.page-luma .video-container .videos .item .video-name { + color: #e1e1e1; + font-size: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 6px 0; + text-align: center; +} +.page-luma .video-container .videos .item .opts { + display: flex; + justify-content: center; +} +.page-luma .video-container .videos .item .opts .btn { + margin-right: 10px; + background-color: rgba(255,255,255,0.15); + border: none; + border-radius: 20px; + padding: 3px 15px; + cursor: pointer; + color: #fff; + font-size: 14px; +} +.page-luma .video-container .videos .item .opts .btn .iconfont { + font-size: 12px; +} +.page-luma .video-container .videos .item .opts .btn:hover { + background-color: rgba(255,255,255,0.2); +} diff --git a/web/src/assets/css/luma.styl b/web/src/assets/css/luma.styl new file mode 100644 index 00000000..9482d12d --- /dev/null +++ b/web/src/assets/css/luma.styl @@ -0,0 +1,354 @@ +.page-luma { + display flex + height 100% + background-color #0E0808 + overflow auto + //justify-content center + flex-flow column + align-items center + background: linear-gradient(180deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3)); + + + .prompt-box { + display flex + max-width 56rem + width 100% + padding 20px + flex-flow column + + .images { + display flex + flex-flow row + padding-bottom 10px + justify-content center + align-items center + + .item { + position relative + + .el-image { + width 100px + height 100px + border-radius 6px + margin-right 10px + } + + .el-icon { + position absolute + cursor pointer + font-size 20px + color #545454 + right 10px + top 0 + + &:hover { + color #888888 + } + } + } + + .btn-swap { + margin-right 10px + .icon-exchange{ + color #ffffff + cursor pointer + } + } + } + + + .prompt-container { + width: 100%; + .input-container { + background: linear-gradient(90deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3)); + border-radius: 28px; + padding: 10px 20px; + display: flex; + align-items: center; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + + .prompt-input { + background: transparent; + border: none; + outline: none; + color: white; + font-size: 14px; + width: 100%; + padding: 10px; + resize: none; + white-space: pre-wrap; + word-wrap: break-word; + line-height 24px + overflow-wrap: break-word; + + scrollbar-width: none; /* 隐藏滚动条 */ + &::placeholder { + color: rgba(255, 255, 255, 0.6); + } + &::-webkit-scrollbar { + display: none; + } + } + + .upload-icon, .send-icon { + color #e1e1e1 + .iconfont { + font-size 20px + cursor pointer + } + } + .upload-icon { + position relative + } + } + .params { + display flex + justify-content right + color #e1e1e1 + font-size 14px + padding 10px 30px + + .item-group { + margin-left 20px + .label { + margin-right 5px + position relative + top 1px + } + } + } + } + + } + + + .video-container { + display flex + flex-flow column + width 100% + padding 0 40px + + .h-title { + color #ffffff + width 100% + font-size 36px + text-align left + } + + .list-box { + padding 0 + .item { + display flex + flex-flow row + align-items center + height 100px + padding 10px 15px + border-radius 10px + cursor pointer + margin-bottom 10px + + &:hover { + background-color #2A2525 + } + + .left { + .container { + width 160px + position relative + + .video{ + width 160px + border-radius 5px + } + + .el-image { + width 160px + height 90px + border-radius 5px + } + + .duration { + position absolute + bottom 0 + right 0 + background-color rgba(14,8,8,.7) + padding 0 3px + font-family 'Input Sans' + font-size 14px + font-weight 700 + border-radius .125rem + } + + .play { + position absolute + width: 100% + height 100% + top: 0; + left: 50%; + border none + border-radius 5px + background rgba(100, 100, 100, 0.3) + cursor pointer + color #ffffff + opacity 0 + transform: translate(-50%, 0px); + transition opacity 0.3s ease 0s + } + + &:hover { + .play { + opacity 1 + //display block + } + } + } + } + + .center { + width 100% + //border 1px solid saddlebrown + display flex + justify-content center + align-items flex-start + flex-flow column + padding 0 20px + + .prompt,.failed { + padding 6px 0 + font-size 16px + max-height 80px + line-height 28px + overflow hidden + text-overflow ellipsis + } + .prompt { + color rgb(250 247 245) + } + .failed { + color #E4696B + } + } + + .right { + display flex + justify-content right + min-width 200px; + font-size 14px + padding 0 + + .tools { + display flex + justify-content left + align-items center + flex-flow row + height 90px + + .btn-publish { + padding 2px 10px + + .text { + margin-right 10px + color #e1e1e1 + } + } + + .btn-icon { + background none + padding 6px + transition background 0.6s ease 0s + color #726E6C + + &:hover { + background #5f5958 + color #e1e1e1 + } + } + } + } + } + + } + + .pagination { + padding 10px 20px + display flex + justify-content center + } + + //.videos { + // .item { + // margin-bottom 20px + // + // .video-box { + // width 100% + // aspect-ratio: 16/9; + // border-radius 10px + // video,img { + // width: 100%; + // height: 100%; + // object-fit: cover; + // border-radius 10px + // cursor pointer + // } + // } + // + // + // .video-name { + // color #e1e1e1 + // font-size 16px + // white-space nowrap + // overflow hidden + // text-overflow ellipsis + // padding 6px 0 + // text-align center + // } + // + // .opts { + // display flex + // justify-content center + // .btn { + // margin-right 10px + // background-color hsla(0,0%,100%,.15) + // border none + // border-radius 20px + // padding 3px 15px + // cursor pointer + // color #ffffff + // font-size 14px + // + // .iconfont { + // font-size 11px + // position relative + // margin-right 5px + // top -2px + // } + // + // .el-image { + // width 14px + // height 14px + // margin-right 5px + // } + // + // &:hover { + // background-color hsla(0,0%,100%,.2) + // } + // } + // } + // } + //} + } + + .btn { + margin-right 10px + background-color #363030 + border none + border-radius 5px + padding 5px 10px + cursor pointer + + &:hover { + background-color #5F5958 + } + } + +} \ No newline at end of file diff --git a/web/src/assets/css/member.styl b/web/src/assets/css/member.styl index 7147db4a..c1f61bb5 100644 --- a/web/src/assets/css/member.styl +++ b/web/src/assets/css/member.styl @@ -4,8 +4,6 @@ .el-dialog { .el-dialog__body { - padding-top 10px - .pay-container { .amount { text-align center diff --git a/web/src/assets/css/suno.styl b/web/src/assets/css/suno.styl index ffe39119..42872022 100644 --- a/web/src/assets/css/suno.styl +++ b/web/src/assets/css/suno.styl @@ -13,6 +13,13 @@ display flex flex-flow row justify-content: space-between; + + .upload-music { + .iconfont { + margin-right 5px + font-size 14px + } + } } .params { @@ -85,6 +92,10 @@ height 50px border-radius 10px } + .icon-mp3 { + font-size 42px + color #A85295 + } .title { display flex margin-left 10px @@ -266,7 +277,7 @@ } .right { - min-width 320px; + min-width 350px; font-size 14px padding 0 15px @@ -292,7 +303,8 @@ color #726E6C &:hover { - background #3C3737 + background #5f5958 + color #e1e1e1 } } } diff --git a/web/src/assets/iconfont/iconfont.css b/web/src/assets/iconfont/iconfont.css index 842956a9..31901bb5 100644 --- a/web/src/assets/iconfont/iconfont.css +++ b/web/src/assets/iconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 4125778 */ - src: url('iconfont.woff2?t=1721896403264') format('woff2'), - url('iconfont.woff?t=1721896403264') format('woff'), - url('iconfont.ttf?t=1721896403264') format('truetype'); + src: url('iconfont.woff2?t=1725929120246') format('woff2'), + url('iconfont.woff?t=1725929120246') format('woff'), + url('iconfont.ttf?t=1725929120246') format('truetype'); } .iconfont { @@ -13,6 +13,42 @@ -moz-osx-font-smoothing: grayscale; } +.icon-luma:before { + content: "\e704"; +} + +.icon-exchange:before { + content: "\e6f5"; +} + +.icon-merge:before { + content: "\e901"; +} + +.icon-upload:before { + content: "\e611"; +} + +.icon-concat:before { + content: "\e630"; +} + +.icon-email:before { + content: "\e670"; +} + +.icon-mobile:before { + content: "\e79a"; +} + +.icon-drag:before { + content: "\e8ec"; +} + +.icon-move:before { + content: "\e6fd"; +} + .icon-link:before { content: "\e6b4"; } @@ -61,7 +97,7 @@ content: "\e608"; } -.icon-mp:before { +.icon-mp3:before { content: "\e6c4"; } diff --git a/web/src/assets/iconfont/iconfont.js b/web/src/assets/iconfont/iconfont.js index 7048f335..ad94b5e2 100644 --- a/web/src/assets/iconfont/iconfont.js +++ b/web/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4125778='',function(a){var l=(l=document.getElementsByTagName("script"))[l.length-1],c=l.getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var t,h,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(c&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}t=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(t,0):(h=function(){document.removeEventListener("DOMContentLoaded",h,!1),t()},document.addEventListener("DOMContentLoaded",h,!1)):document.attachEvent&&(i=t,o=a.document,z=!1,p(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,s())})}function s(){z||(z=!0,i())}function p(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(p,50)}s()}}(window); \ No newline at end of file +window._iconfont_svg_string_4125778='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var t,h,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}t=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(t,0):(h=function(){document.removeEventListener("DOMContentLoaded",h,!1),t()},document.addEventListener("DOMContentLoaded",h,!1)):document.attachEvent&&(i=t,o=a.document,z=!1,p(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,s())})}function s(){z||(z=!0,i())}function p(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(p,50)}s()}})(window); \ No newline at end of file diff --git a/web/src/assets/iconfont/iconfont.json b/web/src/assets/iconfont/iconfont.json index ffc0d972..f114daa2 100644 --- a/web/src/assets/iconfont/iconfont.json +++ b/web/src/assets/iconfont/iconfont.json @@ -1,10 +1,73 @@ { "id": "4125778", - "name": "chatgpt", + "name": "geekai", "font_family": "iconfont", "css_prefix_text": "icon-", "description": "", "glyphs": [ + { + "icon_id": "41645421", + "name": "luma-logo", + "font_class": "luma", + "unicode": "e704", + "unicode_decimal": 59140 + }, + { + "icon_id": "7573248", + "name": "exchange", + "font_class": "exchange", + "unicode": "e6f5", + "unicode_decimal": 59125 + }, + { + "icon_id": "8094809", + "name": "merge-cells", + "font_class": "merge", + "unicode": "e901", + "unicode_decimal": 59649 + }, + { + "icon_id": "10278208", + "name": "上传", + "font_class": "upload", + "unicode": "e611", + "unicode_decimal": 58897 + }, + { + "icon_id": "23538484", + "name": "拼接", + "font_class": "concat", + "unicode": "e630", + "unicode_decimal": 58928 + }, + { + "icon_id": "15838472", + "name": "email", + "font_class": "email", + "unicode": "e670", + "unicode_decimal": 58992 + }, + { + "icon_id": "6151052", + "name": "mobile-alt", + "font_class": "mobile", + "unicode": "e79a", + "unicode_decimal": 59290 + }, + { + "icon_id": "15617554", + "name": "drag", + "font_class": "drag", + "unicode": "e8ec", + "unicode_decimal": 59628 + }, + { + "icon_id": "240317", + "name": "move", + "font_class": "move", + "unicode": "e6fd", + "unicode_decimal": 59133 + }, { "icon_id": "880330", "name": "link", @@ -92,7 +155,7 @@ { "icon_id": "4318807", "name": "mp3", - "font_class": "mp", + "font_class": "mp3", "unicode": "e6c4", "unicode_decimal": 59076 }, diff --git a/web/src/assets/iconfont/iconfont.ttf b/web/src/assets/iconfont/iconfont.ttf index 32d02e7b..f00663ec 100644 Binary files a/web/src/assets/iconfont/iconfont.ttf and b/web/src/assets/iconfont/iconfont.ttf differ diff --git a/web/src/assets/iconfont/iconfont.woff b/web/src/assets/iconfont/iconfont.woff index eb517115..ff63b66a 100644 Binary files a/web/src/assets/iconfont/iconfont.woff and b/web/src/assets/iconfont/iconfont.woff differ diff --git a/web/src/assets/iconfont/iconfont.woff2 b/web/src/assets/iconfont/iconfont.woff2 index 7d4d825e..a6df0ee8 100644 Binary files a/web/src/assets/iconfont/iconfont.woff2 and b/web/src/assets/iconfont/iconfont.woff2 differ diff --git a/web/src/components/ResetAccount.vue b/web/src/components/BindEmail.vue similarity index 51% rename from web/src/components/ResetAccount.vue rename to web/src/components/BindEmail.vue index 04446884..719f9c70 100644 --- a/web/src/components/ResetAccount.vue +++ b/web/src/components/BindEmail.vue @@ -2,26 +2,24 @@ -
- -

当前绑定账号:{{ username }},只允许使绑定有效的手机号或者邮箱地址作为登录账号。

-
+
+
当前已绑定邮箱:{{ email }}
- - - + + + - + - - + + @@ -39,55 +37,67 @@ \ No newline at end of file diff --git a/web/src/components/Captcha.vue b/web/src/components/Captcha.vue new file mode 100644 index 00000000..322ae0c3 --- /dev/null +++ b/web/src/components/Captcha.vue @@ -0,0 +1,144 @@ + + + + + \ No newline at end of file diff --git a/web/src/components/FileSelect.vue b/web/src/components/FileSelect.vue index b2a1cb59..d340c460 100644 --- a/web/src/components/FileSelect.vue +++ b/web/src/components/FileSelect.vue @@ -20,7 +20,7 @@ :auto-upload="true" :show-file-list="false" :http-request="afterRead" - accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf" + accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf,.mp4,.mp3" > diff --git a/web/src/components/InviteList.vue b/web/src/components/InviteList.vue index 91567e79..6fe482ed 100644 --- a/web/src/components/InviteList.vue +++ b/web/src/components/InviteList.vue @@ -33,11 +33,10 @@ - diff --git a/web/src/components/ThirdLogin.vue b/web/src/components/ThirdLogin.vue new file mode 100644 index 00000000..cbfe9a9d --- /dev/null +++ b/web/src/components/ThirdLogin.vue @@ -0,0 +1,93 @@ + + + + + \ No newline at end of file diff --git a/web/src/components/ui/BlackDialog.vue b/web/src/components/ui/BlackDialog.vue index 7794afb0..4e659ad2 100644 --- a/web/src/components/ui/BlackDialog.vue +++ b/web/src/components/ui/BlackDialog.vue @@ -20,10 +20,10 @@
- + + diff --git a/web/src/views/MarkMap.vue b/web/src/views/MarkMap.vue index 812e88f8..06007624 100644 --- a/web/src/views/MarkMap.vue +++ b/web/src/views/MarkMap.vue @@ -201,7 +201,6 @@ window.onresize = () => { } const socket = ref(null) -const heartbeatHandle = ref(0) const connect = (userId) => { if (socket.value !== null) { socket.value.close() @@ -216,24 +215,9 @@ const connect = (userId) => { } } - // 心跳函数 - const sendHeartbeat = () => { - clearTimeout(heartbeatHandle.value) - new Promise((resolve, reject) => { - if (socket.value !== null) { - socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"})) - } - resolve("success") - }).then(() => { - heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000) - }); - } - const _socket = new WebSocket(host + `/api/markMap/client?user_id=${userId}&model_id=${modelID.value}`); _socket.addEventListener('open', () => { socket.value = _socket; - // 发送心跳消息 - sendHeartbeat() }); _socket.addEventListener('message', event => { diff --git a/web/src/views/Member.vue b/web/src/views/Member.vue index 5cfca464..fcf359f9 100644 --- a/web/src/views/Member.vue +++ b/web/src/views/Member.vue @@ -7,13 +7,19 @@ - 修改密码 + 绑定邮箱 - 更改账号 + 绑定手机 + + + 第三方登录 + + + 修改密码 - 兑换码核销 + 卡密兑换 @@ -93,8 +99,10 @@ - + + + + @@ -143,13 +151,15 @@ import {InfoFilled, SuccessFilled} from "@element-plus/icons-vue"; import {checkSession, getSystemInfo} from "@/store/cache"; import UserProfile from "@/components/UserProfile.vue"; import PasswordDialog from "@/components/PasswordDialog.vue"; -import BindMobile from "@/components/ResetAccount.vue"; +import BindMobile from "@/components/BindMobile.vue"; import RedeemVerify from "@/components/RedeemVerify.vue"; import {useRouter} from "vue-router"; import {removeUserToken} from "@/store/session"; import UserOrder from "@/components/UserOrder.vue"; import CountDown from "@/components/CountDown.vue"; import {useSharedStore} from "@/store/sharedata"; +import BindEmail from "@/components/BindEmail.vue"; +import ThirdLogin from "@/components/ThirdLogin.vue"; const list = ref([]) const showPayDialog = ref(false) @@ -157,9 +167,11 @@ const vipImg = ref("/images/vip.png") const enableReward = ref(false) // 是否启用众筹功能 const rewardImg = ref('/images/reward.png') const qrcode = ref("") -const showPasswordDialog = ref(false); -const showBindMobileDialog = ref(false); -const showRedeemVerifyDialog = ref(false); +const showPasswordDialog = ref(false) +const showBindMobileDialog = ref(false) +const showBindEmailDialog = ref(false) +const showRedeemVerifyDialog = ref(false) +const showThirdLoginDialog = ref(false) const text = ref("") const user = ref(null) const isLogin = ref(false) diff --git a/web/src/views/PowerLog.vue b/web/src/views/PowerLog.vue index 909e5c78..84df9cb1 100644 --- a/web/src/views/PowerLog.vue +++ b/web/src/views/PowerLog.vue @@ -23,8 +23,8 @@ --el-table-row-hover-bg-color:#373C47; --el-table-header-bg-color:#474E5C; --el-table-text-color:#d1d1d1"> - - + + - + diff --git a/web/src/views/Register.vue b/web/src/views/Register.vue index f080b568..e5c8354e 100644 --- a/web/src/views/Register.vue +++ b/web/src/views/Register.vue @@ -41,7 +41,7 @@
- +
@@ -50,7 +50,7 @@