mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-09-16 18:27:13 +00:00
d4fd38ab7e
- 同步 Plus v4.3.1 功能源并移除商业 License 闭环 - 更新开源镜像命名、Docker 部署版本和 geekai 数据库配置 - 补充前端 ESLint 检查配置并修复存量解析与模板问题 - 保留 JWT、管理员权限、API Key 和 OAuth 等正常鉴权机制
367 lines
9.9 KiB
JavaScript
367 lines
9.9 KiB
JavaScript
import { getSystemInfo } from '@/store/cache'
|
|
import { closeLoading, showLoading, showMessageError, showMessageOK } from '@/utils/dialog'
|
|
import { httpDownload, httpGet, httpPost } from '@/utils/http'
|
|
import { replaceImg } from '@/utils/libs'
|
|
import { defineStore } from 'pinia'
|
|
import { showConfirmDialog } from 'vant'
|
|
import { reactive, ref } from 'vue'
|
|
|
|
export const useSunoStore = defineStore('suno', () => {
|
|
// 状态
|
|
const custom = ref(false)
|
|
const data = reactive({
|
|
model: 'chirp-auk',
|
|
tags: '',
|
|
lyrics: '',
|
|
prompt: '',
|
|
title: '',
|
|
instrumental: false,
|
|
ref_task_id: '',
|
|
extend_secs: 0,
|
|
ref_song_id: '',
|
|
type: 1,
|
|
})
|
|
const loading = ref(false)
|
|
const list = ref([])
|
|
const listLoading = ref(false)
|
|
const listFinished = ref(false)
|
|
const btnText = ref('开始创作')
|
|
const refSong = ref(null)
|
|
const showModelPicker = ref(false)
|
|
const showPlayer = ref(false)
|
|
const showDeleteModal = ref(false)
|
|
const currentAudio = ref('')
|
|
const uploadFiles = ref([])
|
|
const uploadRef = ref(null)
|
|
const isGenerating = ref(false)
|
|
const deleting = ref(false)
|
|
const models = ref([
|
|
{ label: 'v3.0', value: 'chirp-v3-0' },
|
|
{ label: 'v3.5', value: 'chirp-v3-5' },
|
|
{ label: 'v4.0', value: 'chirp-v4' },
|
|
{ label: 'v4.5', value: 'chirp-auk' },
|
|
])
|
|
const tags = ref([
|
|
{ label: '女声', value: 'female vocals' },
|
|
{ label: '男声', value: 'male vocals' },
|
|
{ label: '流行', value: 'pop' },
|
|
{ label: '摇滚', value: 'rock' },
|
|
{ label: '电音', value: 'electronic' },
|
|
{ label: '钢琴', value: 'piano' },
|
|
{ label: '吉他', value: 'guitar' },
|
|
{ label: '嘻哈', value: 'hip hop' },
|
|
])
|
|
const page = ref(1)
|
|
const pageSize = ref(10)
|
|
const total = ref(0)
|
|
const taskPulling = ref(true)
|
|
const tastPullHandler = ref(null)
|
|
const sunoPowerCost = ref(0)
|
|
|
|
onMounted(() => {
|
|
getSystemInfo().then((res) => {
|
|
sunoPowerCost.value = res.data.suno_power
|
|
})
|
|
})
|
|
|
|
// 方法
|
|
const onModelSelect = (selectedModel) => {
|
|
data.model = selectedModel.value
|
|
}
|
|
const selectTag = (tag) => {
|
|
if (data.tags.length + tag.value.length >= 119) {
|
|
showMessageError('标签长度超出限制')
|
|
return
|
|
}
|
|
const currentTags = data.tags.split(',').filter((t) => t.trim())
|
|
if (!currentTags.includes(tag.value)) {
|
|
currentTags.push(tag.value)
|
|
data.tags = currentTags.join(',')
|
|
}
|
|
}
|
|
const createLyric = () => {
|
|
if (data.lyrics === '') {
|
|
showMessageError('请输入歌词描述')
|
|
return
|
|
}
|
|
isGenerating.value = true
|
|
httpPost('/api/prompt/lyric', { prompt: data.lyrics })
|
|
.then((res) => {
|
|
const lines = res.data.split('\n')
|
|
data.title = lines.shift().replace(/\*/g, '')
|
|
lines.shift()
|
|
data.lyrics = lines.join('\n')
|
|
showMessageOK('歌词生成成功')
|
|
})
|
|
.catch((e) => {
|
|
showMessageError('歌词生成失败:' + e.message)
|
|
})
|
|
.finally(() => {
|
|
isGenerating.value = false
|
|
})
|
|
}
|
|
const handleFileChange = (file) => {
|
|
uploadFiles.value = [file]
|
|
if (file.status === 'ready') {
|
|
uploadAudio(file)
|
|
}
|
|
}
|
|
const beforeUpload = (file) => {
|
|
const isLt10M = file.size / 1024 / 1024 < 10
|
|
if (!isLt10M) {
|
|
showMessageError('文件大小不能超过 10MB!')
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
const uploadAudio = (file) => {
|
|
const formData = new FormData()
|
|
formData.append('file', file.raw, file.name)
|
|
showLoading('正在上传文件...')
|
|
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('歌曲上传成功')
|
|
removeRefSong()
|
|
uploadFiles.value = []
|
|
if (uploadRef.value) {
|
|
uploadRef.value.clearFiles()
|
|
}
|
|
})
|
|
.catch((e) => {
|
|
showMessageError('歌曲上传失败:' + e.message)
|
|
})
|
|
.finally(() => {
|
|
closeLoading()
|
|
})
|
|
})
|
|
.catch((e) => {
|
|
showMessageError('文件上传失败:' + e.message)
|
|
})
|
|
.finally(() => {
|
|
closeLoading()
|
|
})
|
|
}
|
|
const create = () => {
|
|
data.type = custom.value ? 2 : 1
|
|
data.ref_task_id = refSong.value ? refSong.value.task_id : ''
|
|
data.ref_song_id = refSong.value ? refSong.value.song_id : ''
|
|
data.extend_secs = refSong.value ? refSong.value.extend_secs : 0
|
|
if (refSong.value) {
|
|
if (data.extend_secs > refSong.value.duration) {
|
|
showMessageError('续写开始时间不能超过原歌曲长度')
|
|
return
|
|
}
|
|
} else if (custom.value) {
|
|
if (data.lyrics === '') {
|
|
showMessageError('请输入歌词')
|
|
return
|
|
}
|
|
if (data.title === '') {
|
|
showMessageError('请输入歌曲标题')
|
|
return
|
|
}
|
|
} else {
|
|
if (data.prompt === '') {
|
|
showMessageError('请输入歌曲描述')
|
|
return
|
|
}
|
|
}
|
|
loading.value = true
|
|
httpPost('/api/suno/create', data)
|
|
.then(() => {
|
|
fetchData(1)
|
|
taskPulling.value = true
|
|
showMessageOK('创建任务成功')
|
|
})
|
|
.catch((e) => {
|
|
showMessageError('创建任务失败:' + e.message)
|
|
})
|
|
.finally(() => {
|
|
loading.value = false
|
|
})
|
|
}
|
|
const fetchData = (_page) => {
|
|
if (_page) {
|
|
page.value = _page
|
|
}
|
|
listLoading.value = true
|
|
httpGet('/api/suno/list', { page: page.value, page_size: pageSize.value })
|
|
.then((res) => {
|
|
total.value = res.data.total
|
|
let needPull = false
|
|
const items = []
|
|
for (let v of res.data.items) {
|
|
if (v.status === 'success') {
|
|
v.major_model_version = v['raw_data']['major_model_version']
|
|
}
|
|
if (v.status === 'pending' || v.status === 'in_progress' || v.status === 'downloading') {
|
|
needPull = true
|
|
}
|
|
items.push(v)
|
|
}
|
|
listLoading.value = false
|
|
taskPulling.value = needPull
|
|
if (page.value === 1) {
|
|
list.value = items
|
|
} else {
|
|
list.value.push(...items)
|
|
}
|
|
if (items.length < pageSize.value) {
|
|
listFinished.value = true
|
|
}
|
|
})
|
|
.catch((e) => {
|
|
listLoading.value = false
|
|
showMessageError('获取作品列表失败:' + e.message)
|
|
})
|
|
}
|
|
const loadMore = () => {
|
|
if (!listFinished.value && !listLoading.value) {
|
|
page.value++
|
|
fetchData()
|
|
}
|
|
}
|
|
const refreshFirstPage = () => {
|
|
const currentPage = page.value
|
|
const currentList = [...list.value]
|
|
httpGet('/api/suno/list', { page: 1, page_size: pageSize.value })
|
|
.then((res) => {
|
|
let needPull = false
|
|
const firstPageItems = []
|
|
for (let v of res.data.items) {
|
|
if (v.status === 'success') {
|
|
v.major_model_version = v['raw_data']['major_model_version']
|
|
}
|
|
if (v.status === 'pending' || v.status === 'in_progress' || v.status === 'downloading') {
|
|
needPull = true
|
|
}
|
|
firstPageItems.push(v)
|
|
}
|
|
taskPulling.value = needPull
|
|
if (currentPage === 1) {
|
|
list.value = firstPageItems
|
|
} else {
|
|
const otherPagesData = currentList.slice(pageSize.value)
|
|
list.value = [...firstPageItems, ...otherPagesData]
|
|
}
|
|
})
|
|
.catch((e) => {
|
|
console.error('刷新第一页数据失败:', e)
|
|
})
|
|
}
|
|
const play = (item) => {
|
|
currentAudio.value = item.audio_url
|
|
showPlayer.value = true
|
|
}
|
|
const download = (item) => {
|
|
const url = replaceImg(item.audio_url)
|
|
const downloadURL = `/api/download?url=${url}`
|
|
const urlObj = new URL(url)
|
|
const fileName = urlObj.pathname.split('/').pop()
|
|
item.downloading = true
|
|
httpDownload(downloadURL)
|
|
.then((response) => {
|
|
const blob = new Blob([response.data])
|
|
const link = document.createElement('a')
|
|
link.href = URL.createObjectURL(blob)
|
|
link.download = fileName
|
|
document.body.appendChild(link)
|
|
link.click()
|
|
document.body.removeChild(link)
|
|
URL.revokeObjectURL(link.href)
|
|
item.downloading = false
|
|
})
|
|
.catch(() => {
|
|
showMessageError('下载失败')
|
|
item.downloading = false
|
|
})
|
|
.finally(() => {
|
|
item.downloading = false
|
|
})
|
|
}
|
|
|
|
const removeJob = (item) => {
|
|
showConfirmDialog({
|
|
title: '确认删除',
|
|
message: '此操作将会删除任务相关文件,继续操作吗?',
|
|
confirmButtonText: '确认删除',
|
|
cancelButtonText: '取消',
|
|
}).then(() => {
|
|
httpGet('/api/suno/remove', { id: item.id })
|
|
.then(() => {
|
|
showMessageOK('任务删除成功')
|
|
fetchData(1)
|
|
})
|
|
.catch(() => {
|
|
showMessageError('任务删除失败')
|
|
})
|
|
})
|
|
}
|
|
|
|
const extend = (item) => {
|
|
refSong.value = item
|
|
refSong.value.extend_secs = item.duration
|
|
data.title = item.title
|
|
custom.value = true
|
|
btnText.value = '续写歌曲'
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
}
|
|
const removeRefSong = () => {
|
|
refSong.value = null
|
|
btnText.value = '开始创作'
|
|
}
|
|
|
|
// 副作用(定时轮询、滚动监听)建议在页面层处理,store 只暴露方法
|
|
|
|
return {
|
|
// 状态
|
|
custom,
|
|
data,
|
|
loading,
|
|
list,
|
|
listLoading,
|
|
listFinished,
|
|
btnText,
|
|
refSong,
|
|
showModelPicker,
|
|
showPlayer,
|
|
showDeleteModal,
|
|
currentAudio,
|
|
uploadFiles,
|
|
uploadRef,
|
|
isGenerating,
|
|
deleting,
|
|
models,
|
|
tags,
|
|
page,
|
|
pageSize,
|
|
total,
|
|
taskPulling,
|
|
tastPullHandler,
|
|
sunoPowerCost,
|
|
// 方法
|
|
onModelSelect,
|
|
selectTag,
|
|
createLyric,
|
|
handleFileChange,
|
|
beforeUpload,
|
|
uploadAudio,
|
|
create,
|
|
fetchData,
|
|
loadMore,
|
|
refreshFirstPage,
|
|
play,
|
|
download,
|
|
removeJob,
|
|
extend,
|
|
removeRefSong,
|
|
}
|
|
})
|