mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
suno add new function for merging full songs and upload custom music
This commit is contained in:
parent
568201ebbb
commit
f6d8fbf570
@ -87,11 +87,13 @@ type SunoTask struct {
|
|||||||
Type int `json:"type"`
|
Type int `json:"type"`
|
||||||
TaskId string `json:"task_id"`
|
TaskId string `json:"task_id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
RefTaskId string `json:"ref_task_id"`
|
RefTaskId string `json:"ref_task_id,omitempty"`
|
||||||
RefSongId string `json:"ref_song_id"`
|
RefSongId string `json:"ref_song_id,omitempty"`
|
||||||
Prompt string `json:"prompt"` // 提示词/歌词
|
Prompt string `json:"prompt"` // 提示词/歌词
|
||||||
Tags string `json:"tags"`
|
Tags string `json:"tags"`
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Instrumental bool `json:"instrumental"` // 是否纯音乐
|
Instrumental bool `json:"instrumental"` // 是否纯音乐
|
||||||
ExtendSecs int `json:"extend_secs"` // 延长秒杀
|
ExtendSecs int `json:"extend_secs,omitempty"` // 延长秒杀
|
||||||
|
SongId string `json:"song_id,omitempty"` // 合并歌曲ID
|
||||||
|
AudioURL string `json:"audio_url"` // 用户上传音频地址
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"geekai/core"
|
"geekai/core"
|
||||||
"geekai/core/types"
|
|
||||||
"geekai/store/model"
|
"geekai/store/model"
|
||||||
"geekai/store/vo"
|
"geekai/store/vo"
|
||||||
"geekai/utils"
|
"geekai/utils"
|
||||||
@ -59,23 +58,16 @@ func (h *InviteHandler) Code(c *gin.Context) {
|
|||||||
|
|
||||||
// List Log 用户邀请记录
|
// List Log 用户邀请记录
|
||||||
func (h *InviteHandler) List(c *gin.Context) {
|
func (h *InviteHandler) List(c *gin.Context) {
|
||||||
|
page := h.GetInt(c, "page", 1)
|
||||||
var data struct {
|
pageSize := h.GetInt(c, "page_size", 20)
|
||||||
Page int `json:"page"`
|
|
||||||
PageSize int `json:"page_size"`
|
|
||||||
}
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userId := h.GetLoginUserId(c)
|
userId := h.GetLoginUserId(c)
|
||||||
session := h.DB.Session(&gorm.Session{}).Where("inviter_id = ?", userId)
|
session := h.DB.Session(&gorm.Session{}).Where("inviter_id = ?", userId)
|
||||||
var total int64
|
var total int64
|
||||||
session.Model(&model.InviteLog{}).Count(&total)
|
session.Model(&model.InviteLog{}).Count(&total)
|
||||||
var items []model.InviteLog
|
var items []model.InviteLog
|
||||||
var list = make([]vo.InviteLog, 0)
|
var list = make([]vo.InviteLog, 0)
|
||||||
offset := (data.Page - 1) * data.PageSize
|
offset := (page - 1) * pageSize
|
||||||
res := session.Order("id DESC").Offset(offset).Limit(data.PageSize).Find(&items)
|
res := session.Order("id DESC").Offset(offset).Limit(pageSize).Find(&items)
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
var v vo.InviteLog
|
var v vo.InviteLog
|
||||||
@ -89,7 +81,7 @@ func (h *InviteHandler) List(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, list))
|
resp.SUCCESS(c, vo.NewPage(total, page, pageSize, list))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hits 访问邀请码
|
// Hits 访问邀请码
|
||||||
|
@ -72,15 +72,32 @@ func (h *SunoHandler) Create(c *gin.Context) {
|
|||||||
Tags string `json:"tags"`
|
Tags string `json:"tags"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Type int `json:"type"`
|
Type int `json:"type"`
|
||||||
RefTaskId string `json:"ref_task_id"` // 续写的任务id
|
RefTaskId string `json:"ref_task_id"` // 续写的任务id
|
||||||
ExtendSecs int `json:"extend_secs"` // 续写秒数
|
ExtendSecs int `json:"extend_secs"` // 续写秒数
|
||||||
RefSongId string `json:"ref_song_id"` // 续写的歌曲id
|
RefSongId string `json:"ref_song_id"` // 续写的歌曲id
|
||||||
|
SongId string `json:"song_id,omitempty"` // 要拼接的歌曲id
|
||||||
|
AudioURL string `json:"audio_url,omitempty"` // 上传自己创作的歌曲
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 歌曲拼接
|
||||||
|
if data.SongId != "" && data.Type == 3 {
|
||||||
|
var song model.SunoJob
|
||||||
|
if err := h.DB.Where("song_id = ?", data.SongId).First(&song).Error; err == nil {
|
||||||
|
data.Instrumental = song.Instrumental
|
||||||
|
data.Model = song.ModelName
|
||||||
|
data.Tags = song.Tags
|
||||||
|
}
|
||||||
|
// 拼接歌词
|
||||||
|
var refSong model.SunoJob
|
||||||
|
if err := h.DB.Where("song_id = ?", data.RefSongId).First(&refSong).Error; err == nil {
|
||||||
|
data.Prompt = fmt.Sprintf("%s\n%s", song.Prompt, refSong.Prompt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 插入数据库
|
// 插入数据库
|
||||||
job := model.SunoJob{
|
job := model.SunoJob{
|
||||||
UserId: int(h.GetLoginUserId(c)),
|
UserId: int(h.GetLoginUserId(c)),
|
||||||
@ -118,6 +135,8 @@ func (h *SunoHandler) Create(c *gin.Context) {
|
|||||||
Tags: data.Tags,
|
Tags: data.Tags,
|
||||||
Model: data.Model,
|
Model: data.Model,
|
||||||
Instrumental: data.Instrumental,
|
Instrumental: data.Instrumental,
|
||||||
|
SongId: data.SongId,
|
||||||
|
AudioURL: data.AudioURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
// update user's power
|
// update user's power
|
||||||
|
@ -400,7 +400,7 @@ func main() {
|
|||||||
fx.Invoke(func(s *core.AppServer, h *handler.InviteHandler) {
|
fx.Invoke(func(s *core.AppServer, h *handler.InviteHandler) {
|
||||||
group := s.Engine.Group("/api/invite/")
|
group := s.Engine.Group("/api/invite/")
|
||||||
group.GET("code", h.Code)
|
group.GET("code", h.Code)
|
||||||
group.POST("list", h.List)
|
group.GET("list", h.List)
|
||||||
group.GET("hits", h.Hits)
|
group.GET("hits", h.Hits)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -82,14 +82,21 @@ func (s *Service) Run() {
|
|||||||
logger.Errorf("taking task with error: %v", err)
|
logger.Errorf("taking task with error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
var r RespVo
|
||||||
r, err := s.Create(task)
|
if task.Type == 3 && task.SongId != "" { // 歌曲拼接
|
||||||
|
r, err = s.Merge(task)
|
||||||
|
} else if task.Type == 4 && task.AudioURL != "" { // 上传歌曲
|
||||||
|
r, err = s.Upload(task)
|
||||||
|
} else { // 歌曲创作
|
||||||
|
r, err = s.Create(task)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("create task with error: %v", err)
|
logger.Errorf("create task with error: %v", err)
|
||||||
s.db.Model(&model.SunoJob{Id: task.Id}).UpdateColumns(map[string]interface{}{
|
s.db.Model(&model.SunoJob{Id: task.Id}).UpdateColumns(map[string]interface{}{
|
||||||
"err_msg": err.Error(),
|
"err_msg": err.Error(),
|
||||||
"progress": service.FailTaskProgress,
|
"progress": service.FailTaskProgress,
|
||||||
})
|
})
|
||||||
|
s.notifyQueue.RPush(service.NotifyMessage{UserId: task.UserId, JobId: int(task.Id), Message: service.TaskStatusFailed})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +145,94 @@ func (s *Service) Create(task types.SunoTask) (RespVo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var res RespVo
|
var res RespVo
|
||||||
apiURL := fmt.Sprintf("%s/task/suno/v1/submit/music", apiKey.ApiURL)
|
apiURL := fmt.Sprintf("%s/suno/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数据失败:%v, %s", err, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Code != "success" {
|
||||||
|
return RespVo{}, fmt.Errorf("API 返回失败:%s", res.Message)
|
||||||
|
}
|
||||||
|
// update the last_use_at for api key
|
||||||
|
apiKey.LastUsedAt = time.Now().Unix()
|
||||||
|
session.Updates(&apiKey)
|
||||||
|
res.Channel = apiKey.ApiURL
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Merge(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{}{
|
||||||
|
"clip_id": task.SongId,
|
||||||
|
"is_infill": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
var res RespVo
|
||||||
|
apiURL := fmt.Sprintf("%s/suno/submit/concat", 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数据失败:%v, %s", err, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Code != "success" {
|
||||||
|
return RespVo{}, fmt.Errorf("API 返回失败:%s", res.Message)
|
||||||
|
}
|
||||||
|
// update the last_use_at for api key
|
||||||
|
apiKey.LastUsedAt = time.Now().Unix()
|
||||||
|
session.Updates(&apiKey)
|
||||||
|
res.Channel = apiKey.ApiURL
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Upload(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{}{
|
||||||
|
"url": task.AudioURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
var res RespVo
|
||||||
|
apiURL := fmt.Sprintf("%s/suno/uploads/audio-url", apiKey.ApiURL)
|
||||||
logger.Debugf("API URL: %s, request body: %+v", apiURL, reqBody)
|
logger.Debugf("API URL: %s, request body: %+v", apiURL, reqBody)
|
||||||
r, err := req.C().R().
|
r, err := req.C().R().
|
||||||
SetHeader("Authorization", "Bearer "+apiKey.Value).
|
SetHeader("Authorization", "Bearer "+apiKey.Value).
|
||||||
@ -339,7 +433,7 @@ func (s *Service) QueryTask(taskId string, channel string) (QueryRespVo, error)
|
|||||||
return QueryRespVo{}, errors.New("no available API KEY for Suno")
|
return QueryRespVo{}, errors.New("no available API KEY for Suno")
|
||||||
}
|
}
|
||||||
|
|
||||||
apiURL := fmt.Sprintf("%s/task/suno/v1/fetch/%s", apiKey.ApiURL, taskId)
|
apiURL := fmt.Sprintf("%s/suno/fetch/%s", apiKey.ApiURL, taskId)
|
||||||
var res QueryRespVo
|
var res QueryRespVo
|
||||||
r, err := req.C().R().SetHeader("Authorization", "Bearer "+apiKey.Value).Get(apiURL)
|
r, err := req.C().R().SetHeader("Authorization", "Bearer "+apiKey.Value).Get(apiURL)
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ type SunoJob struct {
|
|||||||
UserId int `json:"user_id"`
|
UserId int `json:"user_id"`
|
||||||
Channel string `json:"channel"`
|
Channel string `json:"channel"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Type string `json:"type"`
|
Type int `json:"type"`
|
||||||
TaskId string `json:"task_id"`
|
TaskId string `json:"task_id"`
|
||||||
RefTaskId string `json:"ref_task_id"` // 续写的任务id
|
RefTaskId string `json:"ref_task_id"` // 续写的任务id
|
||||||
Tags string `json:"tags"` // 歌曲风格和标签
|
Tags string `json:"tags"` // 歌曲风格和标签
|
||||||
|
@ -13,6 +13,13 @@
|
|||||||
display flex
|
display flex
|
||||||
flex-flow row
|
flex-flow row
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.upload-music {
|
||||||
|
.iconfont {
|
||||||
|
margin-right 5px
|
||||||
|
font-size 14px
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.params {
|
.params {
|
||||||
@ -85,6 +92,10 @@
|
|||||||
height 50px
|
height 50px
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
}
|
}
|
||||||
|
.icon-mp3 {
|
||||||
|
font-size 42px
|
||||||
|
color #A85295
|
||||||
|
}
|
||||||
.title {
|
.title {
|
||||||
display flex
|
display flex
|
||||||
margin-left 10px
|
margin-left 10px
|
||||||
@ -266,7 +277,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
min-width 320px;
|
min-width 350px;
|
||||||
font-size 14px
|
font-size 14px
|
||||||
padding 0 15px
|
padding 0 15px
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4125778 */
|
font-family: "iconfont"; /* Project id 4125778 */
|
||||||
src: url('iconfont.woff2?t=1723593727785') format('woff2'),
|
src: url('iconfont.woff2?t=1725000514997') format('woff2'),
|
||||||
url('iconfont.woff?t=1723593727785') format('woff'),
|
url('iconfont.woff?t=1725000514997') format('woff'),
|
||||||
url('iconfont.ttf?t=1723593727785') format('truetype');
|
url('iconfont.ttf?t=1725000514997') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -13,6 +13,18 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-merge:before {
|
||||||
|
content: "\e901";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-upload:before {
|
||||||
|
content: "\e611";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-concat:before {
|
||||||
|
content: "\e630";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-email:before {
|
.icon-email:before {
|
||||||
content: "\e670";
|
content: "\e670";
|
||||||
}
|
}
|
||||||
@ -77,7 +89,7 @@
|
|||||||
content: "\e608";
|
content: "\e608";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-mp:before {
|
.icon-mp3:before {
|
||||||
content: "\e6c4";
|
content: "\e6c4";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,10 +1,31 @@
|
|||||||
{
|
{
|
||||||
"id": "4125778",
|
"id": "4125778",
|
||||||
"name": "chatgpt",
|
"name": "geekai",
|
||||||
"font_family": "iconfont",
|
"font_family": "iconfont",
|
||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "8094809",
|
||||||
|
"name": "merge-cells",
|
||||||
|
"font_class": "merge",
|
||||||
|
"unicode": "e901",
|
||||||
|
"unicode_decimal": 59649
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "10278208",
|
||||||
|
"name": "上传",
|
||||||
|
"font_class": "upload",
|
||||||
|
"unicode": "e611",
|
||||||
|
"unicode_decimal": 58897
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "23538484",
|
||||||
|
"name": "拼接",
|
||||||
|
"font_class": "concat",
|
||||||
|
"unicode": "e630",
|
||||||
|
"unicode_decimal": 58928
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "15838472",
|
"icon_id": "15838472",
|
||||||
"name": "email",
|
"name": "email",
|
||||||
@ -120,7 +141,7 @@
|
|||||||
{
|
{
|
||||||
"icon_id": "4318807",
|
"icon_id": "4318807",
|
||||||
"name": "mp3",
|
"name": "mp3",
|
||||||
"font_class": "mp",
|
"font_class": "mp3",
|
||||||
"unicode": "e6c4",
|
"unicode": "e6c4",
|
||||||
"unicode_decimal": 59076
|
"unicode_decimal": 59076
|
||||||
},
|
},
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -33,11 +33,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref, watch} from "vue";
|
import {onMounted, ref} from "vue";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet} from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {dateFormat} from "@/utils/libs";
|
import {dateFormat} from "@/utils/libs";
|
||||||
import {DocumentCopy} from "@element-plus/icons-vue";
|
|
||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
|
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
@ -60,7 +59,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const fetchData = () => {
|
const fetchData = () => {
|
||||||
httpPost('/api/invite/list', {page: page.value, page_size: pageSize.value}).then((res) => {
|
httpGet('/api/invite/list', {page: page.value, page_size: pageSize.value}).then((res) => {
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
items.value = res.data.items
|
items.value = res.data.items
|
||||||
total.value = res.data.total
|
total.value = res.data.total
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
<div class="params">
|
<div class="params">
|
||||||
<div class="item-group">
|
<div class="item-group">
|
||||||
<span class="label">循环</span>
|
<span class="label">循环参考图</span>
|
||||||
<el-switch v-model="loop" size="small" style="--el-switch-on-color:#BF78BF;" />
|
<el-switch v-model="loop" size="small" style="--el-switch-on-color:#BF78BF;" />
|
||||||
</div>
|
</div>
|
||||||
<div class="item-group">
|
<div class="item-group">
|
||||||
|
@ -211,6 +211,11 @@ const licenseConfig = ref({})
|
|||||||
const enableVerify = ref(false)
|
const enableVerify = ref(false)
|
||||||
const captchaRef = ref(null)
|
const captchaRef = ref(null)
|
||||||
|
|
||||||
|
// 记录邀请码点击次数
|
||||||
|
if (data.value.invite_code) {
|
||||||
|
httpGet("/api/invite/hits",{code: data.value.invite_code})
|
||||||
|
}
|
||||||
|
|
||||||
getSystemInfo().then(res => {
|
getSystemInfo().then(res => {
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
title.value = res.data.title
|
title.value = res.data.title
|
||||||
|
@ -5,6 +5,20 @@
|
|||||||
<el-tooltip effect="light" content="定义模式" placement="top">
|
<el-tooltip effect="light" content="定义模式" placement="top">
|
||||||
<black-switch v-model:value="custom" size="large" />
|
<black-switch v-model:value="custom" size="large" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<el-tooltip effect="light" content="请上传6-60秒的原始音频,检测到人声的音频将仅设为私人音频。" placement="bottom-end">
|
||||||
|
<el-upload
|
||||||
|
class="avatar-uploader"
|
||||||
|
:auto-upload="true"
|
||||||
|
:show-file-list="false"
|
||||||
|
:http-request="uploadAudio"
|
||||||
|
accept=".wav,.mp3"
|
||||||
|
>
|
||||||
|
<el-button class="upload-music" color="#363030" round>
|
||||||
|
<i class="iconfont icon-upload"></i>
|
||||||
|
<span>上传音乐</span>
|
||||||
|
</el-button>
|
||||||
|
</el-upload>
|
||||||
|
</el-tooltip>
|
||||||
<black-select v-model:value="data.model" :options="models" placeholder="请选择模型" style="width: 100px" />
|
<black-select v-model:value="data.model" :options="models" placeholder="请选择模型" style="width: 100px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -28,10 +42,10 @@
|
|||||||
</el-popover>
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
<div class="item"
|
<div class="item"
|
||||||
v-loading="generating"
|
v-loading="isGenerating"
|
||||||
element-loading-text="正在生成歌词..."
|
element-loading-text="正在生成歌词..."
|
||||||
element-loading-background="rgba(122, 122, 122, 0.8)">
|
element-loading-background="rgba(122, 122, 122, 0.8)">
|
||||||
<black-input v-model:value="data.lyrics" type="textarea" :rows="10" placeholder="请在这里输入你自己写的歌词..."/>
|
<black-input v-model:value="data.lyrics" type="textarea" :rows="10" :placeholder="promptPlaceholder"/>
|
||||||
<button class="btn btn-lyric" @click="createLyric">生成歌词</button>
|
<button class="btn btn-lyric" @click="createLyric">生成歌词</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -137,7 +151,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="right-box" v-loading="loading" element-loading-background="rgba(100,100,100,0.3)">
|
<div class="right-box" v-loading="loading" element-loading-background="rgba(100,100,100,0.3)">
|
||||||
<div class="list-box" v-if="!noData">
|
<div class="list-box" v-if="!noData">
|
||||||
<div v-for="item in list">
|
<div v-for="item in list" :key="item.id">
|
||||||
<div class="item" v-if="item.progress === 100">
|
<div class="item" v-if="item.progress === 100">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -151,13 +165,18 @@
|
|||||||
<div class="center">
|
<div class="center">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<a :href="'/song/'+item.song_id" target="_blank">{{item.title}}</a>
|
<a :href="'/song/'+item.song_id" target="_blank">{{item.title}}</a>
|
||||||
<span class="model">{{item.major_model_version}}</span>
|
<span class="model" v-if="item.major_model_version">{{item.major_model_version}}</span>
|
||||||
|
<span class="model" v-if="item.type === 4">用户上传</span>
|
||||||
|
<span class="model" v-if="item.type === 3">
|
||||||
|
<i class="iconfont icon-mp3"></i>
|
||||||
|
完整歌曲
|
||||||
|
</span>
|
||||||
<span class="model" v-if="item.ref_song">
|
<span class="model" v-if="item.ref_song">
|
||||||
<i class="iconfont icon-link"></i>
|
<i class="iconfont icon-link"></i>
|
||||||
{{item.ref_song.title}}
|
{{item.ref_song.title}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tags">{{item.tags}}</div>
|
<div class="tags" v-if="item.tags">{{item.tags}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="tools">
|
<div class="tools">
|
||||||
@ -178,6 +197,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip effect="light" content="获取完整歌曲" placement="top" v-if="item.ref_song">
|
||||||
|
<button class="btn btn-icon" @click="merge(item)">
|
||||||
|
<i class="iconfont icon-concat"></i>
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
<el-tooltip effect="light" content="复制歌曲链接" placement="top">
|
<el-tooltip effect="light" content="复制歌曲链接" placement="top">
|
||||||
<button class="btn btn-icon copy-link" :data-clipboard-text="getShareURL(item)" >
|
<button class="btn btn-icon copy-link" :data-clipboard-text="getShareURL(item)" >
|
||||||
<i class="iconfont icon-share1"></i>
|
<i class="iconfont icon-share1"></i>
|
||||||
@ -276,13 +301,13 @@ import MusicPlayer from "@/components/MusicPlayer.vue";
|
|||||||
import {compact} from "lodash";
|
import {compact} from "lodash";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
|
import {showMessageError, showMessageOK} from "@/utils/dialog";
|
||||||
import Generating from "@/components/ui/Generating.vue";
|
|
||||||
import {checkSession} from "@/store/cache";
|
import {checkSession} from "@/store/cache";
|
||||||
import {ElMessage, ElMessageBox} from "element-plus";
|
import {ElMessage, ElMessageBox} from "element-plus";
|
||||||
import {formatTime} from "@/utils/libs";
|
import {formatTime} from "@/utils/libs";
|
||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
import BlackDialog from "@/components/ui/BlackDialog.vue";
|
import BlackDialog from "@/components/ui/BlackDialog.vue";
|
||||||
import Compressor from "compressorjs";
|
import Compressor from "compressorjs";
|
||||||
|
import Generating from "@/components/ui/Generating.vue";
|
||||||
|
|
||||||
const winHeight = ref(window.innerHeight - 50)
|
const winHeight = ref(window.innerHeight - 50)
|
||||||
const custom = ref(false)
|
const custom = ref(false)
|
||||||
@ -329,6 +354,7 @@ const btnText = ref("开始创作")
|
|||||||
const refSong = ref(null)
|
const refSong = ref(null)
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false)
|
||||||
const editData = ref({title:"",cover:"",id:0})
|
const editData = ref({title:"",cover:"",id:0})
|
||||||
|
const promptPlaceholder = ref('请在这里输入你自己写的歌词...')
|
||||||
|
|
||||||
const socket = ref(null)
|
const socket = ref(null)
|
||||||
const userId = ref(0)
|
const userId = ref(0)
|
||||||
@ -422,7 +448,11 @@ const create = () => {
|
|||||||
data.value.ref_task_id = refSong.value ? refSong.value.task_id : ""
|
data.value.ref_task_id = refSong.value ? refSong.value.task_id : ""
|
||||||
data.value.ref_song_id = refSong.value ? refSong.value.song_id : ""
|
data.value.ref_song_id = refSong.value ? refSong.value.song_id : ""
|
||||||
data.value.extend_secs = refSong.value ? refSong.value.extend_secs : 0
|
data.value.extend_secs = refSong.value ? refSong.value.extend_secs : 0
|
||||||
if (custom.value) {
|
if (refSong.value) {
|
||||||
|
if (data.value.extend_secs > refSong.value.duration) {
|
||||||
|
return showMessageError("续写开始时间不能超过原歌曲长度")
|
||||||
|
}
|
||||||
|
} else if (custom.value) {
|
||||||
if (data.value.lyrics === "") {
|
if (data.value.lyrics === "") {
|
||||||
return showMessageError("请输入歌词")
|
return showMessageError("请输入歌词")
|
||||||
}
|
}
|
||||||
@ -434,9 +464,6 @@ const create = () => {
|
|||||||
return showMessageError("请输入歌曲描述")
|
return showMessageError("请输入歌曲描述")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (refSong.value && data.value.extend_secs > refSong.value.duration) {
|
|
||||||
return showMessageError("续写开始时间不能超过原歌曲长度")
|
|
||||||
}
|
|
||||||
|
|
||||||
httpPost("/api/suno/create", data.value).then(() => {
|
httpPost("/api/suno/create", data.value).then(() => {
|
||||||
fetchData(1)
|
fetchData(1)
|
||||||
@ -446,6 +473,35 @@ const create = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拼接歌曲
|
||||||
|
const merge = (item) => {
|
||||||
|
httpPost("/api/suno/create", {song_id: item.song_id, type:3}).then(() => {
|
||||||
|
fetchData(1)
|
||||||
|
showMessageOK("创建任务成功")
|
||||||
|
}).catch(e => {
|
||||||
|
showMessageError("合并歌曲失败:"+e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadAudio = (file) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file.file, file.name);
|
||||||
|
// 执行上传操作
|
||||||
|
httpPost('/api/upload', formData).then((res) => {
|
||||||
|
httpPost("/api/suno/create", {audio_url: res.data.url, title:res.data.name, type:4}).then(() => {
|
||||||
|
fetchData(1)
|
||||||
|
showMessageOK("歌曲上传成功")
|
||||||
|
}).catch(e => {
|
||||||
|
showMessageError("歌曲上传失败:"+e.message)
|
||||||
|
})
|
||||||
|
removeRefSong()
|
||||||
|
ElMessage.success({message: "上传成功", duration: 500})
|
||||||
|
}).catch((e) => {
|
||||||
|
ElMessage.error('文件传失败:' + e.message)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// 续写歌曲
|
// 续写歌曲
|
||||||
const extend = (item) => {
|
const extend = (item) => {
|
||||||
refSong.value = item
|
refSong.value = item
|
||||||
@ -453,6 +509,7 @@ const extend = (item) => {
|
|||||||
data.value.title = item.title
|
data.value.title = item.title
|
||||||
custom.value = true
|
custom.value = true
|
||||||
btnText.value = "续写歌曲"
|
btnText.value = "续写歌曲"
|
||||||
|
promptPlaceholder.value = "输入额外的歌词,根据您之前的歌词来扩展歌曲..."
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更细歌曲
|
// 更细歌曲
|
||||||
@ -485,6 +542,7 @@ watch(() => custom.value, (newValue) => {
|
|||||||
const removeRefSong = () => {
|
const removeRefSong = () => {
|
||||||
refSong.value = null
|
refSong.value = null
|
||||||
btnText.value = "开始创作"
|
btnText.value = "开始创作"
|
||||||
|
promptPlaceholder.value = "请在这里输入你自己写的歌词..."
|
||||||
}
|
}
|
||||||
|
|
||||||
const play = (item) => {
|
const play = (item) => {
|
||||||
@ -553,21 +611,21 @@ const uploadCover = (file) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const generating = ref(false)
|
const isGenerating = ref(false)
|
||||||
const createLyric = () => {
|
const createLyric = () => {
|
||||||
if (data.value.lyrics === "") {
|
if (data.value.lyrics === "") {
|
||||||
return showMessageError("请输入歌词描述")
|
return showMessageError("请输入歌词描述")
|
||||||
}
|
}
|
||||||
generating.value = true
|
isGenerating.value = true
|
||||||
httpPost("/api/suno/lyric", {prompt: data.value.lyrics}).then(res => {
|
httpPost("/api/suno/lyric", {prompt: data.value.lyrics}).then(res => {
|
||||||
const lines = res.data.split('\n');
|
const lines = res.data.split('\n');
|
||||||
data.value.title = lines.shift().replace(/\*/g,"")
|
data.value.title = lines.shift().replace(/\*/g,"")
|
||||||
lines.shift()
|
lines.shift()
|
||||||
data.value.lyrics = lines.join('\n');
|
data.value.lyrics = lines.join('\n');
|
||||||
generating.value = false
|
isGenerating.value = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
showMessageError("歌词生成失败:"+e.message)
|
showMessageError("歌词生成失败:"+e.message)
|
||||||
generating.value = false
|
isGenerating.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user