From 76d32c78d8e8529cddfc344cd1c6a499fae3443a Mon Sep 17 00:00:00 2001 From: GeekMaster Date: Fri, 18 Jul 2025 18:04:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8D=B3=E6=A2=A6AI=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/core/types/config.go | 15 +- api/handler/admin/jimeng_handler.go | 177 +++++ api/handler/dalle_handler.go | 2 +- api/handler/function_handler.go | 18 +- api/handler/jimeng_handler.go | 639 +++++++++++++++++ api/handler/mj_handler.go | 2 +- api/handler/prompt_handler.go | 10 +- api/handler/sd_handler.go | 2 +- api/handler/video_handler.go | 4 +- api/main.go | 39 ++ api/service/dalle/service.go | 4 +- api/service/jimeng/client.go | 332 +++++++++ api/service/jimeng/consumer.go | 177 +++++ api/service/jimeng/service.go | 633 +++++++++++++++++ api/service/jimeng/types.go | 163 +++++ api/service/mj/service.go | 4 +- api/service/sd/service.go | 4 +- api/service/suno/service.go | 4 +- api/service/video/video.go | 4 +- api/store/model/jimeng_job.go | 58 ++ api/store/redis_queue.go | 21 +- api/store/vo/jimeng_job.go | 21 + api/test/test.go | 55 -- web/src/assets/iconfont/iconfont.css | 14 +- web/src/assets/iconfont/iconfont.js | 2 +- web/src/assets/iconfont/iconfont.json | 14 + web/src/assets/iconfont/iconfont.ttf | Bin 54084 -> 54480 bytes web/src/assets/iconfont/iconfont.woff | Bin 35196 -> 35488 bytes web/src/assets/iconfont/iconfont.woff2 | Bin 30412 -> 30676 bytes web/src/components/ImageUpload.vue | 291 ++++++++ web/src/components/admin/AdminSidebar.vue | 5 + web/src/router.js | 12 + web/src/store/jimeng.js | 513 ++++++++++++++ web/src/utils/libs.js | 5 + web/src/views/Home.vue | 15 +- web/src/views/Index.vue | 16 +- web/src/views/Jimeng.vue | 799 ++++++++++++++++++++++ web/src/views/Video.vue | 4 +- web/src/views/admin/JimengJobs.vue | 543 +++++++++++++++ web/src/views/admin/SysConfig.vue | 8 +- 40 files changed, 4511 insertions(+), 118 deletions(-) create mode 100644 api/handler/admin/jimeng_handler.go create mode 100644 api/handler/jimeng_handler.go create mode 100644 api/service/jimeng/client.go create mode 100644 api/service/jimeng/consumer.go create mode 100644 api/service/jimeng/service.go create mode 100644 api/service/jimeng/types.go create mode 100644 api/store/model/jimeng_job.go create mode 100644 api/store/vo/jimeng_job.go delete mode 100644 api/test/test.go create mode 100644 web/src/components/ImageUpload.vue create mode 100644 web/src/store/jimeng.js create mode 100644 web/src/views/Jimeng.vue create mode 100644 web/src/views/admin/JimengJobs.vue diff --git a/api/core/types/config.go b/api/core/types/config.go index 01b6bc02..bd57b31d 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -43,9 +43,16 @@ type SmtpConfig struct { } type ApiConfig struct { - ApiURL string - AppId string - Token string + ApiURL string + AppId string + Token string + JimengConfig JimengConfig // 即梦AI配置 +} + +// JimengConfig 即梦AI配置 +type JimengConfig struct { + AccessKey string // 火山引擎AccessKey + SecretKey string // 火山引擎SecretKey } type AlipayConfig struct { @@ -170,7 +177,7 @@ type SystemConfig struct { EnabledVerify bool `json:"enabled_verify"` // 是否启用验证码 EmailWhiteList []string `json:"email_white_list"` // 邮箱白名单列表 - TranslateModelId int `json:"translate_model_id"` // 用来做提示词翻译的大模型 id + AssistantModelId int `json:"assistant_model_id"` // 用来做提示词,翻译的AI模型 id MaxFileSize int `json:"max_file_size"` // 最大文件大小,单位:MB } diff --git a/api/handler/admin/jimeng_handler.go b/api/handler/admin/jimeng_handler.go new file mode 100644 index 00000000..7580a50f --- /dev/null +++ b/api/handler/admin/jimeng_handler.go @@ -0,0 +1,177 @@ +package admin + +import ( + "strconv" + + "geekai/core" + "geekai/handler" + "geekai/service/jimeng" + "geekai/store/model" + "geekai/utils/resp" + + "github.com/gin-gonic/gin" +) + +// AdminJimengHandler 管理后台即梦AI处理器 +type AdminJimengHandler struct { + handler.BaseHandler + jimengService *jimeng.Service +} + +// NewAdminJimengHandler 创建管理后台即梦AI处理器 +func NewAdminJimengHandler(app *core.AppServer, jimengService *jimeng.Service) *AdminJimengHandler { + return &AdminJimengHandler{ + BaseHandler: handler.BaseHandler{App: app}, + jimengService: jimengService, + } +} + +// Jobs 获取任务列表 +func (h *AdminJimengHandler) Jobs(c *gin.Context) { + page := h.GetInt(c, "page", 1) + pageSize := h.GetInt(c, "page_size", 20) + userId := h.GetInt(c, "user_id", 0) + taskType := h.GetTrim(c, "type") + status := h.GetTrim(c, "status") + + var tasks []model.JimengJob + var total int64 + + session := h.DB.Model(&model.JimengJob{}) + + // 构建查询条件 + if userId > 0 { + session = session.Where("user_id = ?", userId) + } + if taskType != "" { + session = session.Where("type = ?", taskType) + } + if status != "" { + session = session.Where("status = ?", status) + } + + // 获取总数 + err := session.Count(&total).Error + if err != nil { + resp.ERROR(c, "获取任务数量失败") + return + } + + // 获取数据 + offset := (page - 1) * pageSize + err = session.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&tasks).Error + if err != nil { + resp.ERROR(c, "获取任务列表失败") + return + } + + resp.SUCCESS(c, gin.H{ + "jobs": tasks, + "total": total, + "page": page, + "page_size": pageSize, + }) +} + +// JobDetail 获取任务详情 +func (h *AdminJimengHandler) JobDetail(c *gin.Context) { + idStr := c.Param("id") + jobId, err := strconv.ParseUint(idStr, 10, 32) + if err != nil { + resp.ERROR(c, "参数错误") + return + } + + var job model.JimengJob + err = h.DB.Where("id = ?", jobId).First(&job).Error + if err != nil { + resp.ERROR(c, "任务不存在") + return + } + + resp.SUCCESS(c, job) +} + +// Remove 删除任务 +func (h *AdminJimengHandler) Remove(c *gin.Context) { + idStr := c.Param("id") + jobId, err := strconv.ParseUint(idStr, 10, 32) + if err != nil { + resp.ERROR(c, "参数错误") + return + } + + err = h.DB.Where("id = ?", jobId).Delete(&model.JimengJob{}).Error + if err != nil { + resp.ERROR(c, "删除任务失败") + return + } + + resp.SUCCESS(c, gin.H{}) +} + +// BatchRemove 批量删除任务 +func (h *AdminJimengHandler) BatchRemove(c *gin.Context) { + var req struct { + JobIds []uint `json:"job_ids" binding:"required"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + resp.ERROR(c, "参数错误") + return + } + + result := h.DB.Where("id IN ?", req.JobIds).Delete(&model.JimengJob{}) + if result.Error != nil { + resp.ERROR(c, "批量删除失败") + return + } + + resp.SUCCESS(c, gin.H{ + "message": "批量删除成功", + "deleted_count": result.RowsAffected, + }) +} + +// Stats 获取统计信息 +func (h *AdminJimengHandler) Stats(c *gin.Context) { + type StatResult struct { + Status string `json:"status"` + Count int64 `json:"count"` + } + + var stats []StatResult + err := h.DB.Model(&model.JimengJob{}). + Select("status, COUNT(*) as count"). + Group("status"). + Find(&stats).Error + if err != nil { + resp.ERROR(c, "获取统计信息失败") + return + } + + // 整理统计数据 + result := gin.H{ + "totalTasks": int64(0), + "completedTasks": int64(0), + "processingTasks": int64(0), + "failedTasks": int64(0), + "pendingTasks": int64(0), + } + + for _, stat := range stats { + result["totalTasks"] = result["totalTasks"].(int64) + stat.Count + switch stat.Status { + case "completed": + result["completedTasks"] = stat.Count + case "processing": + result["processingTasks"] = stat.Count + case "failed": + result["failedTasks"] = stat.Count + case "pending": + result["pendingTasks"] = stat.Count + } + } + + resp.SUCCESS(c, result) +} \ No newline at end of file diff --git a/api/handler/dalle_handler.go b/api/handler/dalle_handler.go index 256bd07a..0c7bc037 100644 --- a/api/handler/dalle_handler.go +++ b/api/handler/dalle_handler.go @@ -77,7 +77,7 @@ func (h *DallJobHandler) Image(c *gin.Context) { Quality: data.Quality, Size: data.Size, Style: data.Style, - TranslateModelId: h.App.SysConfig.TranslateModelId, + TranslateModelId: h.App.SysConfig.AssistantModelId, Power: chatModel.Power, } job := model.DallJob{ diff --git a/api/handler/function_handler.go b/api/handler/function_handler.go index 9cf59a8a..fb6d6cd4 100644 --- a/api/handler/function_handler.go +++ b/api/handler/function_handler.go @@ -213,7 +213,7 @@ func (h *FunctionHandler) Dall3(c *gin.Context) { Prompt: prompt, ModelId: 0, ModelName: "dall-e-3", - TranslateModelId: h.App.SysConfig.TranslateModelId, + TranslateModelId: h.App.SysConfig.AssistantModelId, N: 1, Quality: "standard", Size: "1024x1024", @@ -265,27 +265,27 @@ func (h *FunctionHandler) WebSearch(c *gin.Context) { resp.ERROR(c, types.InvalidArgs) return } - + // 从参数中获取搜索关键词 keyword, ok := params["keyword"].(string) if !ok || keyword == "" { resp.ERROR(c, "搜索关键词不能为空") return } - + // 从参数中获取最大页数,默认为1页 maxPages := 1 if pages, ok := params["max_pages"].(float64); ok { maxPages = int(pages) } - + // 获取用户ID userID, ok := params["user_id"].(float64) if !ok { resp.ERROR(c, "用户ID不能为空") return } - + // 查询用户信息 var user model.User res := h.DB.Where("id = ?", int(userID)).First(&user) @@ -293,21 +293,21 @@ func (h *FunctionHandler) WebSearch(c *gin.Context) { resp.ERROR(c, "用户不存在") return } - + // 检查用户算力是否足够 searchPower := 1 // 每次搜索消耗1点算力 if user.Power < searchPower { resp.ERROR(c, "算力不足,无法执行网络搜索") return } - + // 执行网络搜索 searchResults, err := crawler.SearchWeb(keyword, maxPages) if err != nil { resp.ERROR(c, fmt.Sprintf("搜索失败: %v", err)) return } - + // 扣减用户算力 err = h.userService.DecreasePower(user.Id, searchPower, model.PowerLog{ Type: types.PowerConsume, @@ -318,7 +318,7 @@ func (h *FunctionHandler) WebSearch(c *gin.Context) { resp.ERROR(c, "扣减算力失败:"+err.Error()) return } - + // 返回搜索结果 resp.SUCCESS(c, searchResults) } diff --git a/api/handler/jimeng_handler.go b/api/handler/jimeng_handler.go new file mode 100644 index 00000000..ba832830 --- /dev/null +++ b/api/handler/jimeng_handler.go @@ -0,0 +1,639 @@ +package handler + +import ( + "fmt" + "strconv" + "time" + + "geekai/core" + "geekai/core/types" + "geekai/service/jimeng" + "geekai/store/model" + "geekai/utils/resp" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +// JimengHandler 即梦AI处理器 +type JimengHandler struct { + BaseHandler + jimengService *jimeng.Service +} + +// NewJimengHandler 创建即梦AI处理器 +func NewJimengHandler(app *core.AppServer, jimengService *jimeng.Service) *JimengHandler { + return &JimengHandler{ + BaseHandler: BaseHandler{App: app}, + jimengService: jimengService, + } +} + +// TextToImage 文生图 +func (h *JimengHandler) TextToImage(c *gin.Context) { + var req struct { + Prompt string `json:"prompt" binding:"required"` + Seed int64 `json:"seed"` + Scale float64 `json:"scale"` + Width int `json:"width"` + Height int `json:"height"` + UsePreLLM bool `json:"use_pre_llm"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + // 获取当前用户 + user, err := h.GetLoginUser(c) + if err != nil { + resp.NotAuth(c) + return + } + + // 检查用户算力 + if user.Power < 20 { // 文生图消耗20算力 + resp.ERROR(c, "算力不足") + return + } + + // 设置默认参数 + if req.Scale == 0 { + req.Scale = 2.5 + } + if req.Width == 0 { + req.Width = 1328 + } + if req.Height == 0 { + req.Height = 1328 + } + if req.Seed == 0 { + req.Seed = -1 + } + + // 构建任务参数 + params := map[string]interface{}{ + "seed": req.Seed, + "scale": req.Scale, + "width": req.Width, + "height": req.Height, + "use_pre_llm": req.UsePreLLM, + } + + // 创建任务 + taskReq := &jimeng.CreateTaskRequest{ + Type: model.JimengJobTypeTextToImage, + Prompt: req.Prompt, + Params: params, + ReqKey: model.ReqKeyTextToImage, + Power: 20, + } + + job, err := h.jimengService.CreateTask(user.Id, taskReq) + if err != nil { + logger.Errorf("create jimeng text to image task failed: %v", err) + resp.ERROR(c, "创建任务失败") + return + } + + // 扣除用户算力 + h.subUserPower(user.Id, 20, model.PowerLog{ + Type: types.PowerConsume, + Model: "即梦文生图", + Remark: fmt.Sprintf("任务ID:%d", job.Id), + }) + + resp.SUCCESS(c, job) +} + +// ImageToImagePortrait 图生图人像写真 +func (h *JimengHandler) ImageToImagePortrait(c *gin.Context) { + var req struct { + ImageInput string `json:"image_input" binding:"required"` + Prompt string `json:"prompt"` + Width int `json:"width"` + Height int `json:"height"` + Gpen float64 `json:"gpen"` + Skin float64 `json:"skin"` + SkinUnifi float64 `json:"skin_unifi"` + GenMode string `json:"gen_mode"` + Seed int64 `json:"seed"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + resp.ERROR(c, "参数错误: "+err.Error()) + return + } + + // 获取当前用户 + user, err := h.GetLoginUser(c) + if err != nil { + resp.NotAuth(c) + return + } + + // 检查用户算力 + if user.Power < 30 { // 图生图消耗30算力 + resp.ERROR(c, "算力不足") + return + } + + // 设置默认参数 + if req.Width == 0 { + req.Width = 1328 + } + if req.Height == 0 { + req.Height = 1328 + } + if req.Gpen == 0 { + req.Gpen = 0.4 + } + if req.Skin == 0 { + req.Skin = 0.3 + } + if req.GenMode == "" { + if req.Prompt != "" { + req.GenMode = jimeng.GenModeCreative + } else { + req.GenMode = jimeng.GenModeReference + } + } + if req.Seed == 0 { + req.Seed = -1 + } + if req.Prompt == "" { + req.Prompt = "演唱会现场的合照,闪光灯拍摄" + } + + // 构建任务参数 + params := map[string]interface{}{ + "image_input": req.ImageInput, + "width": req.Width, + "height": req.Height, + "gpen": req.Gpen, + "skin": req.Skin, + "skin_unifi": req.SkinUnifi, + "gen_mode": req.GenMode, + "seed": req.Seed, + } + + // 创建任务 + taskReq := &jimeng.CreateTaskRequest{ + Type: model.JimengJobTypeImageToImagePortrait, + Prompt: req.Prompt, + Params: params, + ReqKey: model.ReqKeyImageToImagePortrait, + Power: 30, + } + + job, err := h.jimengService.CreateTask(user.Id, taskReq) + if err != nil { + logger.Errorf("create jimeng image to image portrait task failed: %v", err) + resp.ERROR(c, "创建任务失败") + return + } + + // 扣除用户算力 + h.subUserPower(user.Id, 30, model.PowerLog{ + Type: types.PowerConsume, + Model: "即梦图生图", + Remark: fmt.Sprintf("任务ID:%d", job.Id), + }) + + resp.SUCCESS(c, job) +} + +// ImageEdit 图像编辑 +func (h *JimengHandler) ImageEdit(c *gin.Context) { + var req struct { + ImageUrls []string `json:"image_urls"` + BinaryDataBase64 []string `json:"binary_data_base64"` + Prompt string `json:"prompt" binding:"required"` + Seed int64 `json:"seed"` + Scale float64 `json:"scale"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + resp.ERROR(c, "参数错误: "+err.Error()) + return + } + + if len(req.ImageUrls) == 0 && len(req.BinaryDataBase64) == 0 { + resp.ERROR(c, "请提供图片URL或Base64数据") + return + } + + // 获取当前用户 + user, err := h.GetLoginUser(c) + if err != nil { + resp.NotAuth(c) + return + } + + // 检查用户算力 + if user.Power < 25 { // 图像编辑消耗25算力 + resp.ERROR(c, "算力不足") + return + } + + // 设置默认参数 + if req.Scale == 0 { + req.Scale = 0.5 + } + if req.Seed == 0 { + req.Seed = -1 + } + + // 构建任务参数 + params := map[string]interface{}{ + "seed": req.Seed, + "scale": req.Scale, + } + if len(req.ImageUrls) > 0 { + params["image_urls"] = req.ImageUrls + } + if len(req.BinaryDataBase64) > 0 { + params["binary_data_base64"] = req.BinaryDataBase64 + } + + // 创建任务 + taskReq := &jimeng.CreateTaskRequest{ + Type: model.JimengJobTypeImageEdit, + Prompt: req.Prompt, + Params: params, + ReqKey: model.ReqKeyImageEdit, + Power: 25, + } + + job, err := h.jimengService.CreateTask(user.Id, taskReq) + if err != nil { + logger.Errorf("create jimeng image edit task failed: %v", err) + resp.ERROR(c, "创建任务失败") + return + } + + // 扣除用户算力 + h.subUserPower(user.Id, 25, model.PowerLog{ + Type: types.PowerConsume, + Model: "即梦图像编辑", + Remark: fmt.Sprintf("任务ID:%d", job.Id), + }) + + resp.SUCCESS(c, job) +} + +// ImageEffects 图像特效 +func (h *JimengHandler) ImageEffects(c *gin.Context) { + var req struct { + ImageInput1 string `json:"image_input1" binding:"required"` + TemplateId string `json:"template_id" binding:"required"` + Width int `json:"width"` + Height int `json:"height"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + resp.ERROR(c, "参数错误: "+err.Error()) + return + } + + // 获取当前用户 + user, err := h.GetLoginUser(c) + if err != nil { + resp.NotAuth(c) + return + } + + // 检查用户算力 + if user.Power < 15 { // 图像特效消耗15算力 + resp.ERROR(c, "算力不足") + return + } + + // 设置默认参数 + if req.Width == 0 { + req.Width = 1328 + } + if req.Height == 0 { + req.Height = 1328 + } + + // 构建任务参数 + params := map[string]interface{}{ + "image_input1": req.ImageInput1, + "template_id": req.TemplateId, + "width": req.Width, + "height": req.Height, + } + + // 创建任务 + taskReq := &jimeng.CreateTaskRequest{ + Type: model.JimengJobTypeImageEffects, + Prompt: "", + Params: params, + ReqKey: model.ReqKeyImageEffects, + Power: 15, + } + + job, err := h.jimengService.CreateTask(user.Id, taskReq) + if err != nil { + logger.Errorf("create jimeng image effects task failed: %v", err) + resp.ERROR(c, "创建任务失败") + return + } + + // 扣除用户算力 + h.subUserPower(user.Id, 15, model.PowerLog{ + Type: types.PowerConsume, + Model: "即梦图像特效", + Remark: fmt.Sprintf("任务ID:%d", job.Id), + }) + + resp.SUCCESS(c, job) +} + +// TextToVideo 文生视频 +func (h *JimengHandler) TextToVideo(c *gin.Context) { + var req struct { + Prompt string `json:"prompt" binding:"required"` + Seed int64 `json:"seed"` + AspectRatio string `json:"aspect_ratio"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + resp.ERROR(c, "参数错误: "+err.Error()) + return + } + + // 获取当前用户 + user, err := h.GetLoginUser(c) + if err != nil { + resp.NotAuth(c) + return + } + + // 检查用户算力 + if user.Power < 100 { // 文生视频消耗100算力 + resp.ERROR(c, "算力不足") + return + } + + // 设置默认参数 + if req.Seed == 0 { + req.Seed = -1 + } + if req.AspectRatio == "" { + req.AspectRatio = jimeng.AspectRatio16_9 + } + + // 构建任务参数 + params := map[string]interface{}{ + "seed": req.Seed, + "aspect_ratio": req.AspectRatio, + } + + // 创建任务 + taskReq := &jimeng.CreateTaskRequest{ + Type: model.JimengJobTypeTextToVideo, + Prompt: req.Prompt, + Params: params, + ReqKey: model.ReqKeyTextToVideo, + Power: 100, + } + + job, err := h.jimengService.CreateTask(user.Id, taskReq) + if err != nil { + logger.Errorf("create jimeng text to video task failed: %v", err) + resp.ERROR(c, "创建任务失败") + return + } + + // 扣除用户算力 + h.subUserPower(user.Id, 100, model.PowerLog{ + Type: types.PowerConsume, + Model: "即梦文生视频", + Remark: fmt.Sprintf("任务ID:%d", job.Id), + }) + + resp.SUCCESS(c, job) +} + +// ImageToVideo 图生视频 +func (h *JimengHandler) ImageToVideo(c *gin.Context) { + var req struct { + ImageUrls []string `json:"image_urls"` + BinaryDataBase64 []string `json:"binary_data_base64"` + Prompt string `json:"prompt"` + Seed int64 `json:"seed"` + AspectRatio string `json:"aspect_ratio" binding:"required"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + resp.ERROR(c, "参数错误: "+err.Error()) + return + } + + if len(req.ImageUrls) == 0 && len(req.BinaryDataBase64) == 0 { + resp.ERROR(c, "请提供图片URL或Base64数据") + return + } + + // 获取当前用户 + user, err := h.GetLoginUser(c) + if err != nil { + resp.NotAuth(c) + return + } + + // 检查用户算力 + if user.Power < 120 { // 图生视频消耗120算力 + resp.ERROR(c, "算力不足") + return + } + + // 设置默认参数 + if req.Seed == 0 { + req.Seed = -1 + } + + // 构建任务参数 + params := map[string]interface{}{ + "seed": req.Seed, + "aspect_ratio": req.AspectRatio, + } + if len(req.ImageUrls) > 0 { + params["image_urls"] = req.ImageUrls + } + if len(req.BinaryDataBase64) > 0 { + params["binary_data_base64"] = req.BinaryDataBase64 + } + + // 创建任务 + taskReq := &jimeng.CreateTaskRequest{ + Type: model.JimengJobTypeImageToVideo, + Prompt: req.Prompt, + Params: params, + ReqKey: model.ReqKeyImageToVideo, + Power: 120, + } + + job, err := h.jimengService.CreateTask(user.Id, taskReq) + if err != nil { + logger.Errorf("create jimeng image to video task failed: %v", err) + resp.ERROR(c, "创建任务失败") + return + } + + // 扣除用户算力 + h.subUserPower(user.Id, 120, model.PowerLog{ + Type: types.PowerConsume, + Model: "即梦图生视频", + Remark: fmt.Sprintf("任务ID:%d", job.Id), + }) + + resp.SUCCESS(c, job) +} + +// Jobs 获取任务列表 +func (h *JimengHandler) Jobs(c *gin.Context) { + user, err := h.GetLoginUser(c) + if err != nil { + resp.NotAuth(c) + return + } + + page := h.GetInt(c, "page", 1) + pageSize := h.GetInt(c, "page_size", 20) + + jobs, total, err := h.jimengService.GetUserJobs(user.Id, page, pageSize) + if err != nil { + logger.Errorf("get user jimeng jobs failed: %v", err) + resp.ERROR(c, "获取任务列表失败") + return + } + + resp.SUCCESS(c, gin.H{ + "jobs": jobs, + "total": total, + "page": page, + "page_size": pageSize, + }) +} + +// PendingCount 获取未完成任务数量 +func (h *JimengHandler) PendingCount(c *gin.Context) { + user, err := h.GetLoginUser(c) + if err != nil { + resp.NotAuth(c) + return + } + + count, err := h.jimengService.GetPendingTaskCount(user.Id) + if err != nil { + logger.Errorf("get pending task count failed: %v", err) + resp.ERROR(c, "获取待处理任务数量失败") + return + } + + resp.SUCCESS(c, gin.H{"count": count}) +} + +// Remove 删除任务 +func (h *JimengHandler) Remove(c *gin.Context) { + user, err := h.GetLoginUser(c) + if err != nil { + resp.NotAuth(c) + return + } + + jobId := h.GetInt(c, "id", 0) + if jobId == 0 { + resp.ERROR(c, "参数错误") + return + } + + if err := h.jimengService.DeleteJob(uint(jobId), user.Id); err != nil { + logger.Errorf("delete jimeng job failed: %v", err) + resp.ERROR(c, "删除任务失败") + return + } + + resp.SUCCESS(c, gin.H{}) +} + +// Retry 重试任务 +func (h *JimengHandler) Retry(c *gin.Context) { + user, err := h.GetLoginUser(c) + if err != nil { + resp.NotAuth(c) + return + } + + jobIdStr := c.Param("id") + jobId, err := strconv.ParseUint(jobIdStr, 10, 32) + if err != nil { + resp.ERROR(c, "参数错误") + return + } + + // 检查任务是否存在且属于当前用户 + job, err := h.jimengService.GetJob(uint(jobId)) + if err != nil { + resp.ERROR(c, "任务不存在") + return + } + + if job.UserId != user.Id { + resp.ERROR(c, "无权限操作") + return + } + + // 只有失败的任务才能重试 + if job.Status != model.JimengJobStatusFailed { + resp.ERROR(c, "只有失败的任务才能重试") + return + } + + // 重置任务状态 + if err := h.jimengService.UpdateJobStatus(uint(jobId), model.JimengJobStatusPending, ""); err != nil { + logger.Errorf("reset job status failed: %v", err) + resp.ERROR(c, "重置任务状态失败") + return + } + + // 重新推送到队列 + task := map[string]interface{}{ + "job_id": jobId, + "type": job.Type, + } + if err := h.jimengService.PushTaskToQueue(task); err != nil { + logger.Errorf("push retry task to queue failed: %v", err) + resp.ERROR(c, "推送重试任务失败") + return + } + + resp.SUCCESS(c, gin.H{"message": "重试任务已提交"}) +} + +// subUserPower 扣除用户算力 +func (h *JimengHandler) subUserPower(userId uint, power int, powerLog model.PowerLog) { + session := h.DB.Session(&gorm.Session{}) + + // 更新用户算力 + if err := session.Model(&model.User{}).Where("id = ?", userId).UpdateColumn("power", gorm.Expr("power - ?", power)).Error; err != nil { + logger.Errorf("update user power failed: %v", err) + return + } + + // 记录算力消费日志 + powerLog.UserId = userId + powerLog.Amount = power + powerLog.Mark = types.PowerSub + powerLog.CreatedAt = time.Now() + if err := session.Create(&powerLog).Error; err != nil { + logger.Errorf("create power log failed: %v", err) + return + } + + session.Commit() +} diff --git a/api/handler/mj_handler.go b/api/handler/mj_handler.go index b1f9fe96..fc522a7c 100644 --- a/api/handler/mj_handler.go +++ b/api/handler/mj_handler.go @@ -160,7 +160,7 @@ func (h *MidJourneyHandler) Image(c *gin.Context) { UserId: userId, ImgArr: data.ImgArr, Mode: h.App.SysConfig.MjMode, - TranslateModelId: h.App.SysConfig.TranslateModelId, + TranslateModelId: h.App.SysConfig.AssistantModelId, } job := model.MidJourneyJob{ Type: data.TaskType, diff --git a/api/handler/prompt_handler.go b/api/handler/prompt_handler.go index 31fecc9a..100099b4 100644 --- a/api/handler/prompt_handler.go +++ b/api/handler/prompt_handler.go @@ -48,7 +48,7 @@ func (h *PromptHandler) Lyric(c *gin.Context) { resp.ERROR(c, types.InvalidArgs) return } - content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.LyricPromptTemplate, data.Prompt), h.App.SysConfig.TranslateModelId) + content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.LyricPromptTemplate, data.Prompt), h.App.SysConfig.AssistantModelId) if err != nil { resp.ERROR(c, err.Error()) return @@ -79,7 +79,7 @@ func (h *PromptHandler) Image(c *gin.Context) { resp.ERROR(c, types.InvalidArgs) return } - content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.ImagePromptOptimizeTemplate, data.Prompt), h.App.SysConfig.TranslateModelId) + content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.ImagePromptOptimizeTemplate, data.Prompt), h.App.SysConfig.AssistantModelId) if err != nil { resp.ERROR(c, err.Error()) return @@ -108,7 +108,7 @@ func (h *PromptHandler) Video(c *gin.Context) { resp.ERROR(c, types.InvalidArgs) return } - content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.VideoPromptTemplate, data.Prompt), h.App.SysConfig.TranslateModelId) + content, err := utils.OpenAIRequest(h.DB, fmt.Sprintf(service.VideoPromptTemplate, data.Prompt), h.App.SysConfig.AssistantModelId) if err != nil { resp.ERROR(c, err.Error()) return @@ -158,9 +158,9 @@ func (h *PromptHandler) MetaPrompt(c *gin.Context) { } func (h *PromptHandler) getPromptModel() string { - if h.App.SysConfig.TranslateModelId > 0 { + if h.App.SysConfig.AssistantModelId > 0 { var chatModel model.ChatModel - h.DB.Where("id", h.App.SysConfig.TranslateModelId).First(&chatModel) + h.DB.Where("id", h.App.SysConfig.AssistantModelId).First(&chatModel) return chatModel.Value } return "gpt-4o" diff --git a/api/handler/sd_handler.go b/api/handler/sd_handler.go index c8358d08..f2eaf974 100644 --- a/api/handler/sd_handler.go +++ b/api/handler/sd_handler.go @@ -131,7 +131,7 @@ func (h *SdJobHandler) Image(c *gin.Context) { HdSteps: data.HdSteps, }, UserId: userId, - TranslateModelId: h.App.SysConfig.TranslateModelId, + TranslateModelId: h.App.SysConfig.AssistantModelId, } job := model.SdJob{ diff --git a/api/handler/video_handler.go b/api/handler/video_handler.go index a3aff209..6543a8c2 100644 --- a/api/handler/video_handler.go +++ b/api/handler/video_handler.go @@ -85,7 +85,7 @@ func (h *VideoHandler) LumaCreate(c *gin.Context) { Type: types.VideoLuma, Prompt: data.Prompt, Params: params, - TranslateModelId: h.App.SysConfig.TranslateModelId, + TranslateModelId: h.App.SysConfig.AssistantModelId, } // 插入数据库 job := model.VideoJob{ @@ -181,7 +181,7 @@ func (h *VideoHandler) KeLingCreate(c *gin.Context) { Type: types.VideoKeLing, Prompt: data.Prompt, Params: params, - TranslateModelId: h.App.SysConfig.TranslateModelId, + TranslateModelId: h.App.SysConfig.AssistantModelId, Channel: data.Channel, } // 插入数据库 diff --git a/api/main.go b/api/main.go index ba53f64d..f92a1d74 100644 --- a/api/main.go +++ b/api/main.go @@ -17,6 +17,7 @@ import ( logger2 "geekai/logger" "geekai/service" "geekai/service/dalle" + "geekai/service/jimeng" "geekai/service/mj" "geekai/service/oss" "geekai/service/payment" @@ -140,6 +141,7 @@ func main() { fx.Provide(handler.NewProductHandler), fx.Provide(handler.NewConfigHandler), fx.Provide(handler.NewPowerLogHandler), + fx.Provide(handler.NewJimengHandler), fx.Provide(admin.NewConfigHandler), fx.Provide(admin.NewAdminHandler), @@ -153,6 +155,9 @@ func main() { fx.Provide(admin.NewOrderHandler), fx.Provide(admin.NewChatHandler), fx.Provide(admin.NewPowerLogHandler), + fx.Provide(func(app *core.AppServer, service *jimeng.Service) *admin.AdminJimengHandler { + return admin.NewAdminJimengHandler(app, service) + }), // 创建服务 fx.Provide(sms.NewSendServiceManager), @@ -203,6 +208,17 @@ func main() { s.SyncTaskProgress() s.DownloadFiles() }), + + // 即梦AI 服务 + fx.Provide(func(config *types.AppConfig) *jimeng.Client { + return jimeng.NewClient(config.ApiConfig.JimengConfig.AccessKey, config.ApiConfig.JimengConfig.SecretKey) + }), + fx.Provide(jimeng.NewService), + fx.Provide(jimeng.NewConsumer), + fx.Invoke(func(consumer *jimeng.Consumer) { + consumer.Start() + go consumer.MonitorQueue() + }), fx.Provide(service.NewUserService), fx.Provide(payment.NewAlipayService), fx.Provide(payment.NewHuPiPay), @@ -496,6 +512,29 @@ func main() { group.GET("remove", h.Remove) group.GET("publish", h.Publish) }), + + // 即梦AI 路由 + fx.Invoke(func(s *core.AppServer, h *handler.JimengHandler) { + group := s.Engine.Group("/api/jimeng") + group.POST("text-to-image", h.TextToImage) + group.POST("image-to-image-portrait", h.ImageToImagePortrait) + group.POST("image-edit", h.ImageEdit) + group.POST("image-effects", h.ImageEffects) + group.POST("text-to-video", h.TextToVideo) + group.POST("image-to-video", h.ImageToVideo) + group.GET("jobs", h.Jobs) + group.GET("pending-count", h.PendingCount) + group.GET("remove", h.Remove) + group.POST("retry/:id", h.Retry) + }), + fx.Invoke(func(s *core.AppServer, h *admin.AdminJimengHandler) { + group := s.Engine.Group("/api/admin/jimeng") + group.GET("jobs", h.Jobs) + group.GET("job/:id", h.JobDetail) + group.DELETE("job/:id", h.Remove) + group.POST("batch-remove", h.BatchRemove) + group.GET("stats", h.Stats) + }), fx.Provide(admin.NewChatAppTypeHandler), fx.Invoke(func(s *core.AppServer, h *admin.ChatAppTypeHandler) { group := s.Engine.Group("/api/admin/app/type") diff --git a/api/service/dalle/service.go b/api/service/dalle/service.go index 29cf4491..d4f4ea33 100644 --- a/api/service/dalle/service.go +++ b/api/service/dalle/service.go @@ -49,7 +49,9 @@ func NewService(db *gorm.DB, manager *oss.UploaderManager, redisCli *redis.Clien // PushTask push a new mj task in to task queue func (s *Service) PushTask(task types.DallTask) { logger.Infof("add a new DALL-E task to the task list: %+v", task) - s.taskQueue.RPush(task) + if err := s.taskQueue.RPush(task); err != nil { + logger.Errorf("push dall-e task to queue failed: %v", err) + } } func (s *Service) Run() { diff --git a/api/service/jimeng/client.go b/api/service/jimeng/client.go new file mode 100644 index 00000000..291a0aae --- /dev/null +++ b/api/service/jimeng/client.go @@ -0,0 +1,332 @@ +package jimeng + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "sort" + "strings" + "time" + + "geekai/logger" +) + +var clientLogger = logger.GetLogger() + +// Client 即梦API客户端 +type Client struct { + accessKey string + secretKey string + region string + service string + baseURL string + httpClient *http.Client +} + +// NewClient 创建即梦API客户端 +func NewClient(accessKey, secretKey string) *Client { + return &Client{ + accessKey: accessKey, + secretKey: secretKey, + region: "cn-north-1", + service: "cv", + baseURL: "https://visual.volcengineapi.com", + httpClient: &http.Client{ + Timeout: 30 * time.Second, + }, + } +} + +// SubmitTask 提交任务 +func (c *Client) SubmitTask(req *SubmitTaskRequest) (*SubmitTaskResponse, error) { + // 构建请求URL + queryParams := map[string]string{ + "Action": "CVSync2AsyncSubmitTask", + "Version": "2022-08-31", + } + + reqURL := c.buildURL(queryParams) + + // 序列化请求体 + reqBody, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("marshal request body failed: %w", err) + } + + // 创建HTTP请求 + httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(reqBody)) + if err != nil { + return nil, fmt.Errorf("create http request failed: %w", err) + } + + // 设置请求头 + httpReq.Header.Set("Content-Type", "application/json") + + // 签名请求 + if err := c.signRequest(httpReq, reqBody); err != nil { + return nil, fmt.Errorf("sign request failed: %w", err) + } + + // 发送请求 + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("send http request failed: %w", err) + } + defer resp.Body.Close() + + // 读取响应 + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response body failed: %w", err) + } + + clientLogger.Infof("Jimeng SubmitTask Response: %s", string(respBody)) + + // 解析响应 + var result SubmitTaskResponse + if err := json.Unmarshal(respBody, &result); err != nil { + return nil, fmt.Errorf("unmarshal response failed: %w", err) + } + + return &result, nil +} + +// QueryTask 查询任务 +func (c *Client) QueryTask(req *QueryTaskRequest) (*QueryTaskResponse, error) { + // 构建请求URL + queryParams := map[string]string{ + "Action": "CVSync2AsyncGetResult", + "Version": "2022-08-31", + } + + reqURL := c.buildURL(queryParams) + + // 序列化请求体 + reqBody, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("marshal request body failed: %w", err) + } + + // 创建HTTP请求 + httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(reqBody)) + if err != nil { + return nil, fmt.Errorf("create http request failed: %w", err) + } + + // 设置请求头 + httpReq.Header.Set("Content-Type", "application/json") + + // 签名请求 + if err := c.signRequest(httpReq, reqBody); err != nil { + return nil, fmt.Errorf("sign request failed: %w", err) + } + + // 发送请求 + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("send http request failed: %w", err) + } + defer resp.Body.Close() + + // 读取响应 + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response body failed: %w", err) + } + + clientLogger.Infof("Jimeng QueryTask Response: %s", string(respBody)) + + // 解析响应 + var result QueryTaskResponse + if err := json.Unmarshal(respBody, &result); err != nil { + return nil, fmt.Errorf("unmarshal response failed: %w", err) + } + + return &result, nil +} + +// SubmitSyncTask 提交同步任务(仅用于文生图) +func (c *Client) SubmitSyncTask(req *SubmitTaskRequest) (*QueryTaskResponse, error) { + // 构建请求URL + queryParams := map[string]string{ + "Action": "CVProcess", + "Version": "2022-08-31", + } + + reqURL := c.buildURL(queryParams) + + // 序列化请求体 + reqBody, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("marshal request body failed: %w", err) + } + + // 创建HTTP请求 + httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(reqBody)) + if err != nil { + return nil, fmt.Errorf("create http request failed: %w", err) + } + + // 设置请求头 + httpReq.Header.Set("Content-Type", "application/json") + + // 签名请求 + if err := c.signRequest(httpReq, reqBody); err != nil { + return nil, fmt.Errorf("sign request failed: %w", err) + } + + // 发送请求 + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("send http request failed: %w", err) + } + defer resp.Body.Close() + + // 读取响应 + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response body failed: %w", err) + } + + clientLogger.Infof("Jimeng SubmitSyncTask Response: %s", string(respBody)) + + // 解析响应 + var result QueryTaskResponse + if err := json.Unmarshal(respBody, &result); err != nil { + return nil, fmt.Errorf("unmarshal response failed: %w", err) + } + + return &result, nil +} + +// buildURL 构建请求URL +func (c *Client) buildURL(queryParams map[string]string) string { + u, _ := url.Parse(c.baseURL) + q := u.Query() + for k, v := range queryParams { + q.Set(k, v) + } + u.RawQuery = q.Encode() + return u.String() +} + +// signRequest 签名请求 +func (c *Client) signRequest(req *http.Request, body []byte) error { + now := time.Now().UTC() + + // 设置基本头部 + req.Header.Set("X-Date", now.Format("20060102T150405Z")) + req.Header.Set("Host", req.URL.Host) + + // 计算内容哈希 + contentHash := sha256.Sum256(body) + req.Header.Set("X-Content-Sha256", hex.EncodeToString(contentHash[:])) + + // 构建签名字符串 + canonicalRequest := c.buildCanonicalRequest(req) + credentialScope := fmt.Sprintf("%s/%s/%s/request", now.Format("20060102"), c.region, c.service) + stringToSign := fmt.Sprintf("HMAC-SHA256\n%s\n%s\n%s", + now.Format("20060102T150405Z"), credentialScope, sha256Hash(canonicalRequest)) + + // 计算签名 + signature := c.calculateSignature(stringToSign, now) + + // 设置Authorization头部 + authorization := fmt.Sprintf("HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s", + c.accessKey, credentialScope, c.getSignedHeaders(req), signature) + req.Header.Set("Authorization", authorization) + + return nil +} + +// buildCanonicalRequest 构建规范请求 +func (c *Client) buildCanonicalRequest(req *http.Request) string { + // HTTP方法 + method := req.Method + + // 规范URI + uri := req.URL.Path + if uri == "" { + uri = "/" + } + + // 规范查询字符串 + query := req.URL.Query() + var queryParts []string + for k, v := range query { + for _, val := range v { + queryParts = append(queryParts, fmt.Sprintf("%s=%s", url.QueryEscape(k), url.QueryEscape(val))) + } + } + sort.Strings(queryParts) + canonicalQuery := strings.Join(queryParts, "&") + + // 规范头部 + var headerParts []string + headers := make(map[string]string) + for k, v := range req.Header { + key := strings.ToLower(k) + if len(v) > 0 { + headers[key] = strings.TrimSpace(v[0]) + } + } + + var headerKeys []string + for k := range headers { + headerKeys = append(headerKeys, k) + } + sort.Strings(headerKeys) + + for _, k := range headerKeys { + headerParts = append(headerParts, fmt.Sprintf("%s:%s", k, headers[k])) + } + canonicalHeaders := strings.Join(headerParts, "\n") + "\n" + + // 签名头部 + signedHeaders := c.getSignedHeaders(req) + + // 载荷哈希 + payloadHash := req.Header.Get("X-Content-Sha256") + + return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", + method, uri, canonicalQuery, canonicalHeaders, signedHeaders, payloadHash) +} + +// getSignedHeaders 获取签名头部 +func (c *Client) getSignedHeaders(req *http.Request) string { + var headers []string + for k := range req.Header { + headers = append(headers, strings.ToLower(k)) + } + sort.Strings(headers) + return strings.Join(headers, ";") +} + +// calculateSignature 计算签名 +func (c *Client) calculateSignature(stringToSign string, t time.Time) string { + kDate := hmacSha256([]byte("HMAC-SHA256"+c.secretKey), []byte(t.Format("20060102"))) + kRegion := hmacSha256(kDate, []byte(c.region)) + kService := hmacSha256(kRegion, []byte(c.service)) + kSigning := hmacSha256(kService, []byte("request")) + signature := hmacSha256(kSigning, []byte(stringToSign)) + return hex.EncodeToString(signature) +} + +// hmacSha256 计算HMAC-SHA256 +func hmacSha256(key []byte, data []byte) []byte { + h := hmac.New(sha256.New, key) + h.Write(data) + return h.Sum(nil) +} + +// sha256Hash 计算SHA256哈希 +func sha256Hash(data string) string { + hash := sha256.Sum256([]byte(data)) + return hex.EncodeToString(hash[:]) +} diff --git a/api/service/jimeng/consumer.go b/api/service/jimeng/consumer.go new file mode 100644 index 00000000..00f20061 --- /dev/null +++ b/api/service/jimeng/consumer.go @@ -0,0 +1,177 @@ +package jimeng + +import ( + "context" + "time" + + "geekai/logger" + "geekai/store/model" +) + +var jimengLogger = logger.GetLogger() + +// Consumer 即梦任务消费者 +type Consumer struct { + service *Service + ctx context.Context + cancel context.CancelFunc +} + +// NewConsumer 创建即梦任务消费者 +func NewConsumer(service *Service) *Consumer { + ctx, cancel := context.WithCancel(context.Background()) + return &Consumer{ + service: service, + ctx: ctx, + cancel: cancel, + } +} + +// Start 启动消费者 +func (c *Consumer) Start() { + jimengLogger.Info("Starting Jimeng task consumer...") + go c.consume() +} + +// Stop 停止消费者 +func (c *Consumer) Stop() { + jimengLogger.Info("Stopping Jimeng task consumer...") + c.cancel() +} + +// consume 消费任务 +func (c *Consumer) consume() { + for { + select { + case <-c.ctx.Done(): + jimengLogger.Info("Jimeng task consumer stopped") + return + default: + c.processTask() + } + } +} + +// processTask 处理任务 +func (c *Consumer) processTask() { + // 从队列中获取任务 + var task map[string]interface{} + if err := c.service.taskQueue.LPop(&task); err != nil { + // 队列为空,等待1秒后重试 + time.Sleep(time.Second) + return + } + + // 解析任务 + jobIdFloat, ok := task["job_id"].(float64) + if !ok { + jimengLogger.Errorf("invalid job_id in task: %v", task) + return + } + jobId := uint(jobIdFloat) + + taskType, ok := task["type"].(string) + if !ok { + jimengLogger.Errorf("invalid task type in task: %v", task) + return + } + + jimengLogger.Infof("Processing Jimeng task: job_id=%d, type=%s", jobId, taskType) + + // 处理任务 + if err := c.service.ProcessTask(jobId); err != nil { + jimengLogger.Errorf("process jimeng task failed: job_id=%d, error=%v", jobId, err) + + // 任务失败,直接标记为失败状态,不进行重试 + c.service.UpdateJobStatus(jobId, model.JimengJobStatusFailed, err.Error()) + } else { + jimengLogger.Infof("Jimeng task processed successfully: job_id=%d", jobId) + } +} + +// TaskQueueStatus 任务队列状态 +type TaskQueueStatus struct { + QueueLength int `json:"queue_length"` + ActiveTasks int `json:"active_tasks"` +} + +// GetQueueStatus 获取队列状态 +func (c *Consumer) GetQueueStatus() (*TaskQueueStatus, error) { + // 获取队列长度 + length, err := c.service.taskQueue.Size() + if err != nil { + return nil, err + } + + // 获取活跃任务数(正在处理的任务) + activeTasks, err := c.service.GetPendingTaskCount(0) // 0表示所有用户 + if err != nil { + activeTasks = 0 + } + + return &TaskQueueStatus{ + QueueLength: int(length), + ActiveTasks: int(activeTasks), + }, nil +} + +// MonitorQueue 监控队列状态 +func (c *Consumer) MonitorQueue() { + ticker := time.NewTicker(30 * time.Second) // 每30秒监控一次 + defer ticker.Stop() + + for { + select { + case <-c.ctx.Done(): + return + case <-ticker.C: + status, err := c.GetQueueStatus() + if err != nil { + jimengLogger.Errorf("get queue status failed: %v", err) + continue + } + + if status.QueueLength > 0 || status.ActiveTasks > 0 { + jimengLogger.Infof("Jimeng queue status: queue_length=%d, active_tasks=%d", + status.QueueLength, status.ActiveTasks) + } + } + } +} + +// PushTaskToQueue 推送任务到队列(用于手动重试) +func (c *Consumer) PushTaskToQueue(task map[string]interface{}) error { + return c.service.taskQueue.RPush(task) +} + +// GetTaskStats 获取任务统计信息 +func (c *Consumer) GetTaskStats() (map[string]interface{}, error) { + type StatResult struct { + Status string `json:"status"` + Count int64 `json:"count"` + } + + var stats []StatResult + err := c.service.db.Model(&model.JimengJob{}). + Select("status, COUNT(*) as count"). + Group("status"). + Find(&stats).Error + if err != nil { + return nil, err + } + + result := map[string]interface{}{ + "total": int64(0), + "completed": int64(0), + "processing": int64(0), + "failed": int64(0), + "pending": int64(0), + } + + for _, stat := range stats { + result["total"] = result["total"].(int64) + stat.Count + result[stat.Status] = stat.Count + } + + return result, nil +} \ No newline at end of file diff --git a/api/service/jimeng/service.go b/api/service/jimeng/service.go new file mode 100644 index 00000000..b011f55f --- /dev/null +++ b/api/service/jimeng/service.go @@ -0,0 +1,633 @@ +package jimeng + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "gorm.io/gorm" + + "geekai/logger" + "geekai/store" + "geekai/store/model" + "geekai/utils" + + "github.com/go-redis/redis/v8" +) + +var serviceLogger = logger.GetLogger() + +// Service 即梦服务 +type Service struct { + db *gorm.DB + redis *redis.Client + taskQueue *store.RedisQueue + client *Client +} + +// NewService 创建即梦服务 +func NewService(db *gorm.DB, redisCli *redis.Client, client *Client) *Service { + taskQueue := store.NewRedisQueue("JimengTaskQueue", redisCli) + return &Service{ + db: db, + redis: redisCli, + taskQueue: taskQueue, + client: client, + } +} + +// CreateTask 创建任务 +func (s *Service) CreateTask(userId uint, req *CreateTaskRequest) (*model.JimengJob, error) { + // 生成任务ID + taskId := utils.RandString(20) + + // 序列化任务参数 + paramsJson, err := json.Marshal(req.Params) + if err != nil { + return nil, fmt.Errorf("marshal task params failed: %w", err) + } + + // 创建任务记录 + job := &model.JimengJob{ + UserId: userId, + TaskId: taskId, + Type: req.Type, + ReqKey: req.ReqKey, + Prompt: req.Prompt, + TaskParams: string(paramsJson), + Status: model.JimengJobStatusPending, + Power: req.Power, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + // 保存到数据库 + if err := s.db.Create(job).Error; err != nil { + return nil, fmt.Errorf("create jimeng job failed: %w", err) + } + + // 推送到任务队列 + task := map[string]any{ + "job_id": job.Id, + "type": job.Type, + } + if err := s.taskQueue.RPush(task); err != nil { + return nil, fmt.Errorf("push jimeng task to queue failed: %w", err) + } + + return job, nil +} + +// ProcessTask 处理任务 +func (s *Service) ProcessTask(jobId uint) error { + // 获取任务记录 + var job model.JimengJob + if err := s.db.First(&job, jobId).Error; err != nil { + return fmt.Errorf("get jimeng job failed: %w", err) + } + + // 更新任务状态为处理中 + if err := s.UpdateJobStatus(job.Id, model.JimengJobStatusProcessing, ""); err != nil { + return fmt.Errorf("update job status failed: %w", err) + } + + // 根据任务类型处理 + switch job.Type { + case model.JimengJobTypeTextToImage: + return s.processTextToImage(&job) + case model.JimengJobTypeImageToImagePortrait: + return s.processImageToImagePortrait(&job) + case model.JimengJobTypeImageEdit: + return s.processImageEdit(&job) + case model.JimengJobTypeImageEffects: + return s.processImageEffects(&job) + case model.JimengJobTypeTextToVideo: + return s.processTextToVideo(&job) + case model.JimengJobTypeImageToVideo: + return s.processImageToVideo(&job) + default: + return fmt.Errorf("unsupported task type: %s", job.Type) + } +} + +// processTextToImage 处理文生图任务 +func (s *Service) processTextToImage(job *model.JimengJob) error { + // 解析任务参数 + var params map[string]any + if err := json.Unmarshal([]byte(job.TaskParams), ¶ms); err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("parse task params failed: %v", err)) + } + + // 构建请求 + req := &SubmitTaskRequest{ + ReqKey: job.ReqKey, + Prompt: job.Prompt, + } + + // 设置参数 + if seed, ok := params["seed"]; ok { + if seedVal, err := strconv.ParseInt(fmt.Sprintf("%.0f", seed), 10, 64); err == nil { + req.Seed = seedVal + } + } + if scale, ok := params["scale"]; ok { + if scaleVal, ok := scale.(float64); ok { + req.Scale = scaleVal + } + } + if width, ok := params["width"]; ok { + if widthVal, ok := width.(float64); ok { + req.Width = int(widthVal) + } + } + if height, ok := params["height"]; ok { + if heightVal, ok := height.(float64); ok { + req.Height = int(heightVal) + } + } + if usePreLlm, ok := params["use_pre_llm"]; ok { + if usePreLlmVal, ok := usePreLlm.(bool); ok { + req.UsePreLLM = usePreLlmVal + } + } + + // 提交异步任务 + resp, err := s.client.SubmitTask(req) + if err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %v", err)) + } + + if resp.Code != 10000 { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %s", resp.Message)) + } + + // 更新任务ID和原始数据 + rawData, _ := json.Marshal(resp) + if err := s.db.Model(&model.JimengJob{}).Where("id = ?", job.Id).Updates(map[string]any{ + "task_id": resp.Data.TaskId, + "raw_data": string(rawData), + "updated_at": time.Now(), + }).Error; err != nil { + serviceLogger.Errorf("update jimeng job task_id failed: %v", err) + } + + // 开始轮询任务状态 + return s.pollTaskStatus(job.Id, resp.Data.TaskId, job.ReqKey) +} + +// processImageToImagePortrait 处理图生图人像写真任务 +func (s *Service) processImageToImagePortrait(job *model.JimengJob) error { + // 解析任务参数 + var params map[string]any + if err := json.Unmarshal([]byte(job.TaskParams), ¶ms); err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("parse task params failed: %v", err)) + } + + // 构建请求 + req := &SubmitTaskRequest{ + ReqKey: job.ReqKey, + Prompt: job.Prompt, + } + + // 设置图像输入 + if imageInput, ok := params["image_input"].(string); ok { + req.ImageInput = imageInput + } + + // 设置其他参数 + if gpen, ok := params["gpen"]; ok { + if gpenVal, ok := gpen.(float64); ok { + req.Gpen = gpenVal + } + } + if skin, ok := params["skin"]; ok { + if skinVal, ok := skin.(float64); ok { + req.Skin = skinVal + } + } + if skinUnifi, ok := params["skin_unifi"]; ok { + if skinUnifiVal, ok := skinUnifi.(float64); ok { + req.SkinUnifi = skinUnifiVal + } + } + if genMode, ok := params["gen_mode"].(string); ok { + req.GenMode = genMode + } + if width, ok := params["width"]; ok { + if widthVal, ok := width.(float64); ok { + req.Width = int(widthVal) + } + } + if height, ok := params["height"]; ok { + if heightVal, ok := height.(float64); ok { + req.Height = int(heightVal) + } + } + if seed, ok := params["seed"]; ok { + if seedVal, err := strconv.ParseInt(fmt.Sprintf("%.0f", seed), 10, 64); err == nil { + req.Seed = seedVal + } + } + + // 提交异步任务 + resp, err := s.client.SubmitTask(req) + if err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %v", err)) + } + + if resp.Code != 10000 { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %s", resp.Message)) + } + + // 更新任务ID和原始数据 + rawData, _ := json.Marshal(resp) + if err := s.db.Model(&model.JimengJob{}).Where("id = ?", job.Id).Updates(map[string]any{ + "task_id": resp.Data.TaskId, + "raw_data": string(rawData), + "updated_at": time.Now(), + }).Error; err != nil { + serviceLogger.Errorf("update jimeng job task_id failed: %v", err) + } + + // 开始轮询任务状态 + return s.pollTaskStatus(job.Id, resp.Data.TaskId, job.ReqKey) +} + +// processImageEdit 处理图像编辑任务 +func (s *Service) processImageEdit(job *model.JimengJob) error { + // 解析任务参数 + var params map[string]any + if err := json.Unmarshal([]byte(job.TaskParams), ¶ms); err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("parse task params failed: %v", err)) + } + + // 构建请求 + req := &SubmitTaskRequest{ + ReqKey: job.ReqKey, + Prompt: job.Prompt, + } + + // 设置图像输入 + if imageUrls, ok := params["image_urls"].([]any); ok { + for _, url := range imageUrls { + if urlStr, ok := url.(string); ok { + req.ImageUrls = append(req.ImageUrls, urlStr) + } + } + } + if binaryData, ok := params["binary_data_base64"].([]any); ok { + for _, data := range binaryData { + if dataStr, ok := data.(string); ok { + req.BinaryDataBase64 = append(req.BinaryDataBase64, dataStr) + } + } + } + + // 设置其他参数 + if seed, ok := params["seed"]; ok { + if seedVal, err := strconv.ParseInt(fmt.Sprintf("%.0f", seed), 10, 64); err == nil { + req.Seed = seedVal + } + } + if scale, ok := params["scale"]; ok { + if scaleVal, ok := scale.(float64); ok { + req.Scale = scaleVal + } + } + + // 提交异步任务 + resp, err := s.client.SubmitTask(req) + if err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %v", err)) + } + + if resp.Code != 10000 { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %s", resp.Message)) + } + + // 更新任务ID和原始数据 + rawData, _ := json.Marshal(resp) + if err := s.db.Model(&model.JimengJob{}).Where("id = ?", job.Id).Updates(map[string]any{ + "task_id": resp.Data.TaskId, + "raw_data": string(rawData), + "updated_at": time.Now(), + }).Error; err != nil { + serviceLogger.Errorf("update jimeng job task_id failed: %v", err) + } + + // 开始轮询任务状态 + return s.pollTaskStatus(job.Id, resp.Data.TaskId, job.ReqKey) +} + +// processImageEffects 处理图像特效任务 +func (s *Service) processImageEffects(job *model.JimengJob) error { + // 解析任务参数 + var params map[string]any + if err := json.Unmarshal([]byte(job.TaskParams), ¶ms); err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("parse task params failed: %v", err)) + } + + // 构建请求 + req := &SubmitTaskRequest{ + ReqKey: job.ReqKey, + } + + // 设置图像输入 + if imageInput1, ok := params["image_input1"].(string); ok { + req.ImageInput1 = imageInput1 + } + if templateId, ok := params["template_id"].(string); ok { + req.TemplateId = templateId + } + if width, ok := params["width"]; ok { + if widthVal, ok := width.(float64); ok { + req.Width = int(widthVal) + } + } + if height, ok := params["height"]; ok { + if heightVal, ok := height.(float64); ok { + req.Height = int(heightVal) + } + } + + // 提交异步任务 + resp, err := s.client.SubmitTask(req) + if err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %v", err)) + } + + if resp.Code != 10000 { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %s", resp.Message)) + } + + // 更新任务ID和原始数据 + rawData, _ := json.Marshal(resp) + if err := s.db.Model(&model.JimengJob{}).Where("id = ?", job.Id).Updates(map[string]any{ + "task_id": resp.Data.TaskId, + "raw_data": string(rawData), + "updated_at": time.Now(), + }).Error; err != nil { + serviceLogger.Errorf("update jimeng job task_id failed: %v", err) + } + + // 开始轮询任务状态 + return s.pollTaskStatus(job.Id, resp.Data.TaskId, job.ReqKey) +} + +// processTextToVideo 处理文生视频任务 +func (s *Service) processTextToVideo(job *model.JimengJob) error { + // 解析任务参数 + var params map[string]any + if err := json.Unmarshal([]byte(job.TaskParams), ¶ms); err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("parse task params failed: %v", err)) + } + + // 构建请求 + req := &SubmitTaskRequest{ + ReqKey: job.ReqKey, + Prompt: job.Prompt, + } + + // 设置参数 + if seed, ok := params["seed"]; ok { + if seedVal, err := strconv.ParseInt(fmt.Sprintf("%.0f", seed), 10, 64); err == nil { + req.Seed = seedVal + } + } + if aspectRatio, ok := params["aspect_ratio"].(string); ok { + req.AspectRatio = aspectRatio + } + + // 提交异步任务 + resp, err := s.client.SubmitTask(req) + if err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %v", err)) + } + + if resp.Code != 10000 { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %s", resp.Message)) + } + + // 更新任务ID和原始数据 + rawData, _ := json.Marshal(resp) + if err := s.db.Model(&model.JimengJob{}).Where("id = ?", job.Id).Updates(map[string]any{ + "task_id": resp.Data.TaskId, + "raw_data": string(rawData), + "updated_at": time.Now(), + }).Error; err != nil { + serviceLogger.Errorf("update jimeng job task_id failed: %v", err) + } + + // 开始轮询任务状态 + return s.pollTaskStatus(job.Id, resp.Data.TaskId, job.ReqKey) +} + +// processImageToVideo 处理图生视频任务 +func (s *Service) processImageToVideo(job *model.JimengJob) error { + // 解析任务参数 + var params map[string]any + if err := json.Unmarshal([]byte(job.TaskParams), ¶ms); err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("parse task params failed: %v", err)) + } + + // 构建请求 + req := &SubmitTaskRequest{ + ReqKey: job.ReqKey, + Prompt: job.Prompt, + } + + // 设置图像输入 + if imageUrls, ok := params["image_urls"].([]any); ok { + for _, url := range imageUrls { + if urlStr, ok := url.(string); ok { + req.ImageUrls = append(req.ImageUrls, urlStr) + } + } + } + if binaryData, ok := params["binary_data_base64"].([]any); ok { + for _, data := range binaryData { + if dataStr, ok := data.(string); ok { + req.BinaryDataBase64 = append(req.BinaryDataBase64, dataStr) + } + } + } + + // 设置其他参数 + if seed, ok := params["seed"]; ok { + if seedVal, err := strconv.ParseInt(fmt.Sprintf("%.0f", seed), 10, 64); err == nil { + req.Seed = seedVal + } + } + if aspectRatio, ok := params["aspect_ratio"].(string); ok { + req.AspectRatio = aspectRatio + } + + // 提交异步任务 + resp, err := s.client.SubmitTask(req) + if err != nil { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %v", err)) + } + + if resp.Code != 10000 { + return s.handleTaskError(job.Id, fmt.Sprintf("submit task failed: %s", resp.Message)) + } + + // 更新任务ID和原始数据 + rawData, _ := json.Marshal(resp) + if err := s.db.Model(&model.JimengJob{}).Where("id = ?", job.Id).Updates(map[string]any{ + "task_id": resp.Data.TaskId, + "raw_data": string(rawData), + "updated_at": time.Now(), + }).Error; err != nil { + serviceLogger.Errorf("update jimeng job task_id failed: %v", err) + } + + // 开始轮询任务状态 + return s.pollTaskStatus(job.Id, resp.Data.TaskId, job.ReqKey) +} + +// pollTaskStatus 轮询任务状态 +func (s *Service) pollTaskStatus(jobId uint, taskId, reqKey string) error { + maxRetries := 60 // 最大重试次数,60次 * 5秒 = 5分钟 + retryCount := 0 + + for retryCount < maxRetries { + time.Sleep(5 * time.Second) // 等待5秒 + + // 查询任务状态 + resp, err := s.client.QueryTask(&QueryTaskRequest{ + ReqKey: reqKey, + TaskId: taskId, + ReqJson: `{"return_url":true}`, + }) + + if err != nil { + serviceLogger.Errorf("query jimeng task status failed: %v", err) + retryCount++ + continue + } + + // 更新原始数据 + rawData, _ := json.Marshal(resp) + s.db.Model(&model.JimengJob{}).Where("id = ?", jobId).Update("raw_data", string(rawData)) + + if resp.Code != 10000 { + return s.handleTaskError(jobId, fmt.Sprintf("query task failed: %s", resp.Message)) + } + + switch resp.Data.Status { + case TaskStatusDone: + // 任务完成,更新结果 + updates := map[string]any{ + "status": model.JimengJobStatusCompleted, + "progress": 100, + "updated_at": time.Now(), + } + + // 设置结果URL + if len(resp.Data.ImageUrls) > 0 { + updates["img_url"] = resp.Data.ImageUrls[0] + } + if resp.Data.VideoUrl != "" { + updates["video_url"] = resp.Data.VideoUrl + } + + return s.db.Model(&model.JimengJob{}).Where("id = ?", jobId).Updates(updates).Error + + case TaskStatusInQueue: + // 任务在队列中 + s.UpdateJobProgress(jobId, 10) + + case TaskStatusGenerating: + // 任务处理中 + s.UpdateJobProgress(jobId, 50) + + case TaskStatusNotFound, TaskStatusExpired: + // 任务未找到或已过期 + return s.handleTaskError(jobId, fmt.Sprintf("task not found or expired: %s", resp.Data.Status)) + + default: + serviceLogger.Warnf("unknown task status: %s", resp.Data.Status) + } + + retryCount++ + } + + // 超时处理 + return s.handleTaskError(jobId, "task timeout") +} + +// UpdateJobStatus 更新任务状态 +func (s *Service) UpdateJobStatus(jobId uint, status, errMsg string) error { + updates := map[string]any{ + "status": status, + "updated_at": time.Now(), + } + if errMsg != "" { + updates["err_msg"] = errMsg + } + return s.db.Model(&model.JimengJob{}).Where("id = ?", jobId).Updates(updates).Error +} + +// UpdateJobProgress 更新任务进度 +func (s *Service) UpdateJobProgress(jobId uint, progress int) error { + return s.db.Model(&model.JimengJob{}).Where("id = ?", jobId).Updates(map[string]any{ + "progress": progress, + "updated_at": time.Now(), + }).Error +} + +// handleTaskError 处理任务错误 +func (s *Service) handleTaskError(jobId uint, errMsg string) error { + serviceLogger.Errorf("Jimeng task error (job_id: %d): %s", jobId, errMsg) + return s.UpdateJobStatus(jobId, model.JimengJobStatusFailed, errMsg) +} + +// GetJob 获取任务 +func (s *Service) GetJob(jobId uint) (*model.JimengJob, error) { + var job model.JimengJob + if err := s.db.First(&job, jobId).Error; err != nil { + return nil, err + } + return &job, nil +} + +// GetUserJobs 获取用户任务列表 +func (s *Service) GetUserJobs(userId uint, page, pageSize int) ([]*model.JimengJob, int64, error) { + var jobs []*model.JimengJob + var total int64 + + query := s.db.Model(&model.JimengJob{}).Where("user_id = ?", userId) + + // 统计总数 + if err := query.Count(&total).Error; err != nil { + return nil, 0, err + } + + // 分页查询 + offset := (page - 1) * pageSize + if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&jobs).Error; err != nil { + return nil, 0, err + } + + return jobs, total, nil +} + +// GetPendingTaskCount 获取用户未完成任务数量 +func (s *Service) GetPendingTaskCount(userId uint) (int64, error) { + var count int64 + err := s.db.Model(&model.JimengJob{}).Where("user_id = ? AND status IN (?)", userId, + []string{model.JimengJobStatusPending, model.JimengJobStatusProcessing}).Count(&count).Error + return count, err +} + +// DeleteJob 删除任务 +func (s *Service) DeleteJob(jobId uint, userId uint) error { + return s.db.Where("id = ? AND user_id = ?", jobId, userId).Delete(&model.JimengJob{}).Error +} + +// PushTaskToQueue 推送任务到队列 +func (s *Service) PushTaskToQueue(task map[string]interface{}) error { + return s.taskQueue.RPush(task) +} diff --git a/api/service/jimeng/types.go b/api/service/jimeng/types.go new file mode 100644 index 00000000..029e0f91 --- /dev/null +++ b/api/service/jimeng/types.go @@ -0,0 +1,163 @@ +package jimeng + +import "time" + +// SubmitTaskRequest 提交任务请求 +type SubmitTaskRequest struct { + ReqKey string `json:"req_key"` + // 文生图参数 + Prompt string `json:"prompt,omitempty"` + Seed int64 `json:"seed,omitempty"` + Scale float64 `json:"scale,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + UsePreLLM bool `json:"use_pre_llm,omitempty"` + // 图生图参数 + ImageInput string `json:"image_input,omitempty"` + ImageUrls []string `json:"image_urls,omitempty"` + BinaryDataBase64 []string `json:"binary_data_base64,omitempty"` + Gpen float64 `json:"gpen,omitempty"` + Skin float64 `json:"skin,omitempty"` + SkinUnifi float64 `json:"skin_unifi,omitempty"` + GenMode string `json:"gen_mode,omitempty"` + // 图像编辑参数 + // 图像特效参数 + ImageInput1 string `json:"image_input1,omitempty"` + TemplateId string `json:"template_id,omitempty"` + // 视频生成参数 + AspectRatio string `json:"aspect_ratio,omitempty"` +} + +// SubmitTaskResponse 提交任务响应 +type SubmitTaskResponse struct { + Code int `json:"code"` + Message string `json:"message"` + RequestId string `json:"request_id"` + Status int `json:"status"` + TimeElapsed string `json:"time_elapsed"` + Data struct { + TaskId string `json:"task_id"` + } `json:"data"` +} + +// QueryTaskRequest 查询任务请求 +type QueryTaskRequest struct { + ReqKey string `json:"req_key"` + TaskId string `json:"task_id"` + ReqJson string `json:"req_json,omitempty"` +} + +// QueryTaskResponse 查询任务响应 +type QueryTaskResponse struct { + Code int `json:"code"` + Message string `json:"message"` + RequestId string `json:"request_id"` + Status int `json:"status"` + TimeElapsed string `json:"time_elapsed"` + Data struct { + AlgorithmBaseResp struct { + StatusCode int `json:"status_code"` + StatusMessage string `json:"status_message"` + } `json:"algorithm_base_resp"` + BinaryDataBase64 []string `json:"binary_data_base64"` + ImageUrls []string `json:"image_urls"` + VideoUrl string `json:"video_url"` + RespData string `json:"resp_data"` + Status string `json:"status"` + LlmResult string `json:"llm_result"` + PeResult string `json:"pe_result"` + PredictTagsResult string `json:"predict_tags_result"` + RephraserResult string `json:"rephraser_result"` + VlmResult string `json:"vlm_result"` + InferCtx interface{} `json:"infer_ctx"` + } `json:"data"` +} + +// TaskStatus 任务状态 +const ( + TaskStatusInQueue = "in_queue" // 任务已提交 + TaskStatusGenerating = "generating" // 任务处理中 + TaskStatusDone = "done" // 处理完成 + TaskStatusNotFound = "not_found" // 任务未找到 + TaskStatusExpired = "expired" // 任务已过期 +) + +// CreateTaskRequest 创建任务请求 +type CreateTaskRequest struct { + Type string `json:"type"` + Prompt string `json:"prompt"` + Params map[string]interface{} `json:"params"` + ReqKey string `json:"req_key"` + ImageUrls []string `json:"image_urls,omitempty"` + Power int `json:"power,omitempty"` +} + +// TaskInfo 任务信息 +type TaskInfo struct { + Id uint `json:"id"` + UserId uint `json:"user_id"` + TaskId string `json:"task_id"` + Type string `json:"type"` + ReqKey string `json:"req_key"` + Prompt string `json:"prompt"` + TaskParams string `json:"task_params"` + ImgURL string `json:"img_url"` + VideoURL string `json:"video_url"` + Progress int `json:"progress"` + Status string `json:"status"` + ErrMsg string `json:"err_msg"` + Power int `json:"power"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// LogoInfo 水印信息 +type LogoInfo struct { + AddLogo bool `json:"add_logo"` + Position int `json:"position"` + Language int `json:"language"` + Opacity float64 `json:"opacity"` + LogoTextContent string `json:"logo_text_content"` +} + +// ReqJsonConfig 查询配置 +type ReqJsonConfig struct { + ReturnUrl bool `json:"return_url"` + LogoInfo *LogoInfo `json:"logo_info,omitempty"` +} + +// ImageEffectTemplate 图像特效模板 +const ( + TemplateIdFelt3DPolaroid = "felt_3d_polaroid" // 毛毡3d拍立得风格 + TemplateIdMyWorld = "my_world" // 像素世界风 + TemplateIdMyWorldUniversal = "my_world_universal" // 像素世界-万物通用版 + TemplateIdPlasticBubbleFigure = "plastic_bubble_figure" // 盲盒玩偶风 + TemplateIdPlasticBubbleFigureCartoon = "plastic_bubble_figure_cartoon_text" // 塑料泡罩人偶-文字卡头版 + TemplateIdFurryDreamDoll = "furry_dream_doll" // 毛绒玩偶风 + TemplateIdMicroLandscapeMiniWorld = "micro_landscape_mini_world" // 迷你世界玩偶风 + TemplateIdMicroLandscapeProfessional = "micro_landscape_mini_world_professional" // 微型景观小世界-职业版 + TemplateIdAcrylicOrnaments = "acrylic_ornaments" // 亚克力挂饰 + TemplateIdFeltKeychain = "felt_keychain" // 毛毡钥匙扣 + TemplateIdLofiPixelCharacter = "lofi_pixel_character_mini_card" // Lofi像素人物小卡 + TemplateIdAngelFigurine = "angel_figurine" // 天使形象手办 + TemplateIdLyingInFluffyBelly = "lying_in_fluffy_belly" // 躺在毛茸茸肚皮里 + TemplateIdGlassBall = "glass_ball" // 玻璃球 +) + +// AspectRatio 视频宽高比 +const ( + AspectRatio16_9 = "16:9" // 1280×720 + AspectRatio9_16 = "9:16" // 720×1280 + AspectRatio1_1 = "1:1" // 960×960 + AspectRatio4_3 = "4:3" // 960×720 + AspectRatio3_4 = "3:4" // 720×960 + AspectRatio21_9 = "21:9" // 1680×720 + AspectRatio9_21 = "9:21" // 720×1680 +) + +// GenMode 生成模式 +const ( + GenModeCreative = "creative" // 提示词模式 + GenModeReference = "reference" // 全参考模式 + GenModeReferenceChar = "reference_char" // 人物参考模式 +) \ No newline at end of file diff --git a/api/service/mj/service.go b/api/service/mj/service.go index 532b82fd..9253d84b 100644 --- a/api/service/mj/service.go +++ b/api/service/mj/service.go @@ -212,7 +212,9 @@ func (s *Service) DownloadImages() { // PushTask push a new mj task in to task queue func (s *Service) PushTask(task types.MjTask) { logger.Debugf("add a new MidJourney task to the task list: %+v", task) - s.taskQueue.RPush(task) + if err := s.taskQueue.RPush(task); err != nil { + logger.Errorf("push mj task to queue failed: %v", err) + } } // SyncTaskProgress 异步拉取任务 diff --git a/api/service/sd/service.go b/api/service/sd/service.go index 2047923e..f9e4437c 100644 --- a/api/service/sd/service.go +++ b/api/service/sd/service.go @@ -253,7 +253,9 @@ func (s *Service) checkTaskProgress(apiKey model.ApiKey) (*TaskProgressResp, err func (s *Service) PushTask(task types.SdTask) { logger.Debugf("add a new MidJourney task to the task list: %+v", task) - s.taskQueue.RPush(task) + if err := s.taskQueue.RPush(task); err != nil { + logger.Errorf("push sd task to queue failed: %v", err) + } } // CheckTaskStatus 检查任务状态,自动删除过期或者失败的任务 diff --git a/api/service/suno/service.go b/api/service/suno/service.go index 59e2aecc..2a5a457f 100644 --- a/api/service/suno/service.go +++ b/api/service/suno/service.go @@ -51,7 +51,9 @@ func NewService(db *gorm.DB, manager *oss.UploaderManager, redisCli *redis.Clien func (s *Service) PushTask(task types.SunoTask) { logger.Infof("add a new Suno task to the task list: %+v", task) - s.taskQueue.RPush(task) + if err := s.taskQueue.RPush(task); err != nil { + logger.Errorf("push suno task to queue failed: %v", err) + } } func (s *Service) Run() { diff --git a/api/service/video/video.go b/api/service/video/video.go index 42628c13..9b82b26a 100644 --- a/api/service/video/video.go +++ b/api/service/video/video.go @@ -51,7 +51,9 @@ func NewService(db *gorm.DB, manager *oss.UploaderManager, redisCli *redis.Clien func (s *Service) PushTask(task types.VideoTask) { logger.Infof("add a new Video task to the task list: %+v", task) - s.taskQueue.RPush(task) + if err := s.taskQueue.RPush(task); err != nil { + logger.Errorf("push video task to queue failed: %v", err) + } } func (s *Service) Run() { diff --git a/api/store/model/jimeng_job.go b/api/store/model/jimeng_job.go new file mode 100644 index 00000000..5a16b027 --- /dev/null +++ b/api/store/model/jimeng_job.go @@ -0,0 +1,58 @@ +package model + +import ( + "time" +) + +// JimengJob 即梦AI任务模型 +type JimengJob struct { + Id uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + UserId uint `gorm:"column:user_id;type:int;not null;index;comment:用户ID" json:"user_id"` + TaskId string `gorm:"column:task_id;type:varchar(100);not null;index;comment:任务ID" json:"task_id"` + Type string `gorm:"column:type;type:varchar(50);not null;comment:任务类型" json:"type"` + ReqKey string `gorm:"column:req_key;type:varchar(100);comment:请求Key" json:"req_key"` + Prompt string `gorm:"column:prompt;type:text;comment:提示词" json:"prompt"` + TaskParams string `gorm:"column:task_params;type:text;comment:任务参数JSON" json:"task_params"` + ImgURL string `gorm:"column:img_url;type:varchar(1024);comment:图片或封面URL" json:"img_url"` + VideoURL string `gorm:"column:video_url;type:varchar(1024);comment:视频URL" json:"video_url"` + RawData string `gorm:"column:raw_data;type:text;comment:原始API响应" json:"raw_data"` + Progress int `gorm:"column:progress;type:int;default:0;comment:进度百分比" json:"progress"` + Status string `gorm:"column:status;type:varchar(20);default:'pending';comment:任务状态" json:"status"` + ErrMsg string `gorm:"column:err_msg;type:varchar(1024);comment:错误信息" json:"err_msg"` + Power int `gorm:"column:power;type:int;default:0;comment:消耗算力" json:"power"` + CreatedAt time.Time `gorm:"column:created_at;type:datetime;not null;comment:创建时间" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;type:datetime;not null;comment:更新时间" json:"updated_at"` +} + +// JimengJobStatus 即梦任务状态常量 +const ( + JimengJobStatusPending = "pending" + JimengJobStatusProcessing = "processing" + JimengJobStatusCompleted = "completed" + JimengJobStatusFailed = "failed" +) + +// JimengJobType 即梦任务类型常量 +const ( + JimengJobTypeTextToImage = "text_to_image" // 文生图 + JimengJobTypeImageToImagePortrait = "image_to_image_portrait" // 图生图人像写真 + JimengJobTypeImageEdit = "image_edit" // 图像编辑 + JimengJobTypeImageEffects = "image_effects" // 图像特效 + JimengJobTypeTextToVideo = "text_to_video" // 文生视频 + JimengJobTypeImageToVideo = "image_to_video" // 图生视频 +) + +// ReqKey 常量定义 +const ( + ReqKeyTextToImage = "high_aes_general_v30l_zt2i" // 文生图 + ReqKeyImageToImagePortrait = "i2i_portrait_photo" // 图生图人像写真 + ReqKeyImageEdit = "seededit_v3.0" // 图像编辑 + ReqKeyImageEffects = "i2i_multi_style_zx2x" // 图像特效 + ReqKeyTextToVideo = "jimeng_vgfm_t2v_l20" // 文生视频 + ReqKeyImageToVideo = "jimeng_vgfm_i2v_l20" // 图生视频 +) + +// TableName 返回数据表名称 +func (JimengJob) TableName() string { + return "chatgpt_jimeng_jobs" +} diff --git a/api/store/redis_queue.go b/api/store/redis_queue.go index 3251eb57..71e6378b 100644 --- a/api/store/redis_queue.go +++ b/api/store/redis_queue.go @@ -10,6 +10,7 @@ package store import ( "context" "geekai/utils" + "github.com/go-redis/redis/v8" ) @@ -23,15 +24,15 @@ func NewRedisQueue(name string, client *redis.Client) *RedisQueue { return &RedisQueue{name: name, client: client, ctx: context.Background()} } -func (q *RedisQueue) RPush(value interface{}) { - q.client.RPush(q.ctx, q.name, utils.JsonEncode(value)) +func (q *RedisQueue) RPush(value any) error { + return q.client.RPush(q.ctx, q.name, utils.JsonEncode(value)).Err() } -func (q *RedisQueue) LPush(value interface{}) { - q.client.LPush(q.ctx, q.name, utils.JsonEncode(value)) +func (q *RedisQueue) LPush(value any) error { + return q.client.LPush(q.ctx, q.name, utils.JsonEncode(value)).Err() } -func (q *RedisQueue) LPop(value interface{}) error { +func (q *RedisQueue) LPop(value any) error { result, err := q.client.BLPop(q.ctx, 0, q.name).Result() if err != nil { return err @@ -39,10 +40,18 @@ func (q *RedisQueue) LPop(value interface{}) error { return utils.JsonDecode(result[1], value) } -func (q *RedisQueue) RPop(value interface{}) error { +func (q *RedisQueue) RPop(value any) error { result, err := q.client.BRPop(q.ctx, 0, q.name).Result() if err != nil { return err } return utils.JsonDecode(result[1], value) } + +func (q *RedisQueue) Size() (int64, error) { + return q.client.LLen(q.ctx, q.name).Result() +} + +func (q *RedisQueue) Clear() error { + return q.client.Del(q.ctx, q.name).Err() +} diff --git a/api/store/vo/jimeng_job.go b/api/store/vo/jimeng_job.go new file mode 100644 index 00000000..14b76817 --- /dev/null +++ b/api/store/vo/jimeng_job.go @@ -0,0 +1,21 @@ +package vo + +// JimengJob 即梦AI任务VO +type JimengJob struct { + Id uint `json:"id"` + UserId uint `json:"user_id"` + TaskId string `json:"task_id"` + Type string `json:"type"` + ReqKey string `json:"req_key"` + Prompt string `json:"prompt"` + TaskParams string `json:"task_params"` + ImgURL string `json:"img_url"` + VideoURL string `json:"video_url"` + RawData string `json:"raw_data"` + Progress int `json:"progress"` + Status string `json:"status"` + ErrMsg string `json:"err_msg"` + Power int `json:"power"` + CreatedAt int64 `json:"created_at"` // 时间戳 + UpdatedAt int64 `json:"updated_at"` // 时间戳 +} diff --git a/api/test/test.go b/api/test/test.go deleted file mode 100644 index 99fd702a..00000000 --- a/api/test/test.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import ( - "crypto/rand" - "encoding/hex" - "fmt" - "sync" -) - -const ( - codeLength = 32 // 兑换码长度 -) - -var ( - codeMap = make(map[string]bool) - mapMutex = &sync.Mutex{} -) - -// GenerateUniqueCode 生成唯一兑换码 -func GenerateUniqueCode() (string, error) { - for { - code, err := generateCode() - if err != nil { - return "", err - } - - mapMutex.Lock() - if !codeMap[code] { - codeMap[code] = true - mapMutex.Unlock() - return code, nil - } - mapMutex.Unlock() - } -} - -// generateCode 生成兑换码 -func generateCode() (string, error) { - bytes := make([]byte, codeLength/2) // 因为 hex 编码会使长度翻倍 - if _, err := rand.Read(bytes); err != nil { - return "", err - } - return hex.EncodeToString(bytes), nil -} - -func main() { - for i := 0; i < 10; i++ { - code, err := GenerateUniqueCode() - if err != nil { - fmt.Println("Error generating code:", err) - return - } - fmt.Println("Generated code:", code) - } -} diff --git a/web/src/assets/iconfont/iconfont.css b/web/src/assets/iconfont/iconfont.css index 834e6462..73489687 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=1752731646117') format('woff2'), - url('iconfont.woff?t=1752731646117') format('woff'), - url('iconfont.ttf?t=1752731646117') format('truetype'); + src: url('iconfont.woff2?t=1752831319382') format('woff2'), + url('iconfont.woff?t=1752831319382') format('woff'), + url('iconfont.ttf?t=1752831319382') format('truetype'); } .iconfont { @@ -13,6 +13,14 @@ -moz-osx-font-smoothing: grayscale; } +.icon-jimeng2:before { + content: "\eabc"; +} + +.icon-jimeng:before { + content: "\eabb"; +} + .icon-video:before { content: "\e63f"; } diff --git a/web/src/assets/iconfont/iconfont.js b/web/src/assets/iconfont/iconfont.js index 0e090b2d..ffd1ad96 100644 --- a/web/src/assets/iconfont/iconfont.js +++ b/web/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -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 h,t,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)}}h=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(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=h,o=a.document,z=!1,v(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){z||(z=!0,i())}function v(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(v,50)}p()}})(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 h,t,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)}}h=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(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=h,o=a.document,z=!1,v(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){z||(z=!0,i())}function v(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(v,50)}p()}})(window); \ No newline at end of file diff --git a/web/src/assets/iconfont/iconfont.json b/web/src/assets/iconfont/iconfont.json index 9f5dc208..4085727c 100644 --- a/web/src/assets/iconfont/iconfont.json +++ b/web/src/assets/iconfont/iconfont.json @@ -5,6 +5,20 @@ "css_prefix_text": "icon-", "description": "", "glyphs": [ + { + "icon_id": "42693930", + "name": "即梦AI-02", + "font_class": "jimeng2", + "unicode": "eabc", + "unicode_decimal": 60092 + }, + { + "icon_id": "42693927", + "name": "即梦AI-01", + "font_class": "jimeng", + "unicode": "eabb", + "unicode_decimal": 60091 + }, { "icon_id": "1283", "name": "视频", diff --git a/web/src/assets/iconfont/iconfont.ttf b/web/src/assets/iconfont/iconfont.ttf index 47385b321e65e85fab906c0d0fccd957a28dca08..e76c0a11f19c03a18f8c90161beb1439646be141 100644 GIT binary patch delta 3661 zcmaKvdvH|c8OEQpyV=}!lg)LL5R#BPNeIajHe5n3+%Ewlmw+T-qCk-QtqK%bOc7d9 zL?hZ#g@{18sO91aEsP^baRwMa?ljNMU)H*ArFGM)bg!+Qx*~a^UI^z_|++VBg%c8O^c#dcN&B=l;%|`Jd16 z-ZMNKY`?eUiP)<9h<5Z|@UOjpxNZ*2$lqPz{?5LWUn5}a{W0gj@wiot%--+jY!sq8 zO07@rJUn^F`rKAIz(41z72*vzu!oPl0&9_kSGW|rP>-MC zC)k2t;~6YQf8?MTzd$mgF#z)rk2QD!eG!W`G+;Sb`WPNZ5&Ga)2w*HWVJQaUC4}Rr z2&_Xko4?Mvq~Rdm#M{WlJ6MK=7>UO)9aU(;IE=@tcr?O~Nk~N= z6=I`@4v)4*LrHEo7L&4u%X@*vpVAgqSx&>SG671{*Eh?dYWAY&C;2V|T= z6M>9ZXeW>fyuVOPbQZ`&g&qT$q|j|3=H`UH1DUMQfgn>9dJ$x*LRW$`DD)@D9EDB= zX;SD}kY)t|qsqAojSSMN(9$3a6`C8w%$(5XAU|TFMa&YDUjJChV?q;ztWs!)5F=DV zV}v}b&>|u0O#QhtRgm=xeG}5I&_N+)xe2`#VwRZDRUt+%g#HRKt3>FukQWttF63ne zyoc;iXv2^Wg@(-j-_0+C-VAwLp-V%Is0jTUvR9#VLpl}Ia(qXjn?nvLXyJHJp~FLr z`U$-ra!8@;Lq0U{$r%8UGYX>sa$aF5Kt5F%50DEAg97rIf=-T?6@~}oiozIyTvZq- zkZTH~1#(ki$guzKb4QprkVguW2SJCz3_{SYFpUt*QkY8!Qfvk$6oQq{JTVY%xRID&K5h`K*xdSs4Av%?TX^IdhWniu%#Fa5HVG(Lkn6(JCDNJ32 zmMF|$gw`ocVuZFS%w&YlD@>4_@92R&lfxz5(DXu7-i{0_ft{6);~W@qn#j;0pq}PAPmGPP70r5Y_U73S9=|o`Qd35gujy*@91~eRf3n}U``A6%rW{sWVtAOd)GElY zwEUHRUvfgJH8`%q@AD>mt>o|+TR|R=!!j&qPQTIsFR{Kz>OSi3mtuDnS(%;$cVR|T zRCGq6JHcaR7P;&x{oLKNT>V^DPF_Mm>7(BdC`ib&TiuN*vG*cvNuJtzn?2p@>C>GS zp6IfUB=zxl(;q#muk|F^B2Pp_oKG!Ivr>yGD=MgGm!rnMjU6_C({y^B1^IyjPSHx{ z)VNjpy_{rNczL*^#!US`n?1kMZ%=NEab&%6?C|uu?xq8C$4nSrmsgz~<8^KC-tgwe z?s)}PSWKk1D7hdd)8(0Lb4OW8TP~NSZiyV$Kg)LNR#{GbjEx`5(f@B#N4($eaK}gY zDb1*et{h$8W`%ieQRbhb`1`-Jj-R}JWuBF29k)&(eDTxE7A$Y`Idwc)chw$d;or}_ BZ~*`S delta 3315 zcmaKueQZ|c8ODF7Eu~PPrF=svr9gpFpp;UeRK7oI`Gx|uR0QOsB3~+Fg(+`EM0BpA z-4NMC{1}2VaKlU&$-y(6p10?` z&wbt3^;|rjeC}xS{-W5Fm#*%Flw;80i}f3Kto!_#8_ke)9p2kmw|3Q<(u#ApfNP?( zP8T{~$oN>tXT-p|P21`}n|J3A5b6G;GaI+uzv{W@%v9+7tgfxxw5onBx|nNLj$R#G_|DcYTD3d zZ|b+{w?DXZr|F@zC#FQ~i3Uam-*3fHX;cT0)^E}I2{FoCwO+A}g$6Ssxjpvv|Id|~`mhdKH8ACCn8O1CL8Kil5jnxz| zh0YvhA6t2VY<|R(F>*-fZGK2MUf@^kVky5QOH;X5zdN6wYD#~hS=hwGbm1qOqb4eO zoJZKrOKhW#!SrV+Ptlcj45OAz9^wi5(2=#wVZU?y9r%A|z5>A?U-FjBfnRcXIz z%1ljhOLtUpOs^PF?J@OLKq|mGIno1`;Yb-+rX!7DogJwK%W|X} ztcycToornli3#iGNLE;PM*_ooIFcHs&JRd*ST9HN!+JXsBG$(Vt!deg#EJEFBvUL0 zVqqu6V&O(1?P9r(RE+g^q-QM8k+QJ?jx>%9bfk7HJX}ckSiYkUzy>=!sd0#-rifuf zbt2RsSa=bkPQivb>KQEjI-%~thCAvbY=onZ!lbK!dJ8LZ)MZ$hF`<6L!mJ8)9u{VG zbWA5f5G9V<5esDyYD}!uQHx@sR6@;)Nw9(7gf`w$17l^5S{a+*sHw4_5ux_Rf<}ZI z9g9uWPN?UxNltJ`o9xI3uuu{qN5G~!@&+u3Q^+N->5lvYo8ibgu$hiL1Pgi*auck= ziOE>7yB%2!79JvGHrQ-Owu8-aWI)(lM^=Q*b7V@`d`I?#EpTL1SdAmg!j?NSFRV7y zUpLFguzMXj8n()jw_)oXxg55^k>6pP9XTJi#cB1AAV?uM#KHrGd=U#%FXWI|(3Ids z)!*tJ*(SEbk%3}iDut{R3vVlAs#vJ4kiBC292qV4v?I&K!nu(7VxhD`HjFhogUgR~^|p7E~@|@YqpDR*${u$n>#e4l6_d zzpE3WB)~p!lnK~}jw*zGv${6fQEBfzY zN^Uv|9_(L^B8a$tQ9xltT)GG-mWby%3Mk_Gr2$11@dq4*7V+mD#TW7KQIHYW%LWu_ z#EFUQ|?HCW zVx^K#QgiKiTe zGl>r!#Waae_583?Vw1S!Fj?cD9i=#lzdFiu5??w>b`sYeWju+$J4$;JUpdNsbbBjW z0q|wyP^D2N3}{ghqeTY+tq*8Xn*_8{pyfSBYX-heceHxownQ4xIwFe3v;svshpH$g zs!JM@bS}9$Wkj2;sg3#MJ2UN`;vjItTcGasGVJgain$yxuX*jG8H@{QTG zbBgBdnOiXT>byhq&o3xkSW&R>;KJW5%35@Oal_)~s_LqDmeeeHVM$`?-eqaa{!(3a lPgzZV&CZ&}bSDAfP} diff --git a/web/src/assets/iconfont/iconfont.woff b/web/src/assets/iconfont/iconfont.woff index 6029718b472c6dc6d77181460a1864860cad7763..5511edbd93c1f19331ff61e867eb7602de1f2a5e 100644 GIT binary patch delta 34790 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 W>spONb)T%~lS_#!28S{aDF6WTcx0^r delta 34459 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�zzeK8qktE7U(MXa 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 L^^52_u3rEE@LJ_x literal 30412 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 +
+
+
+ +
+ +
+
+ + +
+ +
+ + 上传图片 +
+
+
+
+ + +
+ +
+ +
+

点击上传图片

+

支持 JPG、PNG 格式,最大 10MB

+
+
+
+
+ + + +
+ + + + + diff --git a/web/src/components/admin/AdminSidebar.vue b/web/src/components/admin/AdminSidebar.vue index 9375a8a5..8f852cdd 100644 --- a/web/src/components/admin/AdminSidebar.vue +++ b/web/src/components/admin/AdminSidebar.vue @@ -159,6 +159,11 @@ const items = [ index: '/admin/medias', title: '音视频记录', }, + { + icon: 'image', + index: '/admin/jimeng', + title: '即梦AI任务', + }, ], }, diff --git a/web/src/router.js b/web/src/router.js index 8e05f8ac..83093a66 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -109,6 +109,12 @@ const routes = [ meta: { title: '视频创作中心' }, component: () => import('@/views/Video.vue'), }, + { + name: 'jimeng', + path: '/jimeng', + meta: { title: '即梦AI' }, + component: () => import('@/views/Jimeng.vue'), + }, ], }, { @@ -252,6 +258,12 @@ const routes = [ meta: { title: '音视频管理' }, component: () => import('@/views/admin/records/Medias.vue'), }, + { + path: '/admin/jimeng', + name: 'admin-jimeng', + meta: { title: '即梦AI管理' }, + component: () => import('@/views/admin/JimengJobs.vue'), + }, { path: '/admin/powerLog', name: 'admin-power-log', diff --git a/web/src/store/jimeng.js b/web/src/store/jimeng.js new file mode 100644 index 00000000..33c0b2f9 --- /dev/null +++ b/web/src/store/jimeng.js @@ -0,0 +1,513 @@ +// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// * 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 nodata from '@/assets/img/no-data.png' +import { checkSession } from '@/store/cache' +import { closeLoading, showLoading, showMessageError, showMessageOK } from '@/utils/dialog' +import { httpGet, httpPost } from '@/utils/http' +import { replaceImg, substr, dateFormat } from '@/utils/libs' +import { ElMessage, ElMessageBox } from 'element-plus' +import { defineStore } from 'pinia' +import { computed, reactive, ref } from 'vue' + +export const useJimengStore = defineStore('jimeng', () => { + // 当前激活的功能分类和具体功能 + const activeCategory = ref('image_generation') + const activeFunction = ref('text_to_image') + const useImageInput = ref(false) + + // 共同状态 + const loading = ref(false) + const submitting = ref(false) + const list = ref([]) + const noData = ref(true) + const page = ref(1) + const pageSize = ref(20) + const total = ref(0) + const taskPulling = ref(false) + const pullHandler = ref(null) + const taskFilter = ref('all') + const currentList = ref([]) + + // 用户信息 + const isLogin = ref(false) + const userPower = ref(100) + + // 视频预览 + const showDialog = ref(false) + const currentVideoUrl = ref('') + + // 功能分类配置 + const categories = [ + { key: 'image_generation', name: '图片生成' }, + { key: 'image_editing', name: 'AI修图' }, + { key: 'image_effects', name: '图像特效' }, + { key: 'video_generation', name: '视频生成' }, + ] + + // 功能配置 + const functions = [ + { key: 'text_to_image', name: '文生图', category: 'image_generation', needsPrompt: true, needsImage: false, power: 20 }, + { key: 'image_to_image_portrait', name: '图生图', category: 'image_generation', needsPrompt: true, needsImage: true, power: 30 }, + { key: 'image_edit', name: '图像编辑', category: 'image_editing', needsPrompt: true, needsImage: true, multiple: true, power: 25 }, + { key: 'image_effects', name: '图像特效', category: 'image_effects', needsPrompt: false, needsImage: true, power: 15 }, + { key: 'text_to_video', name: '文生视频', category: 'video_generation', needsPrompt: true, needsImage: false, power: 100 }, + { key: 'image_to_video', name: '图生视频', category: 'video_generation', needsPrompt: true, needsImage: true, multiple: true, power: 120 }, + ] + + // 各功能的参数 + const textToImageParams = reactive({ + prompt: '', + size: '1328x1328', + scale: 2.5, + seed: -1, + use_pre_llm: false, + }) + + const imageToImageParams = reactive({ + image_input: '', + prompt: '演唱会现场的合照,闪光灯拍摄', + size: '1328x1328', + gpen: 0.4, + skin: 0.3, + skin_unifi: 0, + gen_mode: 'creative', + seed: -1, + }) + + const imageEditParams = reactive({ + image_urls: [], + prompt: '', + scale: 0.5, + seed: -1, + }) + + const imageEffectsParams = reactive({ + image_input1: '', + template_id: '', + size: '1328x1328', + }) + + const textToVideoParams = reactive({ + prompt: '', + aspect_ratio: '16:9', + seed: -1, + }) + + const imageToVideoParams = reactive({ + image_urls: [], + prompt: '', + aspect_ratio: '16:9', + seed: -1, + }) + + // 计算属性 + const currentFunction = computed(() => { + return functions.find(f => f.key === activeFunction.value) || functions[0] + }) + + const currentFunctions = computed(() => { + return functions.filter(f => f.category === activeCategory.value) + }) + + const needsPrompt = computed(() => currentFunction.value.needsPrompt) + const needsImage = computed(() => currentFunction.value.needsImage) + const needsMultipleImages = computed(() => currentFunction.value.multiple) + const currentPowerCost = computed(() => currentFunction.value.power) + + // 初始化方法 + const init = async () => { + try { + const user = await checkSession() + isLogin.value = true + userPower.value = user.power + + // 获取任务列表 + await fetchData(1) + + // 检查是否需要开始轮询 + const pendingCount = await getPendingCount() + if (pendingCount > 0) { + startPolling() + } + } catch (error) { + console.error('初始化失败:', error) + } + } + + // 切换功能分类 + const switchCategory = (category) => { + activeCategory.value = category + const categoryFunctions = functions.filter(f => f.category === category) + if (categoryFunctions.length > 0) { + if (category === 'image_generation') { + activeFunction.value = useImageInput.value ? 'image_to_image_portrait' : 'text_to_image' + } else if (category === 'video_generation') { + activeFunction.value = useImageInput.value ? 'image_to_video' : 'text_to_video' + } else { + activeFunction.value = categoryFunctions[0].key + } + } + } + + // 切换输入模式 + const switchInputMode = () => { + if (activeCategory.value === 'image_generation') { + activeFunction.value = useImageInput.value ? 'image_to_image_portrait' : 'text_to_image' + } else if (activeCategory.value === 'video_generation') { + activeFunction.value = useImageInput.value ? 'image_to_video' : 'text_to_video' + } + } + + // 切换功能 + const switchFunction = (functionKey) => { + activeFunction.value = functionKey + } + + // 获取当前算力消耗 + const getCurrentPowerCost = () => { + return currentFunction.value.power + } + + // 获取功能名称 + const getFunctionName = (type) => { + const func = functions.find(f => f.key === type) + return func ? func.name : type + } + + // 获取任务状态文本 + const getTaskStatusText = (status) => { + const statusMap = { + 'pending': '等待中', + 'processing': '处理中', + 'completed': '已完成', + 'failed': '失败' + } + return statusMap[status] || status + } + + // 获取状态类型 + const getStatusType = (status) => { + const typeMap = { + 'pending': 'info', + 'processing': 'warning', + 'completed': 'success', + 'failed': 'danger' + } + return typeMap[status] || 'info' + } + + // 切换任务筛选 + const switchTaskFilter = (filter) => { + taskFilter.value = filter + updateCurrentList() + } + + // 更新当前列表 + const updateCurrentList = () => { + if (taskFilter.value === 'all') { + currentList.value = list.value + } else if (taskFilter.value === 'image') { + currentList.value = list.value.filter(item => + ['text_to_image', 'image_to_image_portrait', 'image_edit', 'image_effects'].includes(item.type) + ) + } else if (taskFilter.value === 'video') { + currentList.value = list.value.filter(item => + ['text_to_video', 'image_to_video'].includes(item.type) + ) + } + } + + // 获取任务列表 + const fetchData = async (pageNum = 1) => { + try { + loading.value = true + page.value = pageNum + + const response = await httpGet('/api/jimeng/jobs', { + page: pageNum, + page_size: pageSize.value + }) + + if (response.data) { + list.value = response.data.jobs || [] + total.value = response.data.total || 0 + noData.value = list.value.length === 0 + updateCurrentList() + } + } catch (error) { + console.error('获取任务列表失败:', error) + showMessageError('获取任务列表失败') + } finally { + loading.value = false + } + } + + // 提交任务 + const submitTask = async () => { + if (!isLogin.value) { + showMessageError('请先登录') + return + } + + if (userPower.value < currentPowerCost.value) { + showMessageError('算力不足') + return + } + + try { + submitting.value = true + let apiUrl = '' + let requestData = {} + + switch (activeFunction.value) { + case 'text_to_image': + apiUrl = '/api/jimeng/text-to-image' + requestData = { + prompt: textToImageParams.prompt, + width: parseInt(textToImageParams.size.split('x')[0]), + height: parseInt(textToImageParams.size.split('x')[1]), + scale: textToImageParams.scale, + seed: textToImageParams.seed, + use_pre_llm: textToImageParams.use_pre_llm, + } + break + + case 'image_to_image_portrait': + apiUrl = '/api/jimeng/image-to-image-portrait' + requestData = { + image_input: imageToImageParams.image_input, + prompt: imageToImageParams.prompt, + width: parseInt(imageToImageParams.size.split('x')[0]), + height: parseInt(imageToImageParams.size.split('x')[1]), + gpen: imageToImageParams.gpen, + skin: imageToImageParams.skin, + skin_unifi: imageToImageParams.skin_unifi, + gen_mode: imageToImageParams.gen_mode, + seed: imageToImageParams.seed, + } + break + + case 'image_edit': + apiUrl = '/api/jimeng/image-edit' + requestData = { + image_urls: imageEditParams.image_urls, + prompt: imageEditParams.prompt, + scale: imageEditParams.scale, + seed: imageEditParams.seed, + } + break + + case 'image_effects': + apiUrl = '/api/jimeng/image-effects' + requestData = { + image_input1: imageEffectsParams.image_input1, + template_id: imageEffectsParams.template_id, + width: parseInt(imageEffectsParams.size.split('x')[0]), + height: parseInt(imageEffectsParams.size.split('x')[1]), + } + break + + case 'text_to_video': + apiUrl = '/api/jimeng/text-to-video' + requestData = { + prompt: textToVideoParams.prompt, + aspect_ratio: textToVideoParams.aspect_ratio, + seed: textToVideoParams.seed, + } + break + + case 'image_to_video': + apiUrl = '/api/jimeng/image-to-video' + requestData = { + image_urls: imageToVideoParams.image_urls, + prompt: imageToVideoParams.prompt, + aspect_ratio: imageToVideoParams.aspect_ratio, + seed: imageToVideoParams.seed, + } + break + } + + const response = await httpPost(apiUrl, requestData) + + if (response.data) { + showMessageOK('任务提交成功') + // 重新获取任务列表 + await fetchData(1) + // 开始轮询 + startPolling() + } + } catch (error) { + console.error('提交任务失败:', error) + showMessageError(error.message || '提交任务失败') + } finally { + submitting.value = false + } + } + + // 获取待处理任务数量 + const getPendingCount = async () => { + try { + const response = await httpGet('/api/jimeng/pending-count') + return response.data?.count || 0 + } catch (error) { + console.error('获取待处理任务数量失败:', error) + return 0 + } + } + + // 开始轮询 + const startPolling = () => { + if (taskPulling.value) return + + taskPulling.value = true + pullHandler.value = setInterval(async () => { + const pendingCount = await getPendingCount() + if (pendingCount > 0) { + await fetchData(page.value) + } else { + stopPolling() + } + }, 3000) + } + + // 停止轮询 + const stopPolling = () => { + if (pullHandler.value) { + clearInterval(pullHandler.value) + pullHandler.value = null + } + taskPulling.value = false + } + + // 重试任务 + const retryTask = async (taskId) => { + try { + const response = await httpPost(`/api/jimeng/retry/${taskId}`) + if (response.data) { + showMessageOK('重试任务已提交') + await fetchData(page.value) + startPolling() + } + } catch (error) { + console.error('重试任务失败:', error) + showMessageError(error.message || '重试任务失败') + } + } + + // 删除任务 + const removeJob = async (item) => { + try { + await ElMessageBox.confirm('确定要删除这个任务吗?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning', + }) + + const response = await httpGet('/api/jimeng/remove', { id: item.id }) + if (response.data) { + showMessageOK('删除成功') + await fetchData(page.value) + } + } catch (error) { + if (error !== 'cancel') { + console.error('删除任务失败:', error) + showMessageError(error.message || '删除任务失败') + } + } + } + + // 播放视频 + const playVideo = (item) => { + currentVideoUrl.value = item.video_url + showDialog.value = true + } + + // 下载文件 + const downloadFile = (item) => { + const url = item.video_url || item.img_url + if (url) { + const link = document.createElement('a') + link.href = url + link.download = `jimeng_${item.id}.${item.video_url ? 'mp4' : 'jpg'}` + link.click() + } + } + + // 清理 + const cleanup = () => { + stopPolling() + } + + // 返回所有状态和方法 + return { + // 状态 + activeCategory, + activeFunction, + useImageInput, + loading, + submitting, + list, + noData, + page, + pageSize, + total, + taskFilter, + currentList, + isLogin, + userPower, + showDialog, + currentVideoUrl, + nodata, + + // 配置 + categories, + functions, + currentFunctions, + + // 参数 + textToImageParams, + imageToImageParams, + imageEditParams, + imageEffectsParams, + textToVideoParams, + imageToVideoParams, + + // 计算属性 + currentFunction, + needsPrompt, + needsImage, + needsMultipleImages, + currentPowerCost, + + // 方法 + init, + switchCategory, + switchFunction, + switchInputMode, + getCurrentPowerCost, + getFunctionName, + getTaskStatusText, + getStatusType, + switchTaskFilter, + updateCurrentList, + fetchData, + submitTask, + getPendingCount, + startPolling, + stopPolling, + retryTask, + removeJob, + playVideo, + downloadFile, + cleanup, + + // 工具函数 + substr, + replaceImg, + } +}) \ No newline at end of file diff --git a/web/src/utils/libs.js b/web/src/utils/libs.js index cdb8695a..1c151507 100644 --- a/web/src/utils/libs.js +++ b/web/src/utils/libs.js @@ -255,3 +255,8 @@ export function isChrome() { const userAgent = navigator.userAgent.toLowerCase() return /chrome/.test(userAgent) && !/edg/.test(userAgent) } + +// 格式化日期时间 +export function formatDateTime(timestamp, format = 'yyyy-MM-dd HH:mm:ss') { + return dateFormat(timestamp, format) +} diff --git a/web/src/views/Home.vue b/web/src/views/Home.vue index 35e575b2..0ad5812e 100644 --- a/web/src/views/Home.vue +++ b/web/src/views/Home.vue @@ -69,7 +69,7 @@ +
+ + 登录 + +
-
+
@@ -281,7 +286,9 @@ const logout = function () { httpGet('/api/user/logout') .then(() => { removeUserToken() - router.push('/login') + // 刷新组件 + routerViewKey.value += 1 + loginUser.value = {} }) .catch(() => { ElMessage.error('注销失败!') diff --git a/web/src/views/Index.vue b/web/src/views/Index.vue index 9023e06f..61354a0e 100644 --- a/web/src/views/Index.vue +++ b/web/src/views/Index.vue @@ -69,7 +69,7 @@ class="nav-item-box" @click="router.push(item.url)" > - +
{{ item.name }}
@@ -107,20 +107,6 @@ const githubURL = ref(import.meta.env.VITE_GITHUB_URL) const giteeURL = ref(import.meta.env.VITE_GITEE_URL) const navs = ref([]) -const iconMap = ref({ - '/chat': 'icon-chat', - '/mj': 'icon-mj', - '/sd': 'icon-sd', - '/dalle': 'icon-dalle', - '/images-wall': 'icon-image', - '/suno': 'icon-suno', - '/xmind': 'icon-xmind', - '/apps': 'icon-app', - '/member': 'icon-vip-user', - '/invite': 'icon-share', - '/luma': 'icon-luma', -}) - const displayedChars = ref([]) const initAnimation = ref('') let timer = null // 定时器句柄 diff --git a/web/src/views/Jimeng.vue b/web/src/views/Jimeng.vue new file mode 100644 index 00000000..76baacb4 --- /dev/null +++ b/web/src/views/Jimeng.vue @@ -0,0 +1,799 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/Video.vue b/web/src/views/Video.vue index c8281602..bb01df83 100644 --- a/web/src/views/Video.vue +++ b/web/src/views/Video.vue @@ -115,12 +115,12 @@
-
+
循环参考图
-
+
提示词优化
diff --git a/web/src/views/admin/JimengJobs.vue b/web/src/views/admin/JimengJobs.vue new file mode 100644 index 00000000..cf87ceec --- /dev/null +++ b/web/src/views/admin/JimengJobs.vue @@ -0,0 +1,543 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/admin/SysConfig.vue b/web/src/views/admin/SysConfig.vue index cc3b8103..ce28b400 100644 --- a/web/src/views/admin/SysConfig.vue +++ b/web/src/views/admin/SysConfig.vue @@ -169,10 +169,10 @@