mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-11-09 18:53:43 +08:00
add function to generate lyrics
This commit is contained in:
@@ -62,10 +62,10 @@
|
||||
|
||||
.prompt {
|
||||
width 100%
|
||||
height 100%
|
||||
height 500px
|
||||
background-color transparent
|
||||
white-space pre-wrap
|
||||
overflow-y hidden
|
||||
overflow-y auto
|
||||
resize none
|
||||
position relative
|
||||
outline 2px solid transparent
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
}
|
||||
.item {
|
||||
margin-bottom: 20px
|
||||
position relative
|
||||
|
||||
.create-btn {
|
||||
margin 20px 0
|
||||
@@ -118,6 +119,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-lyric {
|
||||
position absolute
|
||||
left 10px
|
||||
bottom 10px
|
||||
font-size 12px
|
||||
padding 2px 5px
|
||||
}
|
||||
}
|
||||
|
||||
.tag-select {
|
||||
@@ -268,19 +277,6 @@
|
||||
flex-flow row
|
||||
height 90px
|
||||
|
||||
.btn {
|
||||
margin-right 10px
|
||||
background-color #363030
|
||||
border none
|
||||
border-radius 5px
|
||||
padding 5px 10px
|
||||
cursor pointer
|
||||
|
||||
&:hover {
|
||||
background-color #5F5958
|
||||
}
|
||||
}
|
||||
|
||||
.btn-publish {
|
||||
padding 2px 10px
|
||||
|
||||
@@ -343,7 +339,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.pagination {
|
||||
padding 10px 20px
|
||||
display flex
|
||||
@@ -358,4 +353,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.btn {
|
||||
margin-right 10px
|
||||
background-color #363030
|
||||
border none
|
||||
border-radius 5px
|
||||
padding 5px 10px
|
||||
cursor pointer
|
||||
|
||||
&:hover {
|
||||
background-color #5F5958
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,13 +339,10 @@ const reGenerate = (prompt) => {
|
||||
|
||||
.chat-line-reply-chat {
|
||||
justify-content: center;
|
||||
width 100%
|
||||
padding-bottom: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
padding 1.5rem;
|
||||
|
||||
.chat-line-inner {
|
||||
display flex;
|
||||
padding 0 25px;
|
||||
width 100%
|
||||
flex-flow row
|
||||
|
||||
@@ -364,7 +361,7 @@ const reGenerate = (prompt) => {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
max-width 60%
|
||||
max-width 70%
|
||||
|
||||
.content-wrapper {
|
||||
display flex
|
||||
|
||||
@@ -43,6 +43,7 @@ import {ref, onMounted, watch} from 'vue';
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
import {Close} from "@element-plus/icons-vue";
|
||||
import {formatTime} from "@/utils/libs";
|
||||
import {httpGet} from "@/utils/http"
|
||||
|
||||
const audio = ref(null);
|
||||
const isPlaying = ref(false);
|
||||
@@ -55,6 +56,7 @@ const title = ref("")
|
||||
const tags = ref("")
|
||||
const cover = ref("")
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
songs: {
|
||||
type: Array,
|
||||
@@ -67,7 +69,7 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
// eslint-disable-next-line no-undef
|
||||
const emits = defineEmits(['close']);
|
||||
const emits = defineEmits(['close','play']);
|
||||
|
||||
watch(() => props.songs, (newVal) => {
|
||||
loadSong(newVal[songIndex.value]);
|
||||
@@ -84,6 +86,7 @@ const loadSong = (song) => {
|
||||
cover.value = song.cover_url
|
||||
audio.value.src = song.audio_url;
|
||||
audio.value.load();
|
||||
isPlaying.value = false
|
||||
audio.value.onloadedmetadata = () => {
|
||||
duration.value = audio.value.duration;
|
||||
};
|
||||
@@ -92,15 +95,23 @@ const loadSong = (song) => {
|
||||
const togglePlay = () => {
|
||||
if (isPlaying.value) {
|
||||
audio.value.pause();
|
||||
isPlaying.value = false
|
||||
} else {
|
||||
audio.value.play();
|
||||
play()
|
||||
}
|
||||
isPlaying.value = !isPlaying.value;
|
||||
};
|
||||
|
||||
const play = () => {
|
||||
if (isPlaying.value) {
|
||||
return
|
||||
}
|
||||
audio.value.play();
|
||||
isPlaying.value = true;
|
||||
isPlaying.value = true
|
||||
if (audio.value.currentTime === 0) {
|
||||
emits("play")
|
||||
// 增加播放数量
|
||||
httpGet("/api/suno/play",{song_id:props.songs[songIndex.value].song_id}).then().catch()
|
||||
}
|
||||
}
|
||||
|
||||
const prevSong = () => {
|
||||
|
||||
@@ -12,18 +12,18 @@
|
||||
|
||||
<div class="navbar">
|
||||
<el-tooltip
|
||||
v-if="!licenseConfig.de_copy"
|
||||
v-if="!license.de_copy"
|
||||
class="box-item"
|
||||
effect="light"
|
||||
content="部署文档"
|
||||
placement="bottom">
|
||||
<a href="https://ai.r9it.com/docs/install/" class="link-button" target="_blank">
|
||||
<a href="https://docs.geekai.me/install/" class="link-button" target="_blank">
|
||||
<i class="iconfont icon-book"></i>
|
||||
</a>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
v-if="!licenseConfig.de_copy"
|
||||
v-if="!license.de_copy"
|
||||
class="box-item"
|
||||
effect="light"
|
||||
content="项目源码"
|
||||
@@ -46,7 +46,7 @@
|
||||
<span class="username">{{ loginUser.nickname }}</span>
|
||||
</el-dropdown-item>
|
||||
|
||||
<div v-if="!licenseConfig.de_copy">
|
||||
<div v-if="!license.de_copy">
|
||||
<el-dropdown-item>
|
||||
<i class="iconfont icon-book"></i>
|
||||
<a :href="docsURL" target="_blank">
|
||||
@@ -156,7 +156,7 @@ const loginUser = ref({})
|
||||
const version = ref(process.env.VUE_APP_VERSION)
|
||||
const routerViewKey = ref(0)
|
||||
const showConfigDialog = ref(false)
|
||||
const licenseConfig = ref({})
|
||||
const license = ref({de_copy: true})
|
||||
const docsURL = ref(process.env.VUE_APP_DOCS_URL)
|
||||
const gitURL = ref(process.env.VUE_APP_GIT_URL)
|
||||
|
||||
@@ -205,8 +205,9 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
httpGet("/api/config/license").then(res => {
|
||||
licenseConfig.value = res.data
|
||||
license.value = res.data
|
||||
}).catch(e => {
|
||||
license.value = {de_copy: false}
|
||||
showMessageError("获取 License 配置:" + e.message)
|
||||
})
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ if (isMobile()) {
|
||||
const title = ref("")
|
||||
const logo = ref("")
|
||||
const slogan = ref("")
|
||||
const license = ref({})
|
||||
const license = ref({de_copy: true})
|
||||
const winHeight = window.innerHeight - 150
|
||||
const isLogin = ref(false)
|
||||
const docsURL = ref(process.env.VUE_APP_DOCS_URL)
|
||||
@@ -158,6 +158,7 @@ onMounted(() => {
|
||||
httpGet("/api/config/license").then(res => {
|
||||
license.value = res.data
|
||||
}).catch(e => {
|
||||
license.value = {de_copy: false}
|
||||
ElMessage.error("获取 License 配置失败:" + e.message)
|
||||
})
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ const data = ref({
|
||||
const enableMobile = ref(false)
|
||||
const enableEmail = ref(false)
|
||||
const enableUser = ref(false)
|
||||
const enableRegister = ref(false)
|
||||
const enableRegister = ref(true)
|
||||
const activeName = ref("mobile")
|
||||
const wxImg = ref("/images/wx.png")
|
||||
const licenseConfig = ref({})
|
||||
|
||||
@@ -33,13 +33,13 @@
|
||||
</div>
|
||||
|
||||
<div class="music-player" v-if="playList.length > 0">
|
||||
<music-player :songs="playList" ref="playerRef"/>
|
||||
<music-player :songs="playList" ref="playerRef" @play="song.play_times += 1"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
|
||||
import {onMounted, onUnmounted, ref} from "vue"
|
||||
import {useRouter} from "vue-router";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
@@ -54,7 +54,7 @@ const song = ref({title:""})
|
||||
const playList = ref([])
|
||||
const playerRef = ref(null)
|
||||
|
||||
httpGet("/api/suno/detail",{id:id}).then(res => {
|
||||
httpGet("/api/suno/detail",{song_id:id}).then(res => {
|
||||
song.value = res.data
|
||||
playList.value = [song.value]
|
||||
document.title = song.value?.title+ " | By "+song.value?.user.nickname+" | Suno音乐"
|
||||
@@ -84,7 +84,7 @@ const play = () => {
|
||||
}
|
||||
|
||||
|
||||
const winHeight = ref(window.innerHeight-60)
|
||||
const winHeight = ref(window.innerHeight-50)
|
||||
const getShareURL = (item) => {
|
||||
return `${location.protocol}//${location.host}/song/${item.id}`
|
||||
}
|
||||
|
||||
@@ -27,8 +27,12 @@
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="item"
|
||||
v-loading="generating"
|
||||
element-loading-text="正在生成歌词..."
|
||||
element-loading-background="rgba(122, 122, 122, 0.8)">
|
||||
<black-input v-model:value="data.lyrics" type="textarea" :rows="10" placeholder="请在这里输入你自己写的歌词..."/>
|
||||
<button class="btn btn-lyric" @click="createLyric">生成歌词</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -146,7 +150,7 @@
|
||||
</div>
|
||||
<div class="center">
|
||||
<div class="title">
|
||||
<a :href="'/song/'+item.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.ref_song">
|
||||
<i class="iconfont icon-link"></i>
|
||||
@@ -328,7 +332,6 @@ const editData = ref({title:"",cover:"",id:0})
|
||||
|
||||
const socket = ref(null)
|
||||
const userId = ref(0)
|
||||
const heartbeatHandle = ref(null)
|
||||
const connect = () => {
|
||||
let host = process.env.VUE_APP_WS_HOST
|
||||
if (host === '') {
|
||||
@@ -339,25 +342,9 @@ const connect = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 心跳函数
|
||||
const sendHeartbeat = () => {
|
||||
clearTimeout(heartbeatHandle.value)
|
||||
new Promise((resolve, reject) => {
|
||||
if (socket.value !== null) {
|
||||
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
|
||||
}
|
||||
resolve("success")
|
||||
}).then(() => {
|
||||
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
|
||||
});
|
||||
}
|
||||
|
||||
const _socket = new WebSocket(host + `/api/suno/client?user_id=${userId.value}`);
|
||||
_socket.addEventListener('open', () => {
|
||||
socket.value = _socket;
|
||||
|
||||
// 发送心跳消息
|
||||
sendHeartbeat()
|
||||
});
|
||||
|
||||
_socket.addEventListener('message', event => {
|
||||
@@ -564,6 +551,24 @@ const uploadCover = (file) => {
|
||||
});
|
||||
}
|
||||
|
||||
const generating = ref(false)
|
||||
const createLyric = () => {
|
||||
if (data.value.lyrics === "") {
|
||||
return showMessageError("请输入歌词描述")
|
||||
}
|
||||
generating.value = true
|
||||
httpPost("/api/suno/lyric", {prompt: data.value.lyrics}).then(res => {
|
||||
const lines = res.data.split('\n');
|
||||
data.value.title = lines.shift().replace(/\*/g,"")
|
||||
lines.shift()
|
||||
data.value.lyrics = lines.join('\n');
|
||||
generating.value = false
|
||||
}).catch(e => {
|
||||
showMessageError("歌词生成失败:"+e.message)
|
||||
generating.value = false
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
<div class="content">
|
||||
<van-form>
|
||||
<div class="avatar">
|
||||
<van-uploader v-model="fileList"
|
||||
reupload max-count="1"
|
||||
:deletable="false"
|
||||
:after-read="afterRead"/>
|
||||
<van-image :src="fileList[0].url" size="80" width="80" fit="cover" round />
|
||||
<!-- <van-uploader v-model="fileList"-->
|
||||
<!-- reupload max-count="1"-->
|
||||
<!-- :deletable="false"-->
|
||||
<!-- :after-read="afterRead"/>-->
|
||||
</div>
|
||||
<van-cell-group inset v-model="form">
|
||||
<van-field
|
||||
@@ -154,7 +155,7 @@
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {showFailToast, showNotify, showSuccessToast, showToast} from "vant";
|
||||
import {showFailToast, showNotify, showSuccessToast} from "vant";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import Compressor from 'compressorjs';
|
||||
import {dateFormat, isWeChatBrowser, showLoginDialog} from "@/utils/libs";
|
||||
|
||||
Reference in New Issue
Block a user