add put url file for oss interface

This commit is contained in:
RockYang 2024-07-23 18:36:26 +08:00
parent e17dcf4d5f
commit 59301df073
19 changed files with 476 additions and 77 deletions

View File

@ -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"` // 微信客服地址

View File

@ -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"` // 延长秒杀
}

View File

@ -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

View File

@ -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) {

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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) => {

View File

@ -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>

View File

@ -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">