mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-04-24 12:04:31 +08:00
remove AI3D module files
This commit is contained in:
@@ -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 效果,比如游戏建模、虚拟现实、动画制作等。"},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 物体。"},
|
||||
}
|
||||
}
|
||||
@@ -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") {
|
||||
|
||||
Reference in New Issue
Block a user