mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	add put url file for oss interface
This commit is contained in:
		@@ -180,6 +180,7 @@ type SystemConfig struct {
 | 
			
		||||
	MjActionPower int `json:"mj_action_power,omitempty"` // MJ 操作(放大,变换)消耗算力
 | 
			
		||||
	SdPower       int `json:"sd_power,omitempty"`        // SD 绘画消耗算力
 | 
			
		||||
	DallPower     int `json:"dall_power,omitempty"`      // DALLE3 绘图消耗算力
 | 
			
		||||
	SunoPower     int `json:"suno_power,omitempty"`      // Suno 生成歌曲消耗算力
 | 
			
		||||
 | 
			
		||||
	WechatCardURL string `json:"wechat_card_url,omitempty"` // 微信客服地址
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -80,14 +80,18 @@ type DallTask struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SunoTask struct {
 | 
			
		||||
	Id           int    `json:"id"`
 | 
			
		||||
	UserId       string `json:"user_id"`
 | 
			
		||||
	Id           uint   `json:"id"`
 | 
			
		||||
	Channel      string `json:"channel"`
 | 
			
		||||
	UserId       int    `json:"user_id"`
 | 
			
		||||
	Type         int    `json:"type"`
 | 
			
		||||
	TaskId       string `json:"task_id"`
 | 
			
		||||
	Title        string `json:"title"`
 | 
			
		||||
	ReferenceId  string `json:"reference_id"`
 | 
			
		||||
	Prompt       string `json:"prompt"`
 | 
			
		||||
	RefTaskId    string `json:"ref_task_id"`
 | 
			
		||||
	RefSongId    string `json:"ref_song_id"`
 | 
			
		||||
	Lyrics       string `json:"lyrics"` // 歌词:自定义模式
 | 
			
		||||
	Prompt       string `json:"prompt"` // 提示词:灵感模式
 | 
			
		||||
	Tags         string `json:"tags"`
 | 
			
		||||
	Model        string `json:"model"`
 | 
			
		||||
	Instrumental bool   `json:"instrumental"` // 是否纯音乐
 | 
			
		||||
	ExtendSecs   int    `json:"extend_secs"`  // 延长秒杀
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -469,7 +469,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, sessi
 | 
			
		||||
	} else {
 | 
			
		||||
		client = http.DefaultClient
 | 
			
		||||
	}
 | 
			
		||||
	logger.Debugf("Sending %s request, ApiURL:%s, API KEY:%s, PROXY: %s, Model: %s", session.Model.Platform, apiKey.ApiURL, apiURL, apiKey.ProxyURL, req.Model)
 | 
			
		||||
	logger.Debugf("Sending %s request, Channel:%s, API KEY:%s, PROXY: %s, Model: %s", session.Model.Platform, apiKey.ApiURL, apiURL, apiKey.ProxyURL, req.Model)
 | 
			
		||||
	request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value))
 | 
			
		||||
	return client.Do(request)
 | 
			
		||||
}
 | 
			
		||||
