From a60ffca135a315933b98a9cbe602633abaf23699 Mon Sep 17 00:00:00 2001 From: RockYang Date: Sat, 6 Sep 2025 12:38:49 +0800 Subject: [PATCH] remove AI3D module files --- api/core/config.go | 10 - api/core/types/ai3d.go | 65 -- api/core/types/config.go | 1 - api/go.mod | 2 - api/go.sum | 5 - api/handler/admin/ai3d_handler.go | 222 ------- api/handler/ai3d_handler.go | 264 -------- api/main.go | 18 - api/service/ai3d/gitee_client.go | 146 ----- api/service/ai3d/service.go | 382 ------------ api/service/ai3d/tencent_client.go | 160 ----- api/service/migration_service.go | 19 - api/store/model/ai3d_job.go | 27 - api/store/vo/ai3d_job.go | 39 -- web/src/assets/css/admin/ai3d.scss | 145 ----- web/src/assets/css/ai3d.scss | 634 -------------------- web/src/components/ThreeDPreview.vue | 562 ----------------- web/src/components/admin/AdminSidebar.vue | 17 - web/src/router.js | 30 - web/src/store/ai3d.js | 361 ----------- web/src/views/AIThreeDCreate.vue | 498 --------------- web/src/views/admin/ai3d/AIThreeDConfig.vue | 399 ------------ web/src/views/admin/ai3d/AIThreeDJobs.vue | 469 --------------- 23 files changed, 4475 deletions(-) delete mode 100644 api/core/types/ai3d.go delete mode 100644 api/handler/admin/ai3d_handler.go delete mode 100644 api/handler/ai3d_handler.go delete mode 100644 api/service/ai3d/gitee_client.go delete mode 100644 api/service/ai3d/service.go delete mode 100644 api/service/ai3d/tencent_client.go delete mode 100644 api/store/model/ai3d_job.go delete mode 100644 api/store/vo/ai3d_job.go delete mode 100644 web/src/assets/css/admin/ai3d.scss delete mode 100644 web/src/assets/css/ai3d.scss delete mode 100644 web/src/components/ThreeDPreview.vue delete mode 100644 web/src/store/ai3d.js delete mode 100644 web/src/views/AIThreeDCreate.vue delete mode 100644 web/src/views/admin/ai3d/AIThreeDConfig.vue delete mode 100644 web/src/views/admin/ai3d/AIThreeDJobs.vue diff --git a/api/core/config.go b/api/core/config.go index 7f2fc971..e38ed0bf 100644 --- a/api/core/config.go +++ b/api/core/config.go @@ -166,15 +166,6 @@ func LoadSystemConfig(db *gorm.DB) *types.SystemConfig { logger.Error("load jimeng config error: ", err) } - // 加载3D生成配置 - var ai3dConfig types.AI3DConfig - sysConfig.Id = 0 - db.Where("name", types.ConfigKeyAI3D).First(&sysConfig) - err = utils.JsonDecode(sysConfig.Value, &ai3dConfig) - if err != nil { - logger.Error("load ai3d config error: ", err) - } - return &types.SystemConfig{ Base: baseConfig, License: license, @@ -186,6 +177,5 @@ func LoadSystemConfig(db *gorm.DB) *types.SystemConfig { WxLogin: wxLoginConfig, Moderation: moderationConfig, Jimeng: jimengConfig, - AI3D: ai3dConfig, } } diff --git a/api/core/types/ai3d.go b/api/core/types/ai3d.go deleted file mode 100644 index 41666219..00000000 --- a/api/core/types/ai3d.go +++ /dev/null @@ -1,65 +0,0 @@ -package types - -// AI3DConfig 3D生成配置 -type AI3DConfig struct { - Tencent Tencent3DConfig `json:"tencent,omitempty"` - Gitee Gitee3DConfig `json:"gitee,omitempty"` -} - -// Tencent3DConfig 腾讯云3D配置 -type Tencent3DConfig struct { - SecretId string `json:"secret_id,omitempty"` - SecretKey string `json:"secret_key,omitempty"` - Region string `json:"region,omitempty"` - Enabled bool `json:"enabled,omitempty"` - Models []AI3DModel `json:"models,omitempty"` -} - -// Gitee3DConfig Gitee 3D配置 -type Gitee3DConfig struct { - APIKey string `json:"api_key,omitempty"` - Enabled bool `json:"enabled,omitempty"` - Models []AI3DModel `json:"models,omitempty"` -} - -type AI3DTaskType string - -const ( - AI3DTaskTypeTencent AI3DTaskType = "tencent" - AI3DTaskTypeGitee AI3DTaskType = "gitee" -) - -// AI3DJobResult 3D任务结果 -type AI3DJobResult struct { - TaskId string `json:"task_id"` // 任务ID - Status string `json:"status"` // 任务状态 - FileURL string `json:"file_url"` // 3D模型文件URL - PreviewURL string `json:"preview_url"` // 预览图片URL - ErrorMsg string `json:"error_msg"` // 错误信息 - RawData string `json:"raw_data"` // 原始数据 -} - -// AI3DModel 3D模型配置 -type AI3DModel struct { - Name string `json:"name"` // 模型名称 - Desc string `json:"desc"` // 模型描述 - Power int `json:"power"` // 算力消耗 - Formats []string `json:"formats"` // 支持输出的文件格式 -} - -// AI3DJobRequest 3D任务请求 -type AI3DJobRequest struct { - Type string `json:"type"` // API类型 (tencent/gitee) - Model string `json:"model"` // 3D模型类型 - Prompt string `json:"prompt"` // 文本提示词 - ImageURL string `json:"image_url"` // 输入图片URL - Power int `json:"power"` // 消耗算力 -} - -// AI3DJobStatus 3D任务状态 -const ( - AI3DJobStatusPending = "pending" // 等待中 - AI3DJobStatusProcessing = "processing" // 处理中 - AI3DJobStatusSuccess = "success" // 已完成 - AI3DJobStatusFailed = "failed" // 失败 -) diff --git a/api/core/types/config.go b/api/core/types/config.go index e6b2d4cc..e93a8b9c 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -107,7 +107,6 @@ type SystemConfig struct { Captcha CaptchaConfig WxLogin WxLoginConfig Jimeng JimengConfig - AI3D AI3DConfig License License Moderation ModerationConfig } diff --git a/api/go.mod b/api/go.mod index d5ac26e0..62da5307 100644 --- a/api/go.mod +++ b/api/go.mod @@ -33,8 +33,6 @@ require ( github.com/shirou/gopsutil v3.21.11+incompatible github.com/shopspring/decimal v1.3.1 github.com/syndtr/goleveldb v1.0.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ai3d v1.1.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.21 golang.org/x/image v0.15.0 ) diff --git a/api/go.sum b/api/go.sum index b1388b00..702add84 100644 --- a/api/go.sum +++ b/api/go.sum @@ -245,11 +245,6 @@ github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gt github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ai3d v1.1.0 h1:hOyYsl35o74hOhnnPVQIK/bdSIPNp3TKJlCEOXGO7ms= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ai3d v1.1.0/go.mod h1:3689peGF1zp+P9c+GnUcAzkMp+kXi0Tr44zeQ57Z+7Y= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.0/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.21 h1:ikHhyiq1PiPytUMtEblKPkbf0zzTEi3CpE9z0MARlqY= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.21/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= diff --git a/api/handler/admin/ai3d_handler.go b/api/handler/admin/ai3d_handler.go deleted file mode 100644 index f99db909..00000000 --- a/api/handler/admin/ai3d_handler.go +++ /dev/null @@ -1,222 +0,0 @@ -package admin - -import ( - "strconv" - - "geekai/core" - "geekai/core/types" - "geekai/service/ai3d" - "geekai/store/model" - "geekai/store/vo" - "geekai/utils" - "geekai/utils/resp" - - "github.com/gin-gonic/gin" - "gorm.io/gorm" -) - -// AI3DHandler 3D管理处理器 -type AI3DHandler struct { - app *core.AppServer - db *gorm.DB - service *ai3d.Service -} - -// NewAI3DHandler 创建3D管理处理器 -func NewAI3DHandler(app *core.AppServer, db *gorm.DB, service *ai3d.Service) *AI3DHandler { - return &AI3DHandler{ - app: app, - db: db, - service: service, - } -} - -// RegisterRoutes 注册路由 -func (h *AI3DHandler) RegisterRoutes() { - admin := h.app.Engine.Group("/api/admin/ai3d") - { - admin.GET("/jobs", h.GetJobList) - admin.GET("/jobs/:id", h.GetJobDetail) - admin.DELETE("/jobs/:id", h.DeleteJob) - admin.GET("/stats", h.GetStats) - admin.GET("/models", h.GetModels) - admin.POST("/config", h.SaveConfig) - } -} - -// GetJobList 获取任务列表 -func (h *AI3DHandler) GetJobList(c *gin.Context) { - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) - status := c.Query("status") - jobType := c.Query("type") - userIdStr := c.Query("user_id") - - var userId uint - if userIdStr != "" { - if id, err := strconv.ParseUint(userIdStr, 10, 32); err == nil { - userId = uint(id) - } - } - - // 构建查询条件 - query := h.db.Model(&model.AI3DJob{}) - - if status != "" { - query = query.Where("status = ?", status) - } - - if jobType != "" { - query = query.Where("type = ?", jobType) - } - - if userId > 0 { - query = query.Where("user_id = ?", userId) - } - - // 获取总数 - var total int64 - query.Count(&total) - - // 获取分页数据 - var jobs []model.AI3DJob - offset := (page - 1) * pageSize - err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&jobs).Error - - if err != nil { - resp.ERROR(c, "获取任务列表失败") - return - } - - // 转换为VO - var jobList []vo.AI3DJob - for _, job := range jobs { - var jobVo vo.AI3DJob - err = utils.CopyObject(job, &jobVo) - if err != nil { - continue - } - utils.JsonDecode(job.Params, &jobVo.Params) - jobVo.CreatedAt = job.CreatedAt.Unix() - jobVo.UpdatedAt = job.UpdatedAt.Unix() - jobList = append(jobList, jobVo) - } - - resp.SUCCESS(c, vo.NewPage(total, page, pageSize, jobList)) -} - -// GetJobDetail 获取任务详情 -func (h *AI3DHandler) GetJobDetail(c *gin.Context) { - idStr := c.Param("id") - id, err := strconv.ParseUint(idStr, 10, 32) - if err != nil { - resp.ERROR(c, "无效的任务ID") - return - } - - var job model.AI3DJob - err = h.db.First(&job, uint(id)).Error - if err != nil { - if err == gorm.ErrRecordNotFound { - resp.ERROR(c, "任务不存在") - } else { - resp.ERROR(c, "获取任务详情失败") - } - return - } - - var jobVo vo.AI3DJob - err = utils.CopyObject(job, &jobVo) - if err != nil { - resp.ERROR(c, "获取任务详情失败") - return - } - utils.JsonDecode(job.Params, &jobVo.Params) - jobVo.CreatedAt = job.CreatedAt.Unix() - jobVo.UpdatedAt = job.UpdatedAt.Unix() - resp.SUCCESS(c, jobVo) -} - -// DeleteJob 删除任务 -func (h *AI3DHandler) DeleteJob(c *gin.Context) { - idStr := c.Param("id") - id, err := strconv.ParseUint(idStr, 10, 32) - if err != nil { - resp.ERROR(c, "无效的任务ID") - return - } - - // 检查任务是否存在 - var job model.AI3DJob - err = h.db.First(&job, uint(id)).Error - if err != nil { - if err == gorm.ErrRecordNotFound { - resp.ERROR(c, "任务不存在") - } else { - resp.ERROR(c, "获取任务失败") - } - return - } - - // 删除任务 - err = h.db.Delete(&job).Error - if err != nil { - resp.ERROR(c, "删除任务失败") - return - } - - resp.SUCCESS(c, "删除成功") -} - -// GetStats 获取统计数据 -func (h *AI3DHandler) GetStats(c *gin.Context) { - var stats struct { - Pending int64 `json:"pending"` - Processing int64 `json:"processing"` - Success int64 `json:"success"` - Failed int64 `json:"failed"` - } - - // 统计各状态的任务数量 - h.db.Model(&model.AI3DJob{}).Where("status = ?", "pending").Count(&stats.Pending) - h.db.Model(&model.AI3DJob{}).Where("status = ?", "processing").Count(&stats.Processing) - h.db.Model(&model.AI3DJob{}).Where("status = ?", "success").Count(&stats.Success) - h.db.Model(&model.AI3DJob{}).Where("status = ?", "failed").Count(&stats.Failed) - - resp.SUCCESS(c, stats) -} - -// GetModels 获取配置 -func (h *AI3DHandler) GetModels(c *gin.Context) { - models := h.service.GetSupportedModels() - resp.SUCCESS(c, models) -} - -// SaveGlobalSettings 保存全局配置 -func (h *AI3DHandler) SaveConfig(c *gin.Context) { - var config types.AI3DConfig - err := c.ShouldBindJSON(&config) - if err != nil { - resp.ERROR(c, "参数错误") - return - } - var exist model.Config - err = h.db.Where("name", types.ConfigKeyAI3D).First(&exist).Error - if err != nil { - exist.Name = types.ConfigKeyAI3D - exist.Value = utils.JsonEncode(config) - err = h.db.Create(&exist).Error - } else { - exist.Value = utils.JsonEncode(config) - err = h.db.Updates(&exist).Error - } - if err != nil { - resp.ERROR(c, "保存配置失败") - return - } - - h.service.UpdateConfig(config) - h.app.SysConfig.AI3D = config - - resp.SUCCESS(c, "保存成功") -} diff --git a/api/handler/ai3d_handler.go b/api/handler/ai3d_handler.go deleted file mode 100644 index d889a324..00000000 --- a/api/handler/ai3d_handler.go +++ /dev/null @@ -1,264 +0,0 @@ -package handler - -import ( - "fmt" - "geekai/core" - "geekai/core/middleware" - "geekai/core/types" - "geekai/service/ai3d" - "geekai/store/model" - "geekai/store/vo" - "geekai/utils" - "geekai/utils/resp" - "strconv" - - "github.com/gin-gonic/gin" - "gorm.io/gorm" -) - -type AI3DHandler struct { - BaseHandler - service *ai3d.Service -} - -func NewAI3DHandler(app *core.AppServer, db *gorm.DB, service *ai3d.Service) *AI3DHandler { - return &AI3DHandler{ - service: service, - BaseHandler: BaseHandler{ - App: app, - DB: db, - }, - } -} - -// RegisterRoutes 注册路由 -func (h *AI3DHandler) RegisterRoutes() { - group := h.App.Engine.Group("/api/ai3d/") - - // 公开接口,不需要授权 - group.GET("configs", h.GetConfigs) - - // 需要用户授权的接口 - group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis)) - { - group.POST("generate", h.Generate) - group.GET("jobs", h.JobList) - group.GET("jobs/mock", h.ListMock) // 演示数据接口 - group.GET("job/delete", h.DeleteJob) - } -} - -// Generate 创建3D生成任务 -func (h *AI3DHandler) Generate(c *gin.Context) { - var request vo.AI3DJobParams - - if err := c.ShouldBindJSON(&request); err != nil { - resp.ERROR(c, "参数错误") - return - } - - // 提示词和图片不能同时为空 - if request.Prompt == "" && request.ImageURL == "" { - resp.ERROR(c, "提示词和图片不能同时为空") - return - } - - // Gitee 只支持图片 - if request.Type == types.AI3DTaskTypeGitee && request.ImageURL == "" { - resp.ERROR(c, "Gitee 只支持图生3D") - return - } - - logger.Infof("request: %+v", request) - - // 获取用户ID - userId := h.GetLoginUserId(c) - // 创建任务 - job, err := h.service.CreateJob(uint(userId), request) - if err != nil { - resp.ERROR(c, fmt.Sprintf("创建任务失败: %v", err)) - return - } - - resp.SUCCESS(c, gin.H{ - "job_id": job.Id, - "message": "任务创建成功", - }) -} - -// JobList 获取任务列表 -func (h *AI3DHandler) JobList(c *gin.Context) { - userId := h.GetLoginUserId(c) - if userId == 0 { - resp.ERROR(c, "用户未登录") - return - } - - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10")) - - if page < 1 { - page = 1 - } - if pageSize < 1 || pageSize > 100 { - pageSize = 10 - } - - jobList, err := h.service.GetJobList(uint(userId), page, pageSize) - if err != nil { - resp.ERROR(c, fmt.Sprintf("获取任务列表失败: %v", err)) - return - } - - resp.SUCCESS(c, jobList) -} - -// DeleteJob 删除任务 -func (h *AI3DHandler) DeleteJob(c *gin.Context) { - userId := h.GetLoginUserId(c) - id := h.GetInt(c, "id", 0) - if id == 0 { - resp.ERROR(c, "任务ID不能为空") - return - } - - err := h.service.DeleteUserJob(uint(id), uint(userId)) - if err != nil { - resp.ERROR(c, "删除任务失败") - return - } - - resp.SUCCESS(c, gin.H{"message": "删除成功"}) -} - -// GetConfigs 获取3D生成配置 -func (h *AI3DHandler) GetConfigs(c *gin.Context) { - var config model.Config - err := h.DB.Where("name", types.ConfigKeyAI3D).First(&config).Error - if err != nil { - resp.ERROR(c, err.Error()) - return - } - var config3d types.AI3DConfig - err = utils.JsonDecode(config.Value, &config3d) - if err != nil { - resp.ERROR(c, err.Error()) - return - } - models := h.service.GetSupportedModels() - if len(config3d.Gitee.Models) == 0 { - config3d.Gitee.Models = models["gitee"] - } - if len(config3d.Tencent.Models) == 0 { - config3d.Tencent.Models = models["tencent"] - } - - resp.SUCCESS(c, config3d) -} - -// ListMock 返回演示数据 -func (h *AI3DHandler) ListMock(c *gin.Context) { - // 创建各种状态的演示数据 - mockJobs := []vo.AI3DJob{ - { - Id: 1, - UserId: 1, - Type: "gitee", - Power: 10, - TaskId: "mock_task_1", - FileURL: "https://img.r9it.com/R03TQZ7PZ386RGL7PTMNGFOHAJW15WYF.glb", - PreviewURL: "/static/upload/2025/9/1756873317505073.png", - Model: "gitee-3d-v1", - Status: types.AI3DJobStatusSuccess, - ErrMsg: "", - Params: vo.AI3DJobParams{Prompt: "一只可爱的小猫", ImageURL: "", Texture: true, Seed: 42}, - CreatedAt: 1704067200, // 2024-01-01 00:00:00 - UpdatedAt: 1704067800, // 2024-01-01 00:10:00 - }, - { - Id: 2, - UserId: 1, - Type: "tencent", - Power: 15, - TaskId: "mock_task_2", - FileURL: "", - PreviewURL: "/static/upload/2025/9/1756873317505073.png", - Model: "tencent-3d-v2", - Status: types.AI3DJobStatusProcessing, - ErrMsg: "", - Params: vo.AI3DJobParams{Prompt: "一个现代建筑模型", ImageURL: "", EnablePBR: true}, - CreatedAt: 1704070800, // 2024-01-01 01:00:00 - UpdatedAt: 1704070800, // 2024-01-01 01:00:00 - }, - { - Id: 3, - UserId: 1, - Type: "gitee", - Power: 8, - TaskId: "mock_task_3", - FileURL: "", - PreviewURL: "", - Model: "gitee-3d-v1", - Status: types.AI3DJobStatusPending, - ErrMsg: "", - Params: vo.AI3DJobParams{Prompt: "一辆跑车模型", ImageURL: "https://example.com/car.jpg", Texture: false}, - CreatedAt: 1704074400, // 2024-01-01 02:00:00 - UpdatedAt: 1704074400, // 2024-01-01 02:00:00 - }, - { - Id: 4, - UserId: 1, - Type: "tencent", - Power: 12, - TaskId: "mock_task_4", - FileURL: "", - PreviewURL: "", - Model: "tencent-3d-v1", - Status: types.AI3DJobStatusFailed, - ErrMsg: "模型生成失败:输入图片质量不符合要求", - Params: vo.AI3DJobParams{Prompt: "一个机器人模型", ImageURL: "https://example.com/robot.jpg", EnablePBR: false}, - CreatedAt: 1704078000, // 2024-01-01 03:00:00 - UpdatedAt: 1704078600, // 2024-01-01 03:10:00 - }, - { - Id: 5, - UserId: 1, - Type: "gitee", - Power: 20, - TaskId: "mock_task_5", - FileURL: "https://ai.gitee.com/a8c1af8e-26e9-4ca6-aa5c-6d4ba86bfdac", - PreviewURL: "https://ai.gitee.com/a8c1af8e-26e9-4ca6-aa5c-6d4ba86bfdac", - Model: "gitee-3d-v2", - Status: types.AI3DJobStatusSuccess, - ErrMsg: "", - Params: vo.AI3DJobParams{Prompt: "一个复杂的机械装置", ImageURL: "", Texture: true, OctreeResolution: 512}, - CreatedAt: 1704081600, // 2024-01-01 04:00:00 - UpdatedAt: 1704082200, // 2024-01-01 04:10:00 - }, - { - Id: 6, - UserId: 1, - Type: "tencent", - Power: 18, - TaskId: "mock_task_6", - FileURL: "", - PreviewURL: "", - Model: "tencent-3d-v2", - Status: types.AI3DJobStatusProcessing, - ErrMsg: "", - Params: vo.AI3DJobParams{Prompt: "一个科幻飞船", ImageURL: "", EnablePBR: true}, - CreatedAt: 1704085200, // 2024-01-01 05:00:00 - UpdatedAt: 1704085200, // 2024-01-01 05:00:00 - }, - } - - // 创建分页响应 - mockResponse := vo.Page{ - Page: 1, - PageSize: 10, - Total: int64(len(mockJobs)), - Items: mockJobs, - } - - resp.SUCCESS(c, mockResponse) -} diff --git a/api/main.go b/api/main.go index d6092dd7..7901f8df 100644 --- a/api/main.go +++ b/api/main.go @@ -16,7 +16,6 @@ import ( "geekai/handler/admin" logger2 "geekai/logger" "geekai/service" - "geekai/service/ai3d" "geekai/service/dalle" "geekai/service/jimeng" "geekai/service/mj" @@ -217,13 +216,6 @@ func main() { service.Start() }), - // 3D生成服务 - fx.Provide(ai3d.NewTencent3DClient), - fx.Provide(ai3d.NewGitee3DClient), - fx.Provide(ai3d.NewService), - fx.Invoke(func(s *ai3d.Service) { - s.Run() - }), fx.Provide(service.NewSnowflake), // 创建短信服务 @@ -393,16 +385,6 @@ func main() { h.RegisterRoutes() }), - // 3D生成处理器 - fx.Provide(handler.NewAI3DHandler), - fx.Invoke(func(s *core.AppServer, h *handler.AI3DHandler) { - h.RegisterRoutes() - }), - fx.Provide(admin.NewAI3DHandler), - fx.Invoke(func(s *core.AppServer, h *admin.AI3DHandler) { - h.RegisterRoutes() - }), - // 即梦AI 路由 fx.Invoke(func(s *core.AppServer, h *handler.JimengHandler) { h.RegisterRoutes() diff --git a/api/service/ai3d/gitee_client.go b/api/service/ai3d/gitee_client.go deleted file mode 100644 index d150dacd..00000000 --- a/api/service/ai3d/gitee_client.go +++ /dev/null @@ -1,146 +0,0 @@ -package ai3d - -import ( - "encoding/json" - "fmt" - "geekai/core/types" - "time" - - "github.com/imroc/req/v3" -) - -type Gitee3DClient struct { - httpClient *req.Client - config types.Gitee3DConfig - apiURL string -} - -type Gitee3DParams struct { - Model string `json:"model"` // 模型名称 - FileFormat string `json:"file_format,omitempty"` // 文件格式(Step1X-3D、Hi3DGen模型适用),支持 glb 和 stl - Type string `json:"type,omitempty"` // 输出格式(Hunyuan3D-2模型适用) - ImageURL string `json:"image_url"` // 输入图片URL - Texture bool `json:"texture,omitempty"` // 是否开启纹理 - Seed int `json:"seed,omitempty"` // 随机种子 - NumInferenceSteps int `json:"num_inference_steps,omitempty"` //迭代次数 - GuidanceScale float64 `json:"guidance_scale,omitempty"` //引导系数 - OctreeResolution int `json:"octree_resolution,omitempty"` // 3D 渲染精度,越高3D 细节越丰富 -} - -type Gitee3DResponse struct { - TaskID string `json:"task_id"` - Output struct { - FileURL string `json:"file_url,omitempty"` - PreviewURL string `json:"preview_url,omitempty"` - } `json:"output"` - Status string `json:"status"` - CreatedAt any `json:"created_at"` - StartedAt any `json:"started_at"` - CompletedAt any `json:"completed_at"` - Urls struct { - Get string `json:"get"` - Cancel string `json:"cancel"` - } `json:"urls"` -} - -type GiteeErrorResponse struct { - Error int `json:"error"` - Message string `json:"message"` -} - -func NewGitee3DClient(sysConfig *types.SystemConfig) *Gitee3DClient { - return &Gitee3DClient{ - httpClient: req.C().SetTimeout(time.Minute * 3), - config: sysConfig.AI3D.Gitee, - apiURL: "https://ai.gitee.com/v1", - } -} - -func (c *Gitee3DClient) UpdateConfig(config types.Gitee3DConfig) { - c.config = config -} - -func (c *Gitee3DClient) GetConfig() *types.Gitee3DConfig { - return &c.config -} - -// SubmitJob 提交3D生成任务 -func (c *Gitee3DClient) SubmitJob(params Gitee3DParams) (string, error) { - - var giteeResp Gitee3DResponse - response, err := c.httpClient.R(). - SetHeader("Authorization", "Bearer "+c.config.APIKey). - SetHeader("Content-Type", "application/json"). - SetBody(params). - SetSuccessResult(&giteeResp). - Post(c.apiURL + "/async/image-to-3d") - - if err != nil { - return "", fmt.Errorf("failed to submit gitee 3D job: %v", err) - } - - if giteeResp.TaskID == "" { - var giteeErr GiteeErrorResponse - _ = json.Unmarshal(response.Bytes(), &giteeErr) - return "", fmt.Errorf("no task ID returned from gitee 3D API: %s", giteeErr.Message) - } - - return giteeResp.TaskID, nil -} - -// QueryJob 查询任务状态 -func (c *Gitee3DClient) QueryJob(taskId string) (*types.AI3DJobResult, error) { - var giteeResp Gitee3DResponse - apiURL := fmt.Sprintf("%s/task/%s", c.apiURL, taskId) - response, err := c.httpClient.R(). - SetHeader("Authorization", "Bearer "+c.config.APIKey). - SetSuccessResult(&giteeResp). - Get(apiURL) - - if err != nil { - return nil, fmt.Errorf("failed to query gitee 3D job: %v", err) - } - - result := &types.AI3DJobResult{ - TaskId: taskId, - Status: c.convertStatus(giteeResp.Status), - } - - if giteeResp.TaskID == "" { - var giteeErr GiteeErrorResponse - _ = json.Unmarshal(response.Bytes(), &giteeErr) - result.ErrorMsg = giteeErr.Message - } else if giteeResp.Status == "success" { - result.FileURL = giteeResp.Output.FileURL - } - result.RawData = string(response.Bytes()) - - logger.Debugf("gitee 3D job response: %+v", result) - - return result, nil -} - -// convertStatus 转换Gitee状态到系统状态 -func (c *Gitee3DClient) convertStatus(giteeStatus string) string { - switch giteeStatus { - case "waiting": - return types.AI3DJobStatusPending - case "in_progress": - return types.AI3DJobStatusProcessing - case "success": - return types.AI3DJobStatusSuccess - case "failure", "cancelled": - return types.AI3DJobStatusFailed - default: - return types.AI3DJobStatusPending - } -} - -// GetSupportedModels 获取支持的模型列表 -func (c *Gitee3DClient) GetSupportedModels() []types.AI3DModel { - return []types.AI3DModel{ - {Name: "Hunyuan3D-2", Power: 100, Formats: []string{"GLB"}, Desc: "Hunyuan3D-2 是腾讯混元团队推出的高质量 3D 生成模型,具备高保真度、细节丰富和高效生成的特点,可快速将文本或图像转换为逼真的 3D 物体。"}, - {Name: "Step1X-3D", Power: 55, Formats: []string{"GLB", "STL"}, Desc: "Step1X-3D 是一款由阶跃星辰(StepFun)与光影焕像(LightIllusions)联合研发并开源的高保真 3D 生成模型,专为高质量、可控的 3D 内容创作而设计。"}, - {Name: "Hi3DGen", Power: 35, Formats: []string{"GLB", "STL"}, Desc: "Hi3DGen 是一个 AI 工具,它可以把你上传的普通图片,智能转换成有“立体感”的图片(法线图),常用于制作 3D 效果,比如游戏建模、虚拟现实、动画制作等。"}, - } -} diff --git a/api/service/ai3d/service.go b/api/service/ai3d/service.go deleted file mode 100644 index 3ffc13c5..00000000 --- a/api/service/ai3d/service.go +++ /dev/null @@ -1,382 +0,0 @@ -package ai3d - -import ( - "fmt" - "geekai/core/types" - logger2 "geekai/logger" - "geekai/service" - "geekai/service/oss" - "geekai/store" - "geekai/store/model" - "geekai/store/vo" - "geekai/utils" - "net/url" - "path/filepath" - "strings" - "time" - - "github.com/go-redis/redis/v8" - "gorm.io/gorm" -) - -var logger = logger2.GetLogger() - -// Service 3D生成服务 -type Service struct { - db *gorm.DB - taskQueue *store.RedisQueue - tencentClient *Tencent3DClient - giteeClient *Gitee3DClient - userService *service.UserService - uploadManager *oss.UploaderManager -} - -// NewService 创建3D生成服务 -func NewService(db *gorm.DB, redisCli *redis.Client, tencentClient *Tencent3DClient, giteeClient *Gitee3DClient, userService *service.UserService, uploadManager *oss.UploaderManager) *Service { - return &Service{ - db: db, - taskQueue: store.NewRedisQueue("3D_Task_Queue", redisCli), - tencentClient: tencentClient, - giteeClient: giteeClient, - userService: userService, - uploadManager: uploadManager, - } -} - -// CreateJob 创建3D生成任务 -func (s *Service) CreateJob(userId uint, request vo.AI3DJobParams) (*model.AI3DJob, error) { - switch request.Type { - case types.AI3DTaskTypeGitee: - if s.giteeClient == nil { - return nil, fmt.Errorf("模力方舟 3D 服务未初始化") - } - if !s.giteeClient.GetConfig().Enabled { - return nil, fmt.Errorf("模力方舟 3D 服务未启用") - } - - case types.AI3DTaskTypeTencent: - if s.tencentClient == nil { - return nil, fmt.Errorf("腾讯云 3D 服务未初始化") - } - if !s.tencentClient.GetConfig().Enabled { - return nil, fmt.Errorf("腾讯云 3D 服务未启用") - } - - default: - return nil, fmt.Errorf("不支持的 3D 服务类型: %s", request.Type) - } - - // 创建任务记录 - job := &model.AI3DJob{ - UserId: userId, - Type: request.Type, - Power: request.Power, - Model: request.Model, - Status: types.AI3DJobStatusPending, - PreviewURL: request.ImageURL, - } - - job.Params = utils.JsonEncode(request) - - // 保存到数据库 - if err := s.db.Create(job).Error; err != nil { - return nil, fmt.Errorf("failed to create 3D job: %v", err) - } - - // 更新用户算力 - err := s.userService.DecreasePower(userId, job.Power, model.PowerLog{ - Type: types.PowerConsume, - Model: job.Model, - Remark: fmt.Sprintf("创建3D任务,消耗%d算力", job.Power), - }) - if err != nil { - return nil, fmt.Errorf("failed to update user power: %v", err) - } - - // 将任务添加到队列 - request.JobId = job.Id - s.PushTask(request) - - return job, nil -} - -// PushTask 将任务添加到队列 -func (s *Service) PushTask(job vo.AI3DJobParams) { - logger.Infof("add a new 3D task to the queue: %+v", job) - if err := s.taskQueue.RPush(job); err != nil { - logger.Errorf("push 3D task to queue failed: %v", err) - } -} - -// Run 启动任务处理器 -func (s *Service) Run() { - logger.Info("Starting 3D job consumer...") - go func() { - for { - var params vo.AI3DJobParams - err := s.taskQueue.LPop(¶ms) - if err != nil { - logger.Errorf("taking 3D task with error: %v", err) - continue - } - logger.Infof("handle a new 3D task: %+v", params) - go func() { - if err := s.processJob(¶ms); err != nil { - logger.Errorf("error processing 3D job: %v", err) - s.updateJobStatus(params.JobId, types.AI3DJobStatusFailed, err.Error()) - } - }() - } - }() - - go s.pollJobStatus() -} - -// processJob 处理3D任务 -func (s *Service) processJob(params *vo.AI3DJobParams) error { - // 更新状态为处理中 - s.updateJobStatus(params.JobId, types.AI3DJobStatusProcessing, "") - - var taskId string - var err error - - // 根据类型选择客户端 - switch params.Type { - case types.AI3DTaskTypeTencent: - if s.tencentClient == nil { - return fmt.Errorf("tencent 3D client not initialized") - } - tencentParams := Tencent3DParams{ - Prompt: params.Prompt, - ImageURL: params.ImageURL, - ResultFormat: params.FileFormat, - EnablePBR: params.EnablePBR, - } - taskId, err = s.tencentClient.SubmitJob(tencentParams) - case types.AI3DTaskTypeGitee: - if s.giteeClient == nil { - return fmt.Errorf("gitee 3D client not initialized") - } - giteeParams := Gitee3DParams{ - Model: params.Model, - Texture: params.Texture, - Seed: params.Seed, - NumInferenceSteps: params.NumInferenceSteps, - GuidanceScale: params.GuidanceScale, - OctreeResolution: params.OctreeResolution, - ImageURL: params.ImageURL, - } - if params.Model == "Hunyuan3D-2" { - giteeParams.Type = strings.ToLower(params.FileFormat) - } else { - giteeParams.FileFormat = strings.ToLower(params.FileFormat) - } - taskId, err = s.giteeClient.SubmitJob(giteeParams) - default: - return fmt.Errorf("unsupported 3D API type: %s", params.Type) - } - - if err != nil { - return fmt.Errorf("failed to submit 3D job: %v", err) - } - - // 更新任务ID - s.db.Model(model.AI3DJob{}).Where("id = ?", params.JobId).Update("task_id", taskId) - - return nil -} - -// pollJobStatus 轮询任务状态 -func (s *Service) pollJobStatus() { - // 10秒轮询一次 - ticker := time.NewTicker(10 * time.Second) - defer ticker.Stop() - - for range ticker.C { - var jobs []model.AI3DJob - s.db.Where("status IN (?)", []string{types.AI3DJobStatusProcessing, types.AI3DJobStatusPending}).Find(&jobs) - if len(jobs) == 0 { - logger.Debug("no 3D jobs to poll, sleep 10s") - continue - } - - for _, job := range jobs { - // 15 分钟超时 - if job.CreatedAt.Before(time.Now().Add(-20 * time.Minute)) { - s.updateJobStatus(job.Id, types.AI3DJobStatusFailed, "task timeout") - continue - } - - result, err := s.queryJobStatus(&job) - if err != nil { - logger.Errorf("failed to query job status: %v", err) - continue - } - - updates := map[string]any{ - "status": result.Status, - "raw_data": result.RawData, - "err_msg": result.ErrorMsg, - } - if result.FileURL != "" { - // 下载文件到本地 - url, err := s.uploadManager.GetUploadHandler().PutUrlFile(result.FileURL, getFileExt(result.FileURL), false) - if err != nil { - logger.Errorf("failed to download file: %v", err) - continue - } - updates["file_url"] = url - logger.Infof("download file: %s", url) - } - if result.PreviewURL != "" { - url, err := s.uploadManager.GetUploadHandler().PutUrlFile(result.PreviewURL, getFileExt(result.PreviewURL), false) - if err != nil { - logger.Errorf("failed to download preview image: %v", err) - continue - } - updates["preview_url"] = url - logger.Infof("download preview image: %s", url) - } - - s.db.Model(&model.AI3DJob{}).Where("id = ?", job.Id).Updates(updates) - - } - } -} - -// queryJobStatus 查询任务状态 -func (s *Service) queryJobStatus(job *model.AI3DJob) (*types.AI3DJobResult, error) { - switch job.Type { - case types.AI3DTaskTypeTencent: - if s.tencentClient == nil { - return nil, fmt.Errorf("tencent 3D client not initialized") - } - return s.tencentClient.QueryJob(job.TaskId) - case types.AI3DTaskTypeGitee: - if s.giteeClient == nil { - return nil, fmt.Errorf("gitee 3D client not initialized") - } - return s.giteeClient.QueryJob(job.TaskId) - default: - return nil, fmt.Errorf("unsupported 3D API type: %s", job.Type) - } -} - -// updateJobStatus 更新任务状态 -func (s *Service) updateJobStatus(jobId uint, status string, errMsg string) error { - - return s.db.Model(model.AI3DJob{}).Where("id = ?", jobId).Updates(map[string]any{ - "status": status, - "err_msg": errMsg, - }).Error -} - -// GetJobList 获取任务列表 -func (s *Service) GetJobList(userId uint, page, pageSize int) (*vo.Page, error) { - var total int64 - var jobs []model.AI3DJob - - // 查询总数 - if err := s.db.Model(&model.AI3DJob{}).Where("user_id = ?", userId).Count(&total).Error; err != nil { - return nil, err - } - - // 查询任务列表 - offset := (page - 1) * pageSize - if err := s.db.Where("user_id = ?", userId).Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&jobs).Error; err != nil { - return nil, err - } - - // 转换为VO - var jobList []vo.AI3DJob - for _, job := range jobs { - jobVO := vo.AI3DJob{ - Id: job.Id, - UserId: job.UserId, - Type: job.Type, - Power: job.Power, - TaskId: job.TaskId, - FileURL: job.FileURL, - PreviewURL: job.PreviewURL, - Model: job.Model, - Status: job.Status, - ErrMsg: job.ErrMsg, - CreatedAt: job.CreatedAt.Unix(), - UpdatedAt: job.UpdatedAt.Unix(), - } - _ = utils.JsonDecode(job.Params, &jobVO.Params) - jobList = append(jobList, jobVO) - } - - return &vo.Page{ - Page: page, - PageSize: pageSize, - Total: total, - Items: jobList, - }, nil -} - -// DeleteJob 删除任务 -func (s *Service) DeleteUserJob(id uint, userId uint) error { - var job model.AI3DJob - err := s.db.Where("id = ?", id).Where("user_id = ?", userId).First(&job).Error - if err != nil { - return err - } - - tx := s.db.Begin() - err = tx.Delete(&job).Error - if err != nil { - return err - } - - // 失败的任务要退回算力 - if job.Status == types.AI3DJobStatusFailed { - err = s.userService.IncreasePower(userId, job.Power, model.PowerLog{ - Type: types.PowerRefund, - Model: job.Model, - Remark: fmt.Sprintf("删除任务,退回%d算力", job.Power), - }) - if err != nil { - tx.Rollback() - return err - } - } - tx.Commit() - return nil -} - -// GetSupportedModels 获取支持的模型列表 -func (s *Service) GetSupportedModels() map[string][]types.AI3DModel { - - models := make(map[string][]types.AI3DModel) - if s.tencentClient != nil { - models["tencent"] = s.tencentClient.GetSupportedModels() - } - if s.giteeClient != nil { - models["gitee"] = s.giteeClient.GetSupportedModels() - } - return models -} - -func (s *Service) UpdateConfig(config types.AI3DConfig) { - if s.tencentClient != nil { - s.tencentClient.UpdateConfig(config.Tencent) - } - if s.giteeClient != nil { - s.giteeClient.UpdateConfig(config.Gitee) - } -} - -// getFileExt 获取文件扩展名 -func getFileExt(fileURL string) string { - parse, err := url.Parse(fileURL) - if err != nil { - return "" - } - ext := filepath.Ext(parse.Path) - if ext == "" { - return ".glb" - } - return ext -} diff --git a/api/service/ai3d/tencent_client.go b/api/service/ai3d/tencent_client.go deleted file mode 100644 index 4b971936..00000000 --- a/api/service/ai3d/tencent_client.go +++ /dev/null @@ -1,160 +0,0 @@ -package ai3d - -import ( - "fmt" - "geekai/core/types" - "geekai/utils" - - tencent3d "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ai3d/v20250513" - tencentcloud "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" - "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" -) - -type Tencent3DClient struct { - client *tencent3d.Client - config types.Tencent3DConfig -} - -type Tencent3DParams struct { - Prompt string `json:"prompt"` // 文本提示词 - ImageURL string `json:"image_url"` // 输入图片URL - ResultFormat string `json:"result_format"` // 输出格式 - EnablePBR bool `json:"enable_pbr"` // 是否开启PBR材质 - MultiViewImages []ViewImage `json:"multi_view_images,omitempty"` // 多视角图片 -} - -type ViewImage struct { - ViewType string `json:"view_type"` // 视角类型 (left/right/back) - ViewImageURL string `json:"view_image_url"` // 图片URL -} - -func NewTencent3DClient(sysConfig *types.SystemConfig) (*Tencent3DClient, error) { - config := sysConfig.AI3D.Tencent - credential := tencentcloud.NewCredential(config.SecretId, config.SecretKey) - cpf := profile.NewClientProfile() - cpf.HttpProfile.Endpoint = "ai3d.tencentcloudapi.com" - - client, err := tencent3d.NewClient(credential, config.Region, cpf) - if err != nil { - return nil, fmt.Errorf("failed to create tencent 3D client: %v", err) - } - - return &Tencent3DClient{ - client: client, - config: config, - }, nil -} - -func (c *Tencent3DClient) UpdateConfig(config types.Tencent3DConfig) error { - c.config = config - credential := tencentcloud.NewCredential(config.SecretId, config.SecretKey) - cpf := profile.NewClientProfile() - cpf.HttpProfile.Endpoint = "ai3d.tencentcloudapi.com" - - client, err := tencent3d.NewClient(credential, config.Region, cpf) - if err != nil { - return fmt.Errorf("failed to create tencent 3D client: %v", err) - } - c.client = client - return nil -} - -func (c *Tencent3DClient) GetConfig() *types.Tencent3DConfig { - return &c.config -} - -// SubmitJob 提交3D生成任务 -func (c *Tencent3DClient) SubmitJob(params Tencent3DParams) (string, error) { - request := tencent3d.NewSubmitHunyuanTo3DJobRequest() - - if params.Prompt != "" { - request.Prompt = tencentcloud.StringPtr(params.Prompt) - } - - if params.ImageURL != "" { - request.ImageUrl = tencentcloud.StringPtr(params.ImageURL) - } - - if params.ResultFormat != "" { - request.ResultFormat = tencentcloud.StringPtr(params.ResultFormat) - } - - request.EnablePBR = tencentcloud.BoolPtr(params.EnablePBR) - - if len(params.MultiViewImages) > 0 { - var viewImages []*tencent3d.ViewImage - for _, img := range params.MultiViewImages { - viewImage := &tencent3d.ViewImage{ - ViewType: tencentcloud.StringPtr(img.ViewType), - ViewImageUrl: tencentcloud.StringPtr(img.ViewImageURL), - } - viewImages = append(viewImages, viewImage) - } - request.MultiViewImages = viewImages - } - - response, err := c.client.SubmitHunyuanTo3DJob(request) - if err != nil { - return "", fmt.Errorf("failed to submit tencent 3D job: %v", err) - } - - if response.Response.JobId == nil { - return "", fmt.Errorf("no job ID returned from tencent 3D API") - } - - return *response.Response.JobId, nil -} - -// QueryJob 查询任务状态 -func (c *Tencent3DClient) QueryJob(jobId string) (*types.AI3DJobResult, error) { - request := tencent3d.NewQueryHunyuanTo3DJobRequest() - request.JobId = tencentcloud.StringPtr(jobId) - - response, err := c.client.QueryHunyuanTo3DJob(request) - if err != nil { - return nil, fmt.Errorf("failed to query tencent 3D job: %v", err) - } - - result := &types.AI3DJobResult{ - TaskId: jobId, - } - - // 根据状态设置进度 - switch *response.Response.Status { - case "WAIT": - result.Status = types.AI3DJobStatusPending - case "RUN": - result.Status = types.AI3DJobStatusProcessing - case "DONE": - result.Status = types.AI3DJobStatusSuccess - // 处理结果文件 - if len(response.Response.ResultFile3Ds) > 0 { - // 取第一个文件 - file := response.Response.ResultFile3Ds[0] - if file.Url != nil { - result.FileURL = *file.Url - } - if file.PreviewImageUrl != nil { - result.PreviewURL = *file.PreviewImageUrl - } - } - case "FAIL": - result.Status = types.AI3DJobStatusFailed - if response.Response.ErrorMessage != nil { - result.ErrorMsg = *response.Response.ErrorMessage - } - } - - logger.Debugf("tencent 3D job result: %+v", *response.Response) - - result.RawData = utils.JsonEncode(response.Response) - - return result, nil -} - -// GetSupportedModels 获取支持的模型列表 -func (c *Tencent3DClient) GetSupportedModels() []types.AI3DModel { - return []types.AI3DModel{ - {Name: "Hunyuan3D-3", Power: 500, Formats: []string{"GLB", "OBJ", "STL", "USDZ", "FBX", "MP4"}, Desc: "Hunyuan3D 是腾讯混元团队推出的高质量 3D 生成模型,具备高保真度、细节丰富和高效生成的特点,可快速将文本或图像转换为逼真的 3D 物体。"}, - } -} diff --git a/api/service/migration_service.go b/api/service/migration_service.go index 47027f1c..4db9ea1e 100644 --- a/api/service/migration_service.go +++ b/api/service/migration_service.go @@ -154,24 +154,6 @@ func (s *MigrationService) MigrateConfigContent() error { return fmt.Errorf("迁移配置内容失败: %v", err) } - // 3D生成配置 - if err := s.saveConfig(types.ConfigKeyAI3D, map[string]any{ - "tencent": map[string]any{ - "access_key": "", - "secret_key": "", - "region": "", - "enabled": false, - "models": make([]types.AI3DModel, 0), - }, - "gitee": map[string]any{ - "api_key": "", - "enabled": false, - "models": make([]types.AI3DModel, 0), - }, - }); err != nil { - return fmt.Errorf("迁移配置内容失败: %v", err) - } - return nil } @@ -179,7 +161,6 @@ func (s *MigrationService) MigrateConfigContent() error { func (s *MigrationService) TableMigration() { // 新数据表 s.db.AutoMigrate(&model.Moderation{}) - s.db.AutoMigrate(&model.AI3DJob{}) // 订单字段整理 if s.db.Migrator().HasColumn(&model.Order{}, "pay_type") { diff --git a/api/store/model/ai3d_job.go b/api/store/model/ai3d_job.go deleted file mode 100644 index 6f46dbb8..00000000 --- a/api/store/model/ai3d_job.go +++ /dev/null @@ -1,27 +0,0 @@ -package model - -import ( - "geekai/core/types" - "time" -) - -type AI3DJob struct { - Id uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` - UserId uint `gorm:"column:user_id;type:int(11);not null;comment:用户ID" json:"user_id"` - Type types.AI3DTaskType `gorm:"column:type;type:varchar(20);not null;comment:API类型 (tencent/gitee)" json:"type"` - Power int `gorm:"column:power;type:int(11);not null;comment:消耗算力" json:"power"` - TaskId string `gorm:"column:task_id;type:varchar(100);comment:第三方任务ID" json:"task_id"` - FileURL string `gorm:"column:file_url;type:varchar(1024);comment:生成的3D模型文件地址" json:"file_url"` - PreviewURL string `gorm:"column:preview_url;type:varchar(1024);comment:预览图片地址" json:"preview_url"` - Model string `gorm:"column:model;type:varchar(50);comment:使用的3D模型类型" json:"model"` - Status string `gorm:"column:status;type:varchar(20);not null;default:pending;comment:任务状态" json:"status"` - ErrMsg string `gorm:"column:err_msg;type:varchar(1024);comment:错误信息" json:"err_msg"` - Params string `gorm:"column:params;type:text;comment:任务参数(JSON格式)" json:"params"` - RawData string `gorm:"column:raw_data;type:text;comment:API返回的原始数据" json:"raw_data"` - CreatedAt time.Time `gorm:"column:created_at;type:datetime;not null" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at;type:datetime;not null" json:"updated_at"` -} - -func (m *AI3DJob) TableName() string { - return "geekai_3d_jobs" -} diff --git a/api/store/vo/ai3d_job.go b/api/store/vo/ai3d_job.go deleted file mode 100644 index 7610da9c..00000000 --- a/api/store/vo/ai3d_job.go +++ /dev/null @@ -1,39 +0,0 @@ -package vo - -import "geekai/core/types" - -type AI3DJob struct { - Id uint `json:"id"` - UserId uint `json:"user_id"` - Type types.AI3DTaskType `json:"type"` - Power int `json:"power"` - TaskId string `json:"task_id"` - FileURL string `json:"file_url"` - PreviewURL string `json:"preview_url"` - Model string `json:"model"` - Status string `json:"status"` - ErrMsg string `json:"err_msg"` - Params AI3DJobParams `json:"params"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -// AI3DJobParams 创建3D任务请求 -type AI3DJobParams struct { - // 通用参数 - JobId uint `json:"job_id,omitempty"` // 任务ID - Type types.AI3DTaskType `json:"type,omitempty"` // API类型 (tencent/gitee) - Model string `json:"model,omitempty"` // 3D模型类型 - Prompt string `json:"prompt,omitempty"` // 文本提示词 - ImageURL string `json:"image_url,omitempty"` // 输入图片URL - FileFormat string `json:"file_format,omitempty"` // 输出文件格式 - Power int `json:"power,omitempty"` // 消耗算力 - // 腾讯3d专有参数 - EnablePBR bool `json:"enable_pbr,omitempty"` // 是否开启PBR材质 - // Gitee3d专有参数 - Texture bool `json:"texture,omitempty"` // 是否开启纹理 - Seed int `json:"seed,omitempty"` // 随机种子 - NumInferenceSteps int `json:"num_inference_steps,omitempty"` //迭代次数 - GuidanceScale float64 `json:"guidance_scale,omitempty"` //引导系数 - OctreeResolution int `json:"octree_resolution"` // 3D 渲染精度,越高3D 细节越丰富 -} diff --git a/web/src/assets/css/admin/ai3d.scss b/web/src/assets/css/admin/ai3d.scss deleted file mode 100644 index dfc4a988..00000000 --- a/web/src/assets/css/admin/ai3d.scss +++ /dev/null @@ -1,145 +0,0 @@ -.admin-threed-jobs { - padding: 20px; - - .page-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 24px; - - h2 { - margin: 0; - color: var(--theme-text-color-primary); - } - } - - .search-section { - background: var(--card-bg); - padding: 20px; - border-radius: 8px; - margin-bottom: 20px; - box-shadow: var(--el-box-shadow, 0 2px 4px rgba(0, 0, 0, 0.1)); - - .el-form-item { - margin-bottom: 0; - .el-select__wrapper { - height: 36px; - line-height: 36px; - } - } - } - - .stats-section { - margin-bottom: 20px; - - .stat-card { - background: var(--card-bg); - padding: 20px; - border-radius: 8px; - box-shadow: var(--el-box-shadow, 0 2px 4px rgba(0, 0, 0, 0.1)); - display: flex; - align-items: center; - gap: 16px; - - .stat-icon { - width: 48px; - height: 48px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - - i { - font-size: 24px; - color: white; - } - - &.pending { - background: #e6a23c; - } - - &.processing { - background: #409eff; - } - - &.completed { - background: #67c23a; - } - - &.failed { - background: #f56c6c; - } - } - - .stat-content { - .stat-number { - font-size: 24px; - font-weight: bold; - color: var(--theme-text-color-primary); - margin-bottom: 4px; - } - - .stat-label { - font-size: 14px; - color: var(--theme-text-color-secondary); - } - } - } - } - - .table-section { - background: var(--card-bg); - border-radius: 8px; - box-shadow: var(--el-box-shadow, 0 2px 4px rgba(0, 0, 0, 0.1)); - overflow: hidden; - } - - .pagination-section { - padding: 20px; - text-align: center; - } - - .task-detail { - .task-params, - .task-result, - .task-error { - margin-top: 20px; - - h4 { - margin: 0 0 12px 0; - color: var(--theme-text-color-primary); - font-size: 16px; - } - - .params-content { - background: var(--card-bg); - padding: 12px; - border-radius: 6px; - border: 1px solid var(--line-box); - } - } - - .result-links { - display: flex; - gap: 12px; - } - } - - .preview-container { - text-align: center; - } - - // 3D 模型预览弹窗 - .model-preview-dialog { - .el-dialog__body { - padding: 0 0 16px 0; - background: var(--el-bg-color-overlay); - } - - .model-preview-wrapper { - height: calc(100vh - 125px); - padding: 12px; - background: var(--card-bg); - } - } -} diff --git a/web/src/assets/css/ai3d.scss b/web/src/assets/css/ai3d.scss deleted file mode 100644 index 7a191c18..00000000 --- a/web/src/assets/css/ai3d.scss +++ /dev/null @@ -1,634 +0,0 @@ -.page-threed { - display: flex; - height: 100vh; - background: var(--theme-bg-all); -} - -.params-panel { - width: 400px; - background: var(--card-bg); - border-right: 1px solid var(--line-box); - padding: 20px; - overflow-y: auto; -} - -.platform-tabs { - margin-bottom: 20px; -} - -.platform-info { - display: flex; - align-items: center; - gap: 8px; - - i { - font-size: 18px; - color: var(--text-color-primary); - } -} - -.params-container { - .param-line { - margin-bottom: 16px; - - &.pt { - margin-top: 20px; - } - - .label { - display: block; - font-weight: 600; - color: var(--theme-text-color-primary); - } - } - - .advanced-toggle-btn { - padding: 0; - font-size: 14px; - color: var(--text-color-primary); - border: none; - background: none; - display: flex; - align-items: center; - gap: 4px; - - &:hover { - color: var(--text-color-primary); - background: var(--el-color-primary-light-9); - border-radius: 4px; - } - } - - .advanced-params { - padding: 10px 16px; - border-radius: 8px; - border-left: 4px solid var(--text-color-primary); - } -} - -.power-display { - display: flex; - align-items: center; - gap: 8px; - - .power-value { - font-size: 24px; - font-weight: bold; - color: var(--text-color-primary); - } - - .power-unit { - color: var(--theme-text-color-secondary); - } -} - -.generate-section { - margin-top: 30px; - - .generate-btn { - width: 100%; - height: 44px; - font-size: 16px; - } -} - -.content-panel { - flex: 1; - padding: 20px; - overflow-y: auto; -} - -.task-list { - .list-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 30px; - padding: 20px; - background: var(--panel-bg); - border-radius: 12px; - color: var(--theme-text-color-primary); - - h3 { - margin: 0; - color: var(--theme-text-color-primary); - font-size: 24px; - font-weight: 600; - display: flex; - align-items: center; - - &::before { - content: ''; - width: 4px; - height: 24px; - background: var(--theme-text-color-primary); - margin-right: 12px; - border-radius: 2px; - } - } - - .el-button { - // background: rgba(255, 255, 255, 0.2); - // border: 1px solid rgba(255, 255, 255, 0.3); - // color: white; - - // &:hover { - // background: rgba(255, 255, 255, 0.3); - // border-color: rgba(255, 255, 255, 0.5); - // } - } - } -} - -.task-items { - .task-card { - background: var(--card-bg); - border-radius: 12px; - padding: 16px; - margin-bottom: 16px; - border: 1px solid var(--line-box); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - display: flex; - flex-direction: column; - transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; - position: relative; - overflow: hidden; - - &:hover { - transform: translateY(-5px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - } - - &.task-card-completed { - border-left: 4px solid #67c23a; - background: var(--el-fill-color-light); - } - - &.task-card-processing { - border-left: 4px solid var(--text-color-primary); - background: var(--el-fill-color-light); - } - - &.task-card-failed { - border-left: 4px solid #f56c6c; - background: var(--el-fill-color-light); - } - - &.task-card-default { - border-left: 4px solid #909399; - background: var(--el-fill-color-light); - } - } - - .task-card-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; - padding-bottom: 12px; - border-bottom: 1px dashed #eee; - - .task-info { - display: flex; - align-items: center; - gap: 10px; - - .task-id { - font-size: 18px; - font-weight: bold; - color: var(--theme-text-color-primary); - display: flex; - align-items: center; - - i { - font-size: 20px; - color: var(--text-color-primary); - } - } - - .task-platform { - font-size: 14px; - color: var(--theme-text-color-secondary); - display: flex; - align-items: center; - - i { - font-size: 16px; - color: var(--text-color-primary); - } - } - } - - .task-status-wrapper { - display: flex; - align-items: center; - gap: 10px; - - .task-status { - padding: 4px 10px; - border-radius: 6px; - font-size: 13px; - font-weight: bold; - display: flex; - align-items: center; - - &.pending { - background: var(--el-fill-color-light); - color: #e6a23c; - } - - &.processing { - background: var(--el-fill-color-light); - color: #67c23a; - } - - &.completed { - background: var(--el-fill-color-light); - color: #67c23a; - } - - &.failed { - background: var(--el-fill-color-light); - color: #f56c6c; - } - - i { - font-size: 14px; - margin-right: 4px; - } - } - - .task-power { - font-size: 14px; - color: var(--theme-text-color-secondary); - display: flex; - align-items: center; - - i { - font-size: 14px; - margin-right: 4px; - color: var(--text-color-primary); - } - } - } - } - - .task-card-content { - display: flex; - gap: 16px; - margin-bottom: 12px; - - .task-preview { - flex: 1; - position: relative; - border-radius: 8px; - overflow: hidden; - background: var(--chat-wel-bg); - display: flex; - align-items: center; - justify-content: center; - color: var(--theme-text-color-secondary); - min-height: 200px; - max-height: 200px; - min-width: 200px; - max-width: 200px; - border: 1px solid var(--line-box); - - .preview-image { - width: 100%; - height: 100%; - object-fit: cover; - position: relative; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } - - .preview-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - color: var(--theme-text-color-primary); - font-size: 24px; - opacity: 0; - transition: opacity 0.3s ease; - - &:hover { - opacity: 1; - } - } - } - - .input-image { - width: 100%; - height: 100%; - position: relative; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } - - .input-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - color: white; - font-size: 24px; - opacity: 0; - transition: opacity 0.3s ease; - - &:hover { - opacity: 1; - } - } - } - - .prompt-placeholder { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - color: var(--theme-text-color-secondary); - - i { - font-size: 48px; - margin-bottom: 12px; - } - - span { - font-size: 14px; - } - } - } - - .task-details { - flex: 1; - display: flex; - flex-direction: column; - justify-content: space-between; - - .task-model { - font-size: 16px; - font-weight: bold; - color: var(--theme-text-color-primary); - display: flex; - align-items: center; - - i { - font-size: 18px; - margin-right: 6px; - color: var(--text-color-primary); - } - } - - .task-prompt { - font-size: 14px; - color: var(--theme-text-color-secondary); - display: flex; - align-items: center; - margin-top: 4px; - - i { - font-size: 14px; - margin-right: 6px; - color: var(--theme-text-color-secondary); - } - } - - .task-params { - font-size: 14px; - color: var(--theme-text-color-secondary); - display: flex; - align-items: center; - margin-top: 4px; - - i { - font-size: 14px; - margin-right: 6px; - color: var(--theme-text-color-secondary); - } - } - - .task-time { - font-size: 12px; - color: var(--theme-text-color-secondary); - display: flex; - align-items: center; - margin-top: 4px; - - i { - font-size: 12px; - margin-right: 6px; - } - } - - .task-error { - font-size: 12px; - color: #f56c6c; - display: flex; - align-items: center; - margin-top: 4px; - - i { - font-size: 12px; - margin-right: 6px; - } - } - } - } - - .task-card-footer { - display: flex; - justify-content: flex-end; - gap: 8px; - padding-top: 12px; - border-top: 1px dashed var(--line-box); - - .task-actions { - display: flex; - gap: 8px; - - .action-btn { - padding: 6px 12px; - font-size: 13px; - border-radius: 6px; - display: flex; - align-items: center; - justify-content: center; - gap: 4px; - - &.preview-btn { - background: var(--text-color-primary); - // color: var(--theme-text-color-primary); - border: 1px solid var(--text-color-primary); - - &:hover { - background: var(--text-color-primary); - border-color: var(--border-active); - } - } - - &.download-btn { - background: #67c23a; - // color: var(--theme-text-color-primary); - border: 1px solid #67c23a; - - &:hover { - background: #85ce61; - border-color: #85ce61; - } - } - - &.delete-btn { - background: #f56c6c; - // color: var(--theme-text-color-primary); - border: 1px solid #f56c6c; - - &:hover { - background: #f78989; - border-color: #f78989; - } - } - - &.processing-btn { - background: #909399; - // color: var(--theme-text-color-primary); - border: 1px solid #909399; - cursor: not-allowed; - } - } - } - } - - .empty-state { - width: 100%; - height: 200px; - background: var(--chat-wel-bg); - border-radius: 12px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - color: var(--theme-text-color-secondary); - font-size: 14px; - - i { - font-size: 48px; - margin-bottom: 12px; - } - } -} - -.pagination { - display: flex; - justify-content: center; - margin-top: 20px; -} - -.preview-container { - width: 100%; - height: calc(100vh - 125px); - background: var(--chat-wel-bg); - border-radius: 8px; - position: relative; - - .three-container { - width: 100%; - height: 100%; - background: var(--chat-wel-bg); - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - color: var(--theme-text-color-secondary); - } - - .preview-placeholder { - width: 100%; - min-height: 500px; - background: var(--chat-wel-bg); - border-radius: 8px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - color: var(--theme-text-color-secondary); - - i { - font-size: 48px; - margin-bottom: 12px; - color: var(--theme-text-color-secondary); - } - - p { - margin: 0; - font-size: 14px; - } - } -} - -@media (max-width: 768px) { - .page-threed { - flex-direction: column; - } - - .params-panel { - width: 100%; - border-right: none; - border-bottom: 1px solid var(--line-box); - } - - .task-card-content { - flex-direction: column; - gap: 12px; - } - - .task-preview { - max-width: 100%; - min-height: 150px; - } - - .task-card-header { - flex-direction: column; - align-items: flex-start; - gap: 12px; - } - - .task-status-wrapper { - align-self: flex-end; - } -} - -@media (max-width: 480px) { - .task-card { - padding: 12px; - } - - .task-actions { - flex-wrap: wrap; - justify-content: center; - } - - .action-btn { - flex: 1; - min-width: 80px; - } -} diff --git a/web/src/components/ThreeDPreview.vue b/web/src/components/ThreeDPreview.vue deleted file mode 100644 index 8cdcdb35..00000000 --- a/web/src/components/ThreeDPreview.vue +++ /dev/null @@ -1,562 +0,0 @@ - - - - - diff --git a/web/src/components/admin/AdminSidebar.vue b/web/src/components/admin/AdminSidebar.vue index 0b48288a..5d83dc7e 100644 --- a/web/src/components/admin/AdminSidebar.vue +++ b/web/src/components/admin/AdminSidebar.vue @@ -179,23 +179,6 @@ const items = [ }, ], }, - { - icon: 'cube', - index: '/admin/ai3d', - title: '3D生成', - subs: [ - { - icon: 'list', - index: '/admin/ai3d/jobs', - title: '任务管理', - }, - { - icon: 'config', - index: '/admin/ai3d/config', - title: '配置管理', - }, - ], - }, { icon: 'moderation', diff --git a/web/src/router.js b/web/src/router.js index d45aad08..744defb3 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -92,18 +92,6 @@ const routes = [ meta: { title: 'Suno音乐创作' }, component: () => import('@/views/Suno.vue'), }, - { - name: 'ai3d', - path: '/ai3d', - meta: { title: 'AI3D模型生成' }, - component: () => import('@/views/AIThreeDCreate.vue'), - }, - { - name: 'test3d', - path: '/test3d', - meta: { title: '3D预览测试' }, - component: () => import('@/views/test/Test3D.vue'), - }, { name: 'ExternalLink', path: '/external', @@ -354,18 +342,6 @@ const routes = [ meta: { title: '即梦设置' }, component: () => import('@/views/admin/jimeng/JimengConfig.vue'), }, - { - path: '/admin/ai3d/jobs', - name: 'admin-ai3d-jobs', - meta: { title: '3D任务管理' }, - component: () => import('@/views/admin/ai3d/AIThreeDJobs.vue'), - }, - { - path: '/admin/ai3d/config', - name: 'admin-ai3d-config', - meta: { title: '3D配置管理' }, - component: () => import('@/views/admin/ai3d/AIThreeDConfig.vue'), - }, { path: '/admin/powerLog', name: 'admin-power-log', @@ -478,12 +454,6 @@ const routes = [ name: 'mobile-jimeng', component: () => import('@/views/mobile/JimengCreate.vue'), }, - { - path: '/mobile/3d', - name: 'mobile-3d', - meta: { title: '3D模型生成' }, - component: () => import('@/views/AIThreeDCreate.vue'), - }, ], }, diff --git a/web/src/store/ai3d.js b/web/src/store/ai3d.js deleted file mode 100644 index 523c550e..00000000 --- a/web/src/store/ai3d.js +++ /dev/null @@ -1,361 +0,0 @@ -import { checkSession } from '@/store/cache' -import { showMessageError } from '@/utils/dialog' -import { httpDownload, httpGet, httpPost } from '@/utils/http' -import { replaceImg } from '@/utils/libs' -import { ElMessage, ElMessageBox } from 'element-plus' -import { defineStore } from 'pinia' -import { computed, onMounted, ref } from 'vue' - -export const useAI3DStore = defineStore('ai3d', () => { - // 响应式数据 - const activePlatform = ref('gitee') - const loading = ref(false) - const previewVisible = ref(false) - const currentPage = ref(1) - const pageSize = ref(10) - const total = ref(0) - const taskList = ref([]) - const currentPreviewTask = ref({ - downloading: false, - }) - const giteeAdvancedVisible = ref(false) - const taskPulling = ref(false) - - const tencentDefaultForm = { - text3d: false, - prompt: '', - image_url: '', - model: '', - file_format: '', - enable_pbr: false, - model_desc: '', - power: 0, - } - - const giteeDefaultForm = { - prompt: '', - image_url: '', - model: '', - file_format: '', - texture: false, - seed: 1234, - num_inference_steps: 5, - guidance_scale: 7.5, - octree_resolution: 128, - model_desc: '', - power: 0, - } - - const tencentForm = ref({ ...tencentDefaultForm }) - const giteeForm = ref({ ...giteeDefaultForm }) - const currentPower = ref(0) - const tencentSupportedFormats = ref([]) - const giteeSupportedFormats = ref([]) - - // 定时器引用 - let taskPullHandler = null - - const configs = ref({ - gitee: { models: [] }, - tencent: { models: [] }, - }) - - // 计算属性 - const currentForm = computed(() => - activePlatform.value === 'tencent' ? tencentForm.value : giteeForm.value - ) - const selectedModel = computed(() => currentForm.value.model) - const currentPrompt = computed(() => currentForm.value.prompt) - const currentImage = computed(() => - currentForm.value.image_url ? [{ url: currentForm.value.image_url }] : [] - ) - - // 加载配置 - const loadConfigs = async () => { - const response = await httpGet('/api/ai3d/configs') - configs.value = response.data - } - - const handleModelChange = (value) => { - if (activePlatform.value === 'tencent') { - const model = configs.value.tencent.models.find((m) => m.name === value) - if (!model) return - currentPower.value = model.power - tencentForm.value.power = model.power - tencentForm.value.model_desc = model.desc - tencentForm.value.file_format = model.formats[0] - tencentSupportedFormats.value = model.formats - } else { - const model = configs.value.gitee.models.find((m) => m.name === value) - if (!model) return - currentPower.value = model.power - giteeForm.value.power = model.power - giteeForm.value.model_desc = model.desc - giteeForm.value.file_format = model.formats[0] - giteeSupportedFormats.value = model.formats - } - } - - const handlePlatformChange = (value) => { - activePlatform.value = value - currentPower.value = value === 'tencent' ? tencentForm.value.power : giteeForm.value.power - } - - const generate3D = async () => { - const requestData = { - ...(activePlatform.value === 'tencent' ? tencentForm.value : giteeForm.value), - } - if (requestData.model === '') { - ElMessage.warning('请选择模型') - return - } - if (requestData.file_format === '') { - ElMessage.warning('请选择输出格式') - return - } - - try { - loading.value = true - requestData.type = activePlatform.value - const response = await httpPost('/api/ai3d/generate', requestData) - ElMessage.success('任务创建成功') - await loadTasks() - } catch (error) { - ElMessage.error('创建任务失败:' + error.message) - } finally { - loading.value = false - } - } - - const loadTasks = async () => { - try { - const response = await httpGet('/api/ai3d/jobs', { - page: currentPage.value, - page_size: pageSize.value, - }) - if (response.code === 0) { - let needPull = false - const items = response.data.items - - // 检查是否有进行中的任务 - for (let item of items) { - if (item.status === 'pending' || item.status === 'processing') { - needPull = true - break - } - } - - taskPulling.value = needPull - taskList.value = items - total.value = response.data.total - } - } catch (error) { - ElMessage.error('加载任务列表失败:' + error.message) - } - } - - const refreshTasks = () => { - loadTasks() - } - - const handlePageSizeChange = (size) => { - pageSize.value = size - currentPage.value = 1 - loadTasks() - } - - const handleCurrentPageChange = (page) => { - currentPage.value = page - loadTasks() - } - - const deleteTask = async (taskId) => { - try { - await ElMessageBox.confirm('确定要删除这个任务吗?', '提示', { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning', - }) - const response = await httpGet(`/api/ai3d/job/delete?id=${taskId}`) - if (response.code === 0) { - ElMessage.success('删除成功') - loadTasks() - } else { - ElMessage.error(response.message || '删除失败') - } - } catch (error) { - if (error !== 'cancel') { - ElMessage.error('删除失败:' + error.message) - } - } - } - - const preview3D = (task) => { - currentPreviewTask.value = task - previewVisible.value = true - } - - const closePreview = () => { - previewVisible.value = false - } - - const downloadFile = async (item) => { - const url = replaceImg(item.file_url) - const downloadURL = `/api/download?url=${url}` - const urlObj = new URL(url) - const fileName = urlObj.pathname.split('/').pop() - item.downloading = true - try { - const response = await httpDownload(downloadURL) - const blob = new Blob([response.data]) - const link = document.createElement('a') - link.href = URL.createObjectURL(blob) - link.download = fileName - document.body.appendChild(link) - link.click() - document.body.removeChild(link) - URL.revokeObjectURL(link.href) - item.downloading = false - } catch (error) { - showMessageError('下载失败') - item.downloading = false - } - } - - const downloadCurrentModel = () => { - if (currentPreviewTask.value) { - downloadFile(currentPreviewTask.value) - } - } - - const getStatusText = (status) => { - const statusMap = { - pending: { text: '等待中', type: 'warning' }, - processing: { text: '处理中', type: 'primary' }, - success: { text: '已完成', type: 'success' }, - failed: { text: '失败', type: 'danger' }, - } - return statusMap[status] || status - } - - const getTaskCardClass = (status) => { - if (status === 'success') return 'task-card-completed' - if (status === 'processing') return 'task-card-processing' - if (status === 'failed') return 'task-card-failed' - return 'task-card-default' - } - - const getPlatformIcon = (type) => { - if (type === 'gitee') return 'iconfont icon-gitee' - if (type === 'tencent') return 'iconfont icon-tencent' - return 'iconfont icon-question' - } - - const getPlatformName = (type) => { - if (type === 'gitee') return 'Gitee 模力方舟' - if (type === 'tencent') return '腾讯云混元3D' - return '未知平台' - } - - const getTaskPrompt = (task) => { - return task.params.prompt ? task.params.prompt : '图生3D任务' - } - - const getTaskImageUrl = (task) => { - try { - if (task.params) { - const parsedParams = task.params - return parsedParams.image_url || null - } - return null - } catch (e) { - return null - } - } - - const getTaskParams = (task) => { - const parsedParams = task.params - const params = [] - if (parsedParams.texture) params.push('纹理') - if (parsedParams.enable_pbr) params.push('PBR材质') - if (parsedParams.num_inference_steps) - params.push(`迭代次数: ${parsedParams.num_inference_steps}`) - if (parsedParams.guidance_scale) params.push(`引导系数: ${parsedParams.guidance_scale}`) - if (parsedParams.octree_resolution) params.push(`精度: ${parsedParams.octree_resolution}`) - if (parsedParams.seed) params.push(`Seed: ${parsedParams.seed}`) - return params.join(',') - } - - const startTaskPolling = () => { - taskPullHandler = setInterval(() => { - if (taskPulling.value) { - loadTasks() - } - }, 5000) - } - - const stopTaskPolling = () => { - if (taskPullHandler) { - clearInterval(taskPullHandler) - taskPullHandler = null - } - } - - // 生命周期:加载配置与任务 - onMounted(() => { - loadConfigs() - checkSession() - .then(() => { - loadTasks() - startTaskPolling() - }) - .catch(() => {}) - }) - - return { - // 状态 - activePlatform, - loading, - previewVisible, - currentPage, - pageSize, - total, - taskList, - currentPreviewTask, - giteeAdvancedVisible, - taskPulling, - tencentForm, - giteeForm, - currentPower, - tencentSupportedFormats, - giteeSupportedFormats, - configs, - currentForm, - selectedModel, - currentPrompt, - currentImage, - // 方法 - loadConfigs, - handleModelChange, - handlePlatformChange, - generate3D, - loadTasks, - refreshTasks, - handlePageSizeChange, - handleCurrentPageChange, - deleteTask, - preview3D, - closePreview, - downloadFile, - downloadCurrentModel, - getStatusText, - getTaskCardClass, - getPlatformIcon, - getPlatformName, - getTaskPrompt, - getTaskImageUrl, - getTaskParams, - startTaskPolling, - stopTaskPolling, - } -}) diff --git a/web/src/views/AIThreeDCreate.vue b/web/src/views/AIThreeDCreate.vue deleted file mode 100644 index 5bfe351a..00000000 --- a/web/src/views/AIThreeDCreate.vue +++ /dev/null @@ -1,498 +0,0 @@ - - - - - diff --git a/web/src/views/admin/ai3d/AIThreeDConfig.vue b/web/src/views/admin/ai3d/AIThreeDConfig.vue deleted file mode 100644 index 30c500c4..00000000 --- a/web/src/views/admin/ai3d/AIThreeDConfig.vue +++ /dev/null @@ -1,399 +0,0 @@ - - - - - diff --git a/web/src/views/admin/ai3d/AIThreeDJobs.vue b/web/src/views/admin/ai3d/AIThreeDJobs.vue deleted file mode 100644 index 77cb25ea..00000000 --- a/web/src/views/admin/ai3d/AIThreeDJobs.vue +++ /dev/null @@ -1,469 +0,0 @@ - - - - -