@@ -607,7 +607,7 @@ func (h *ChatHandler) extractImgUrl(text string) string {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newImgURL, err := h.uploadManager.GetUploadHandler().PutImg(imageURL, false)
 | 
			
		||||
		newImgURL, err := h.uploadManager.GetUploadHandler().PutUrlFile(imageURL, false)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Error("error with download image: ", err)
 | 
			
		||||
			continue
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,22 @@ package handler
 | 
			
		||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"geekai/core"
 | 
			
		||||
	"geekai/core/types"
 | 
			
		||||
	"geekai/service/suno"
 | 
			
		||||
	"geekai/store/model"
 | 
			
		||||
	"geekai/store/vo"
 | 
			
		||||
	"geekai/utils"
 | 
			
		||||
	"geekai/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SunoHandler struct {
 | 
			
		||||
	BaseHandler
 | 
			
		||||
	service *suno.Service
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewSunoHandler(app *core.AppServer, db *gorm.DB) *SunoHandler {
 | 
			
		||||
@@ -48,6 +57,80 @@ func (h *SunoHandler) Client(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
func (h *SunoHandler) Create(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Prompt       string `json:"prompt"`
 | 
			
		||||
		Instrumental bool   `json:"instrumental"`
 | 
			
		||||
		Lyrics       string `json:"lyrics"`
 | 
			
		||||
		Model        string `json:"model"`
 | 
			
		||||
		Tags         string `json:"tags"`
 | 
			
		||||
		Title        string `json:"title"`
 | 
			
		||||
		Type         int    `json:"type"`
 | 
			
		||||
		RefTaskId    string `json:"ref_task_id"` // 续写的任务id
 | 
			
		||||
		ExtendSecs   int    `json:"extend_secs"` // 续写秒数
 | 
			
		||||
		RefSongId    string `json:"ref_song_id"` // 续写的歌曲id
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 插入数据库
 | 
			
		||||
	job := model.SunoJob{
 | 
			
		||||
		UserId:       int(h.GetLoginUserId(c)),
 | 
			
		||||
		Prompt:       data.Prompt,
 | 
			
		||||
		Instrumental: data.Instrumental,
 | 
			
		||||
		ModelName:    data.Model,
 | 
			
		||||
		Tags:         data.Tags,
 | 
			
		||||
		Title:        data.Title,
 | 
			
		||||
		Type:         data.Type,
 | 
			
		||||
		RefSongId:    data.RefSongId,
 | 
			
		||||
		RefTaskId:    data.RefTaskId,
 | 
			
		||||
		ExtendSecs:   data.ExtendSecs,
 | 
			
		||||
		Power:        h.App.SysConfig.SunoPower,
 | 
			
		||||
	}
 | 
			
		||||
	tx := h.DB.Create(&job)
 | 
			
		||||
	if tx.Error != nil {
 | 
			
		||||
		resp.ERROR(c, tx.Error.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 创建任务
 | 
			
		||||
	h.service.PushTask(types.SunoTask{
 | 
			
		||||
		Id:           job.Id,
 | 
			
		||||
		UserId:       job.UserId,
 | 
			
		||||
		Type:         job.Type,
 | 
			
		||||
		Title:        job.Title,
 | 
			
		||||
		Lyrics:       data.Lyrics,
 | 
			
		||||
		RefTaskId:    data.RefTaskId,
 | 
			
		||||
		RefSongId:    data.RefSongId,
 | 
			
		||||
		ExtendSecs:   data.ExtendSecs,
 | 
			
		||||
		Prompt:       data.Prompt,
 | 
			
		||||
		Tags:         data.Tags,
 | 
			
		||||
		Model:        data.Model,
 | 
			
		||||
		Instrumental: data.Instrumental,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// update user's power
 | 
			
		||||
	tx = h.DB.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power - ?", job.Power))
 | 
			
		||||
	// 记录算力变化日志
 | 
			
		||||
	if tx.Error == nil && tx.RowsAffected > 0 {
 | 
			
		||||
		user, _ := h.GetLoginUser(c)
 | 
			
		||||
		h.DB.Create(&model.PowerLog{
 | 
			
		||||
			UserId:    user.Id,
 | 
			
		||||
			Username:  user.Username,
 | 
			
		||||
			Type:      types.PowerConsume,
 | 
			
		||||
			Amount:    job.Power,
 | 
			
		||||
			Balance:   user.Power - job.Power,
 | 
			
		||||
			Mark:      types.PowerSub,
 | 
			
		||||
			Model:     job.ModelName,
 | 
			
		||||
			Remark:    fmt.Sprintf("Suno 文生歌曲,%s", job.ModelName),
 | 
			
		||||
			CreatedAt: time.Now(),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var itemVo vo.SunoJob
 | 
			
		||||
	_ = utils.CopyObject(job, &itemVo)
 | 
			
		||||
	resp.SUCCESS(c, itemVo)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *SunoHandler) List(c *gin.Context) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								api/main.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								api/main.go
									
									
									
									
									
								
							@@ -23,6 +23,7 @@ import (
 | 
			
		||||
	"geekai/service/payment"
 | 
			
		||||
	"geekai/service/sd"
 | 
			
		||||
	"geekai/service/sms"
 | 
			
		||||
	"geekai/service/suno"
 | 
			
		||||
	"geekai/service/wx"
 | 
			
		||||
	"geekai/store"
 | 
			
		||||
	"io"
 | 
			
		||||
@@ -209,6 +210,12 @@ func main() {
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		fx.Provide(suno.NewService),
 | 
			
		||||
		fx.Invoke(func(s *suno.Service) {
 | 
			
		||||
			s.Run()
 | 
			
		||||
			s.SyncTaskProgress()
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		fx.Provide(payment.NewAlipayService),
 | 
			
		||||
		fx.Provide(payment.NewHuPiPay),
 | 
			
		||||
		fx.Provide(payment.NewJPayService),
 | 
			
		||||
@@ -475,6 +482,15 @@ func main() {
 | 
			
		||||
			group.GET("remove", h.Remove)
 | 
			
		||||
			group.GET("publish", h.Publish)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Provide(handler.NewSunoHandler),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.SunoHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/suno")
 | 
			
		||||
			group.Any("client", h.Client)
 | 
			
		||||
			group.POST("create", h.Create)
 | 
			
		||||
			group.GET("list", h.List)
 | 
			
		||||
			group.GET("remove", h.Remove)
 | 
			
		||||
			group.GET("publish", h.Publish)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
 | 
			
		||||
			go func() {
 | 
			
		||||
				err := s.Run(db)
 | 
			
		||||
 
 | 
			
		||||
@@ -166,7 +166,7 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
 | 
			
		||||
		Style:   task.Style,
 | 
			
		||||
		Quality: task.Quality,
 | 
			
		||||
	}
 | 
			
		||||
	logger.Infof("Sending %s request, ApiURL:%s, API KEY:%s, BODY: %+v", apiKey.Platform, apiURL, apiKey.Value, reqBody)
 | 
			
		||||
	logger.Infof("Sending %s request, Channel:%s, API KEY:%s, BODY: %+v", apiKey.Platform, apiURL, apiKey.Value, reqBody)
 | 
			
		||||
	r, err := s.httpClient.R().SetHeader("Content-Type", "application/json").
 | 
			
		||||
		SetHeader("Authorization", "Bearer "+apiKey.Value).
 | 
			
		||||
		SetBody(reqBody).
 | 
			
		||||
@@ -259,7 +259,7 @@ func (s *Service) DownloadImages() {
 | 
			
		||||
 | 
			
		||||
func (s *Service) downloadImage(jobId uint, userId int, orgURL string) (string, error) {
 | 
			
		||||
	// sava image
 | 
			
		||||
	imgURL, err := s.uploadManager.GetUploadHandler().PutImg(orgURL, false)
 | 
			
		||||
	imgURL, err := s.uploadManager.GetUploadHandler().PutUrlFile(orgURL, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -139,7 +139,7 @@ func (p *ServicePool) DownloadImages() {
 | 
			
		||||
				if strings.HasPrefix(v.OrgURL, "https://cdn.discordapp.com") {
 | 
			
		||||
					proxy = true
 | 
			
		||||
				}
 | 
			
		||||
				imgURL, err := p.uploaderManager.GetUploadHandler().PutImg(v.OrgURL, proxy)
 | 
			
		||||
				imgURL, err := p.uploaderManager.GetUploadHandler().PutUrlFile(v.OrgURL, proxy)
 | 
			
		||||
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logger.Errorf("error with download image %s, %v", v.OrgURL, err)
 | 
			
		||||
 
 | 
			
		||||
@@ -84,25 +84,25 @@ func (s AliYunOss) PutFile(ctx *gin.Context, name string) (File, error) {
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) {
 | 
			
		||||
	var imageData []byte
 | 
			
		||||
func (s AliYunOss) PutUrlFile(fileURL string, useProxy bool) (string, error) {
 | 
			
		||||
	var fileData []byte
 | 
			
		||||
	var err error
 | 
			
		||||
	if useProxy {
 | 
			
		||||
		imageData, err = utils.DownloadImage(imageURL, s.proxyURL)
 | 
			
		||||
		fileData, err = utils.DownloadImage(fileURL, s.proxyURL)
 | 
			
		||||
	} else {
 | 
			
		||||
		imageData, err = utils.DownloadImage(imageURL, "")
 | 
			
		||||
		fileData, err = utils.DownloadImage(fileURL, "")
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error with download image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	parse, err := url.Parse(imageURL)
 | 
			
		||||
	parse, err := url.Parse(fileURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error with parse image URL: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	fileExt := utils.GetImgExt(parse.Path)
 | 
			
		||||
	objectKey := fmt.Sprintf("%s/%d%s", s.config.SubDir, time.Now().UnixMicro(), fileExt)
 | 
			
		||||
	// 上传文件字节数据
 | 
			
		||||
	err = s.bucket.PutObject(objectKey, bytes.NewReader(imageData))
 | 
			
		||||
	err = s.bucket.PutObject(objectKey, bytes.NewReader(fileData))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -57,8 +57,8 @@ func (s LocalStorage) PutFile(ctx *gin.Context, name string) (File, error) {
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s LocalStorage) PutImg(imageURL string, useProxy bool) (string, error) {
 | 
			
		||||
	parse, err := url.Parse(imageURL)
 | 
			
		||||
func (s LocalStorage) PutUrlFile(fileURL string, useProxy bool) (string, error) {
 | 
			
		||||
	parse, err := url.Parse(fileURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error with parse image URL: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -69,9 +69,9 @@ func (s LocalStorage) PutImg(imageURL string, useProxy bool) (string, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if useProxy {
 | 
			
		||||
		err = utils.DownloadFile(imageURL, filePath, s.proxyURL)
 | 
			
		||||
		err = utils.DownloadFile(fileURL, filePath, s.proxyURL)
 | 
			
		||||
	} else {
 | 
			
		||||
		err = utils.DownloadFile(imageURL, filePath, "")
 | 
			
		||||
		err = utils.DownloadFile(fileURL, filePath, "")
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error with download image: %v", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -44,18 +44,18 @@ func NewMiniOss(appConfig *types.AppConfig) (MiniOss, error) {
 | 
			
		||||
	return MiniOss{config: config, client: minioClient, proxyURL: appConfig.ProxyURL}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s MiniOss) PutImg(imageURL string, useProxy bool) (string, error) {
 | 
			
		||||
	var imageData []byte
 | 
			
		||||
func (s MiniOss) PutUrlFile(fileURL string, useProxy bool) (string, error) {
 | 
			
		||||
	var fileData []byte
 | 
			
		||||
	var err error
 | 
			
		||||
	if useProxy {
 | 
			
		||||
		imageData, err = utils.DownloadImage(imageURL, s.proxyURL)
 | 
			
		||||
		fileData, err = utils.DownloadImage(fileURL, s.proxyURL)
 | 
			
		||||
	} else {
 | 
			
		||||
		imageData, err = utils.DownloadImage(imageURL, "")
 | 
			
		||||
		fileData, err = utils.DownloadImage(fileURL, "")
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error with download image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	parse, err := url.Parse(imageURL)
 | 
			
		||||
	parse, err := url.Parse(fileURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error with parse image URL: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -65,8 +65,8 @@ func (s MiniOss) PutImg(imageURL string, useProxy bool) (string, error) {
 | 
			
		||||
		context.Background(),
 | 
			
		||||
		s.config.Bucket,
 | 
			
		||||
		filename,
 | 
			
		||||
		strings.NewReader(string(imageData)),
 | 
			
		||||
		int64(len(imageData)),
 | 
			
		||||
		strings.NewReader(string(fileData)),
 | 
			
		||||
		int64(len(fileData)),
 | 
			
		||||
		minio.PutObjectOptions{ContentType: "image/png"})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
 
 | 
			
		||||
@@ -93,18 +93,18 @@ func (s QinNiuOss) PutFile(ctx *gin.Context, name string) (File, error) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s QinNiuOss) PutImg(imageURL string, useProxy bool) (string, error) {
 | 
			
		||||
	var imageData []byte
 | 
			
		||||
func (s QinNiuOss) PutUrlFile(fileURL string, useProxy bool) (string, error) {
 | 
			
		||||
	var fileData []byte
 | 
			
		||||
	var err error
 | 
			
		||||
	if useProxy {
 | 
			
		||||
		imageData, err = utils.DownloadImage(imageURL, s.proxyURL)
 | 
			
		||||
		fileData, err = utils.DownloadImage(fileURL, s.proxyURL)
 | 
			
		||||
	} else {
 | 
			
		||||
		imageData, err = utils.DownloadImage(imageURL, "")
 | 
			
		||||
		fileData, err = utils.DownloadImage(fileURL, "")
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error with download image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	parse, err := url.Parse(imageURL)
 | 
			
		||||
	parse, err := url.Parse(fileURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error with parse image URL: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -113,7 +113,7 @@ func (s QinNiuOss) PutImg(imageURL string, useProxy bool) (string, error) {
 | 
			
		||||
	ret := storage.PutRet{}
 | 
			
		||||
	extra := storage.PutExtra{}
 | 
			
		||||
	// 上传文件字节数据
 | 
			
		||||
	err = s.uploader.Put(context.Background(), &ret, s.putPolicy.UploadToken(s.mac), key, bytes.NewReader(imageData), int64(len(imageData)), &extra)
 | 
			
		||||
	err = s.uploader.Put(context.Background(), &ret, s.putPolicy.UploadToken(s.mac), key, bytes.NewReader(fileData), int64(len(fileData)), &extra)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ type File struct {
 | 
			
		||||
}
 | 
			
		||||
type Uploader interface {
 | 
			
		||||
	PutFile(ctx *gin.Context, name string) (File, error)
 | 
			
		||||
	PutImg(imageURL string, useProxy bool) (string, error)
 | 
			
		||||
	PutUrlFile(url string, useProxy bool) (string, error)
 | 
			
		||||
	PutBase64(imageData string) (string, error)
 | 
			
		||||
	Delete(fileURL string) error
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package dalle
 | 
			
		||||
package suno
 | 
			
		||||
 | 
			
		||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
 | 
			
		||||
@@ -8,11 +8,17 @@ package dalle
 | 
			
		||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"geekai/core/types"
 | 
			
		||||
	logger2 "geekai/logger"
 | 
			
		||||
	"geekai/service/oss"
 | 
			
		||||
	"geekai/store"
 | 
			
		||||
	"geekai/store/model"
 | 
			
		||||
	"geekai/utils"
 | 
			
		||||
	"github.com/go-redis/redis/v8"
 | 
			
		||||
	"io"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/imroc/req/v3"
 | 
			
		||||
@@ -57,10 +63,230 @@ func (s *Service) Run() {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			r, err := s.Create(task)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Errorf("create task with error: %v", err)
 | 
			
		||||
				s.db.UpdateColumns(map[string]interface{}{
 | 
			
		||||
					"err_msg":  err.Error(),
 | 
			
		||||
					"progress": 101,
 | 
			
		||||
				})
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 更新任务信息
 | 
			
		||||
			s.db.Model(&model.SunoJob{Id: task.Id}).UpdateColumns(map[string]interface{}{
 | 
			
		||||
				"task_id": r.Data,
 | 
			
		||||
				"channel": r.Channel,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Service) Create(task types.SunoTask) {
 | 
			
		||||
 | 
			
		||||
type RespVo struct {
 | 
			
		||||
	Code    int    `json:"code"`
 | 
			
		||||
	Message string `json:"message"`
 | 
			
		||||
	Data    string `json:"data"`
 | 
			
		||||
	Channel string `json:"channel,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Service) Create(task types.SunoTask) (RespVo, error) {
 | 
			
		||||
	// 读取 API KEY
 | 
			
		||||
	var apiKey model.ApiKey
 | 
			
		||||
	session := s.db.Session(&gorm.Session{}).Where("type", "suno").Where("enabled", true)
 | 
			
		||||
	if task.Channel != "" {
 | 
			
		||||
		session = session.Where("api_url", task.Channel)
 | 
			
		||||
	}
 | 
			
		||||
	tx := session.Order("last_used_at DESC").First(&apiKey)
 | 
			
		||||
	if tx.Error != nil {
 | 
			
		||||
		return RespVo{}, errors.New("no available API KEY for Suno")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reqBody := map[string]interface{}{
 | 
			
		||||
		"task_id":           task.TaskId,
 | 
			
		||||
		"continue_clip_id":  task.RefSongId,
 | 
			
		||||
		"continue_at":       task.ExtendSecs,
 | 
			
		||||
		"make_instrumental": task.Instrumental,
 | 
			
		||||
	}
 | 
			
		||||
	// 灵感模式
 | 
			
		||||
	if task.Type == 1 {
 | 
			
		||||
		reqBody["gpt_description_prompt"] = task.Prompt
 | 
			
		||||
	} else { // 自定义模式
 | 
			
		||||
		reqBody["prompt"] = task.Lyrics
 | 
			
		||||
		reqBody["tags"] = task.Tags
 | 
			
		||||
		reqBody["mv"] = task.Model
 | 
			
		||||
		reqBody["title"] = task.Title
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res RespVo
 | 
			
		||||
	apiURL := fmt.Sprintf("%s/task/suno/v1/submit/music", apiKey.ApiURL)
 | 
			
		||||
	logger.Debugf("API URL: %s, request body: %+v", apiURL, reqBody)
 | 
			
		||||
	r, err := req.C().R().
 | 
			
		||||
		SetHeader("Authorization", "Bearer "+apiKey.Value).
 | 
			
		||||
		SetBody(reqBody).
 | 
			
		||||
		Post(apiURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return RespVo{}, fmt.Errorf("请求 API 出错:%v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	body, _ := io.ReadAll(r.Body)
 | 
			
		||||
	err = json.Unmarshal(body, &res)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return RespVo{}, fmt.Errorf("解析API数据失败:%s", string(body))
 | 
			
		||||
	}
 | 
			
		||||
	res.Channel = apiKey.ApiURL
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SyncTaskProgress 异步拉取任务
 | 
			
		||||
func (s *Service) SyncTaskProgress() {
 | 
			
		||||
	go func() {
 | 
			
		||||
		var jobs []model.SunoJob
 | 
			
		||||
		for {
 | 
			
		||||
			res := s.db.Where("progress < ?", 100).Where("task_id <> ?", "").Find(&jobs)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, job := range jobs {
 | 
			
		||||
				task, err := s.QueryTask(job.TaskId, job.Channel)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logger.Errorf("query task with error: %v", err)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if task.Code != "success" {
 | 
			
		||||
					logger.Errorf("query task with error: %v", task.Message)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				logger.Debugf("task: %+v", task.Data.Status)
 | 
			
		||||
				// 任务完成,删除旧任务插入两条新任务
 | 
			
		||||
				if task.Data.Status == "SUCCESS" {
 | 
			
		||||
					var jobId = job.Id
 | 
			
		||||
					var flag = false
 | 
			
		||||
					tx := s.db.Begin()
 | 
			
		||||
					for _, v := range task.Data.Data {
 | 
			
		||||
						job.Id = 0
 | 
			
		||||
						job.Progress = 100
 | 
			
		||||
						job.Title = v.Title
 | 
			
		||||
						job.SongId = v.Id
 | 
			
		||||
						job.Duration = int(v.Metadata.Duration)
 | 
			
		||||
						job.Prompt = v.Metadata.Prompt
 | 
			
		||||
						job.Tags = v.Metadata.Tags
 | 
			
		||||
						job.ModelName = v.ModelName
 | 
			
		||||
						job.RawData = utils.JsonEncode(v)
 | 
			
		||||
 | 
			
		||||
						// 下载图片和音频
 | 
			
		||||
						thumbURL, err := s.uploadManager.GetUploadHandler().PutUrlFile(v.ImageUrl, true)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							logger.Errorf("download image with error: %v", err)
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
						coverURL, err := s.uploadManager.GetUploadHandler().PutUrlFile(v.ImageLargeUrl, true)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							logger.Errorf("download image with error: %v", err)
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
						audioURL, err := s.uploadManager.GetUploadHandler().PutUrlFile(v.AudioUrl, true)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							logger.Errorf("download audio with error: %v", err)
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
						job.ThumbImgURL = thumbURL
 | 
			
		||||
						job.CoverImgURL = coverURL
 | 
			
		||||
						job.AudioURL = audioURL
 | 
			
		||||
 | 
			
		||||
						if err = tx.Create(&job).Error; err != nil {
 | 
			
		||||
							logger.Error("create job with error: %v", err)
 | 
			
		||||
							tx.Rollback()
 | 
			
		||||
							break
 | 
			
		||||
						}
 | 
			
		||||
						flag = true
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// 删除旧任务
 | 
			
		||||
					if flag {
 | 
			
		||||
						if err = tx.Delete(&model.SunoJob{}, "id = ?", jobId).Error; err != nil {
 | 
			
		||||
							logger.Error("create job with error: %v", err)
 | 
			
		||||
							tx.Rollback()
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					tx.Commit()
 | 
			
		||||
 | 
			
		||||
				} else if task.Data.FailReason != "" {
 | 
			
		||||
					job.Progress = 101
 | 
			
		||||
					job.ErrMsg = task.Data.FailReason
 | 
			
		||||
					s.db.Updates(&job)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			time.Sleep(time.Second * 10)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type QueryRespVo struct {
 | 
			
		||||
	Code    string `json:"code"`
 | 
			
		||||
	Message string `json:"message"`
 | 
			
		||||
	Data    struct {
 | 
			
		||||
		TaskId     string `json:"task_id"`
 | 
			
		||||
		Action     string `json:"action"`
 | 
			
		||||
		Status     string `json:"status"`
 | 
			
		||||
		FailReason string `json:"fail_reason"`
 | 
			
		||||
		SubmitTime int    `json:"submit_time"`
 | 
			
		||||
		StartTime  int    `json:"start_time"`
 | 
			
		||||
		FinishTime int    `json:"finish_time"`
 | 
			
		||||
		Progress   string `json:"progress"`
 | 
			
		||||
		Data       []struct {
 | 
			
		||||
			Id       string `json:"id"`
 | 
			
		||||
			Title    string `json:"title"`
 | 
			
		||||
			Status   string `json:"status"`
 | 
			
		||||
			Metadata struct {
 | 
			
		||||
				Tags         string      `json:"tags"`
 | 
			
		||||
				Type         string      `json:"type"`
 | 
			
		||||
				Prompt       string      `json:"prompt"`
 | 
			
		||||
				Stream       bool        `json:"stream"`
 | 
			
		||||
				Duration     float64     `json:"duration"`
 | 
			
		||||
				ErrorMessage interface{} `json:"error_message"`
 | 
			
		||||
			} `json:"metadata"`
 | 
			
		||||
			AudioUrl          string `json:"audio_url"`
 | 
			
		||||
			ImageUrl          string `json:"image_url"`
 | 
			
		||||
			VideoUrl          string `json:"video_url"`
 | 
			
		||||
			ModelName         string `json:"model_name"`
 | 
			
		||||
			DisplayName       string `json:"display_name"`
 | 
			
		||||
			ImageLargeUrl     string `json:"image_large_url"`
 | 
			
		||||
			MajorModelVersion string `json:"major_model_version"`
 | 
			
		||||
		} `json:"data"`
 | 
			
		||||
	} `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Service) QueryTask(taskId string, channel string) (QueryRespVo, error) {
 | 
			
		||||
	// 读取 API KEY
 | 
			
		||||
	var apiKey model.ApiKey
 | 
			
		||||
	tx := s.db.Session(&gorm.Session{}).Where("type", "suno").
 | 
			
		||||
		Where("api_url", channel).
 | 
			
		||||
		Where("enabled", true).
 | 
			
		||||
		Order("last_used_at DESC").First(&apiKey)
 | 
			
		||||
	if tx.Error != nil {
 | 
			
		||||
		return QueryRespVo{}, errors.New("no available API KEY for Suno")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	apiURL := fmt.Sprintf("%s/task/suno/v1/fetch/%s", apiKey.ApiURL, taskId)
 | 
			
		||||
	var res QueryRespVo
 | 
			
		||||
	r, err := req.C().R().SetHeader("Authorization", "Bearer "+apiKey.Value).Get(apiURL)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return QueryRespVo{}, fmt.Errorf("请求 API 失败:%v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer r.Body.Close()
 | 
			
		||||
	body, _ := io.ReadAll(r.Body)
 | 
			
		||||
	err = json.Unmarshal(body, &res)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return QueryRespVo{}, fmt.Errorf("解析API数据失败:%s", string(body))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,16 @@ import "time"
 | 
			
		||||
type SunoJob struct {
 | 
			
		||||
	Id           uint `gorm:"primarykey;column:id"`
 | 
			
		||||
	UserId       int
 | 
			
		||||
	Channel      string // 频道
 | 
			
		||||
	Title        string
 | 
			
		||||
	Type         string
 | 
			
		||||
	Type         int
 | 
			
		||||
	TaskId       string
 | 
			
		||||
	ReferenceId  string // 续写的任务id
 | 
			
		||||
	RefTaskId    string // 续写的任务id
 | 
			
		||||
	Tags         string // 歌曲风格和标签
 | 
			
		||||
	Instrumental bool   // 是否生成纯音乐
 | 
			
		||||
	ExtendSecs   int    // 续写秒数
 | 
			
		||||
	SongId       int    // 续写的歌曲id
 | 
			
		||||
	SongId       string // 续写的歌曲id
 | 
			
		||||
	RefSongId    string
 | 
			
		||||
	Prompt       string // 提示词
 | 
			
		||||
	ThumbImgURL  string // 缩略图 URL
 | 
			
		||||
	CoverImgURL  string // 封面图 URL
 | 
			
		||||
 
 | 
			
		||||
@@ -3,27 +3,29 @@ package vo
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type SunoJob struct {
 | 
			
		||||
	Id           uint   `json:"id"`
 | 
			
		||||
	UserId       int    `json:"user_id"`
 | 
			
		||||
	Title        string `json:"title"`
 | 
			
		||||
	Type         string `json:"type"`
 | 
			
		||||
	TaskId       string `json:"task_id"`
 | 
			
		||||
	ReferenceId  string `json:"reference_id"`  // 续写的任务id
 | 
			
		||||
	Tags         string `json:"tags"`          // 歌曲风格和标签
 | 
			
		||||
	Instrumental bool   `json:"instrumental"`  // 是否生成纯音乐
 | 
			
		||||
	ExtendSecs   int    `json:"extend_secs"`   // 续写秒数
 | 
			
		||||
	SongId       int    `json:"song_id"`       // 续写的歌曲id
 | 
			
		||||
	Prompt       string `json:"prompt"`        // 提示词
 | 
			
		||||
	ThumbImgURL  string `json:"thumb_img_url"` // 缩略图 URL
 | 
			
		||||
	CoverImgURL  string `json:"cover_img_url"` // 封面图 URL
 | 
			
		||||
	AudioURL     string `json:"audio_url"`     // 音频 URL
 | 
			
		||||
	ModelName    string `json:"model_name"`    // 模型名称
 | 
			
		||||
	Progress     int    `json:"progress"`      // 任务进度
 | 
			
		||||
	Duration     int    `json:"duration"`      // 银屏时长,秒
 | 
			
		||||
	Publish      bool   `json:"publish"`       // 是否发布
 | 
			
		||||
	ErrMsg       string `json:"err_msg"`       // 错误信息
 | 
			
		||||
	RawData      string `json:"raw_data"`      // 原始数据 json
 | 
			
		||||
	Power        int    `json:"power"`         // 消耗算力
 | 
			
		||||
	Id           uint                   `json:"id"`
 | 
			
		||||
	UserId       int                    `json:"user_id"`
 | 
			
		||||
	Channel      string                 `json:"channel"`
 | 
			
		||||
	Title        string                 `json:"title"`
 | 
			
		||||
	Type         string                 `json:"type"`
 | 
			
		||||
	TaskId       string                 `json:"task_id"`
 | 
			
		||||
	RefTaskId    string                 `json:"ref_task_id"`   // 续写的任务id
 | 
			
		||||
	Tags         string                 `json:"tags"`          // 歌曲风格和标签
 | 
			
		||||
	Instrumental bool                   `json:"instrumental"`  // 是否生成纯音乐
 | 
			
		||||
	ExtendSecs   int                    `json:"extend_secs"`   // 续写秒数
 | 
			
		||||
	SongId       string                 `json:"song_id"`       // 续写的歌曲id
 | 
			
		||||
	RefSongId    string                 `json:"ref_song_id"`   // 续写的歌曲id
 | 
			
		||||
	Prompt       string                 `json:"prompt"`        // 提示词
 | 
			
		||||
	ThumbImgURL  string                 `json:"thumb_img_url"` // 缩略图 URL
 | 
			
		||||
	CoverImgURL  string                 `json:"cover_img_url"` // 封面图 URL
 | 
			
		||||
	AudioURL     string                 `json:"audio_url"`     // 音频 URL
 | 
			
		||||
	ModelName    string                 `json:"model_name"`    // 模型名称
 | 
			
		||||
	Progress     int                    `json:"progress"`      // 任务进度
 | 
			
		||||
	Duration     int                    `json:"duration"`      // 银屏时长,秒
 | 
			
		||||
	Publish      bool                   `json:"publish"`       // 是否发布
 | 
			
		||||
	ErrMsg       string                 `json:"err_msg"`       // 错误信息
 | 
			
		||||
	RawData      map[string]interface{} `json:"raw_data"`      // 原始数据 json
 | 
			
		||||
	Power        int                    `json:"power"`         // 消耗算力
 | 
			
		||||
	CreatedAt    time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,30 @@
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .tag-select {
 | 
			
		||||
        position relative
 | 
			
		||||
        overflow-x auto
 | 
			
		||||
        overflow-y hidden
 | 
			
		||||
        width 100%
 | 
			
		||||
 | 
			
		||||
        .inner {
 | 
			
		||||
          display flex
 | 
			
		||||
          flex-flow row
 | 
			
		||||
          padding-bottom 10px
 | 
			
		||||
 | 
			
		||||
          .tag {
 | 
			
		||||
            margin-right 10px
 | 
			
		||||
            word-break keep-all
 | 
			
		||||
            background-color #312C2C
 | 
			
		||||
            color #e1e1e1
 | 
			
		||||
            border-radius 5px
 | 
			
		||||
            padding 3px 6px
 | 
			
		||||
            cursor pointer
 | 
			
		||||
            font-size 13px
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .right-box {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {ref, watch} from "vue";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  value : {
 | 
			
		||||
@@ -43,6 +43,9 @@ const props = defineProps({
 | 
			
		||||
    default: 1024
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
watch(() => props.value, (newValue) => {
 | 
			
		||||
  model.value = newValue
 | 
			
		||||
})
 | 
			
		||||
const model = ref(props.value)
 | 
			
		||||
const emits = defineEmits(['update:value']);
 | 
			
		||||
const onInput = (value) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,11 +10,11 @@
 | 
			
		||||
 | 
			
		||||
      <div class="params">
 | 
			
		||||
        <div class="pure-music">
 | 
			
		||||
          <span class="switch"><black-switch  v-model:value="instrumental" size="default"  /></span>
 | 
			
		||||
          <span class="switch"><black-switch  v-model:value="data.instrumental" size="default"  /></span>
 | 
			
		||||
          <span class="text">纯音乐</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-if="custom">
 | 
			
		||||
          <div class="item-group" v-if="!instrumental">
 | 
			
		||||
          <div class="item-group" v-if="!data.instrumental">
 | 
			
		||||
            <div class="label">
 | 
			
		||||
              <span class="text">歌词</span>
 | 
			
		||||
              <el-popover placement="right"
 | 
			
		||||
@@ -44,20 +44,20 @@
 | 
			
		||||
                  </el-icon>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-popover>
 | 
			
		||||
              <div class="tag-select">
 | 
			
		||||
                <el-tag
 | 
			
		||||
                  v-for="tag in tags"
 | 
			
		||||
                  :key="tag"
 | 
			
		||||
                  :type="tag === data.tags ? 'success' : ''"
 | 
			
		||||
                  :hit="tag === data.tags"
 | 
			
		||||
                  style="margin-right: 10px"
 | 
			
		||||
                  @click="data.tags = tag"
 | 
			
		||||
                >{{ tag }}</el-tag>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="item">
 | 
			
		||||
              <black-input v-model:value="data.tags" type="textarea" :maxlength="120" :rows="3" placeholder="请输入音乐风格,多个风格之间用英文逗号隔开..."/>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="tag-select">
 | 
			
		||||
              <div class="inner">
 | 
			
		||||
                <span
 | 
			
		||||
                    class="tag"
 | 
			
		||||
                    @click="selectTag(tag)"
 | 
			
		||||
                    v-for="tag in tags"
 | 
			
		||||
                    :key="tag.value">{{ tag.label }}</span>
 | 
			
		||||
              </div>
 | 
			
		||||
              </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="item-group">
 | 
			
		||||
@@ -93,7 +93,7 @@
 | 
			
		||||
            </el-popover>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="item">
 | 
			
		||||
            <black-input v-model:value="data.lyrics" type="textarea" :rows="10" placeholder="例如:一首关于鸟人的摇滚歌曲..."/>
 | 
			
		||||
            <black-input v-model:value="data.prompt" type="textarea" :rows="10" placeholder="例如:一首关于鸟人的摇滚歌曲..."/>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@@ -182,21 +182,41 @@ import BlackSwitch from "@/components/ui/BlackSwitch.vue";
 | 
			
		||||
import BlackInput from "@/components/ui/BlackInput.vue";
 | 
			
		||||
import MusicPlayer from "@/components/MusicPlayer.vue";
 | 
			
		||||
import {compact} from "lodash";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
 | 
			
		||||
import {showSuccessToast} from "vant";
 | 
			
		||||
 | 
			
		||||
const winHeight = ref(window.innerHeight - 50)
 | 
			
		||||
const custom = ref(false)
 | 
			
		||||
const instrumental = ref(false)
 | 
			
		||||
const models = ref([
 | 
			
		||||
  {label: "v3.0", value: "chirp-v3-0"},
 | 
			
		||||
  {label: "v3.5", value:"chirp-v3-5"}
 | 
			
		||||
])
 | 
			
		||||
const tags = ref([])
 | 
			
		||||
const tags = ref([
 | 
			
		||||
  {label: "女声", value: "female vocals"},
 | 
			
		||||
  {label: "男声", value: "male vocals"},
 | 
			
		||||
  {label: "流行", value: "pop"},
 | 
			
		||||
  {label: "摇滚", value: "rock"},
 | 
			
		||||
  {label: "硬摇滚", value: "hard rock"},
 | 
			
		||||
  {label: "电音", value: "electronic"},
 | 
			
		||||
  {label: "金属", value: "metal"},
 | 
			
		||||
  {label: "重金属", value: "heavy metal"},
 | 
			
		||||
  {label: "节拍", value: "beat"},
 | 
			
		||||
  {label: "弱拍", value: "upbeat"},
 | 
			
		||||
  {label: "合成器", value: "synth"},
 | 
			
		||||
  {label: "吉他", value: "guitar"},
 | 
			
		||||
  {label: "钢琴", value: "piano"},
 | 
			
		||||
  {label: "小提琴", value: "violin"},
 | 
			
		||||
  {label: "贝斯", value: "bass"},
 | 
			
		||||
  {label: "嘻哈", value: "hip hop"},
 | 
			
		||||
])
 | 
			
		||||
const data = ref({
 | 
			
		||||
  model: "chirp-v3-0",
 | 
			
		||||
  tags: "",
 | 
			
		||||
  lyrics: "",
 | 
			
		||||
  prompt: "",
 | 
			
		||||
  title: ""
 | 
			
		||||
  title: "",
 | 
			
		||||
  instrumental:false
 | 
			
		||||
})
 | 
			
		||||
const loading = ref(false)
 | 
			
		||||
const noData = ref(false)
 | 
			
		||||
@@ -247,7 +267,14 @@ const list = ref([
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
const create = () => {
 | 
			
		||||
  data.value.type = custom.value ? 2 : 1
 | 
			
		||||
  console.log(data.value)
 | 
			
		||||
  httpPost("/api/suno/create", data.value).then(res => {
 | 
			
		||||
    console.log(res)
 | 
			
		||||
    showMessageOK("创建任务成功")
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError("创建任务失败:"+e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const play = (item) => {
 | 
			
		||||
@@ -261,6 +288,14 @@ const duration = (secs) => {
 | 
			
		||||
  const seconds = secs%60
 | 
			
		||||
  return `${minutes}:${seconds}`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const selectTag = (tag) => {
 | 
			
		||||
  if (data.value.tags.length + tag.value.length >= 119) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
  data.value.tags = compact([...data.value.tags.split(","), tag.value]).join(",")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -254,6 +254,9 @@
 | 
			
		||||
                <el-form-item label="DALL-E-3算力" prop="dall_power">
 | 
			
		||||
                  <el-input v-model.number="system['dall_power']" placeholder="使用DALL-E-3画一张图消耗算力"/>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="Suno 算力" prop="suno_power">
 | 
			
		||||
                  <el-input v-model.number="system['suno_power']" placeholder="使用 Suno 生成一首音乐消耗算力"/>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
              </el-tab-pane>
 | 
			
		||||
              <el-tab-pane label="众筹支付">
 | 
			
		||||
                <el-form-item label="启用众筹功能" prop="enabled_reward">
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user