@@ -37,6 +37,7 @@ const nowSpeakingAvatarBorderOpacityMax = 100
|
||||
const openAiApiKey = useLocalStorage('openai-api-key', '')
|
||||
const openAiApiBaseURL = useLocalStorage('openai-api-base-url', '')
|
||||
const openAIModel = useLocalStorage<{ id: string, name?: string }>('openai-model', { id: 'openai/gpt-3.5-turbo', name: 'OpenAI GPT3.5 Turbo' })
|
||||
const elevenLabsApiKey = useLocalStorage('elevenlabs-api-key', '')
|
||||
|
||||
const { setupOpenAI, streamSpeech, stream, models } = useLLM()
|
||||
const { audioContext, calculateVolume } = useAudioContext()
|
||||
@@ -102,7 +103,7 @@ const ttsQueue = useQueue<string>({
|
||||
handlers: [
|
||||
async (ctx) => {
|
||||
const now = Date.now()
|
||||
const res = await streamSpeech(ctx.data)
|
||||
const res = await streamSpeech(ctx.data, elevenLabsApiKey.value)
|
||||
const elapsed = Date.now() - now
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -253,6 +254,13 @@ onUnmounted(() => {
|
||||
<template>
|
||||
<div max-h="[100vh]" h-full p="2" flex="~ col">
|
||||
<div space-x="2" flex="~ row" w-full>
|
||||
<div flex="~ row" w-full>
|
||||
<input
|
||||
v-model="openAiApiBaseURL"
|
||||
placeholder="Input your API base URL"
|
||||
p="2" bg="zinc-100 dark:zinc-700" w-full rounded-lg outline-none
|
||||
>
|
||||
</div>
|
||||
<div flex="~ row" w-full>
|
||||
<input
|
||||
v-model="openAiApiKey"
|
||||
@@ -262,8 +270,8 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div flex="~ row" w-full>
|
||||
<input
|
||||
v-model="openAiApiBaseURL"
|
||||
placeholder="Input your API base URL"
|
||||
v-model="elevenLabsApiKey"
|
||||
placeholder="Input your ElevenLabs API key"
|
||||
p="2" bg="zinc-100 dark:zinc-700" w-full rounded-lg outline-none
|
||||
>
|
||||
</div>
|
||||
|
||||
+2
-1
@@ -35,13 +35,14 @@ export const useLLM = defineStore('llm', () => {
|
||||
return await openAI.value.models.list()
|
||||
}
|
||||
|
||||
async function streamSpeech(text: string) {
|
||||
async function streamSpeech(text: string, apiKey: string) {
|
||||
if (!text || !text.trim())
|
||||
throw new Error('Text is required')
|
||||
|
||||
return await ofetch('/api/v1/llm/voice/text-to-speech', {
|
||||
body: {
|
||||
text,
|
||||
apiKey,
|
||||
},
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
|
||||
@@ -62,5 +62,8 @@
|
||||
"vue-tsc": "^2.1.10",
|
||||
"yauzl": "^3.2.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"elevenlabs": "^0.18.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+93
@@ -7,6 +7,10 @@ settings:
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
elevenlabs:
|
||||
specifier: ^0.18.1
|
||||
version: 0.18.1
|
||||
devDependencies:
|
||||
'@ai-sdk/openai':
|
||||
specifier: ^1.0.5
|
||||
@@ -2640,6 +2644,9 @@ packages:
|
||||
comma-separated-tokens@2.0.3:
|
||||
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||
|
||||
command-exists@1.2.9:
|
||||
resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==}
|
||||
|
||||
commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
|
||||
@@ -2974,6 +2981,9 @@ packages:
|
||||
electron-to-chromium@1.5.67:
|
||||
resolution: {integrity: sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==}
|
||||
|
||||
elevenlabs@0.18.1:
|
||||
resolution: {integrity: sha512-JHdgqPyRJFwpo6jfJy8UaVWmt5yQ/SMVsUWAHOng0QpeeI0UxxYMrIR1TcpIXCaFkk4+MOgk8neJu2K1JIFaoA==}
|
||||
|
||||
email-addresses@3.1.0:
|
||||
resolution: {integrity: sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==}
|
||||
|
||||
@@ -3302,6 +3312,10 @@ packages:
|
||||
resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
execa@5.1.1:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
execa@7.2.0:
|
||||
resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==}
|
||||
engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
|
||||
@@ -3411,6 +3425,10 @@ packages:
|
||||
form-data-encoder@1.7.2:
|
||||
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
|
||||
|
||||
form-data-encoder@4.0.2:
|
||||
resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
form-data@4.0.1:
|
||||
resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -3419,6 +3437,10 @@ packages:
|
||||
resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
|
||||
engines: {node: '>= 12.20'}
|
||||
|
||||
formdata-node@6.0.3:
|
||||
resolution: {integrity: sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
@@ -3660,6 +3682,10 @@ packages:
|
||||
httpxy@0.1.5:
|
||||
resolution: {integrity: sha512-hqLDO+rfststuyEUTWObQK6zHEEmZ/kaIP2/zclGGZn6X8h/ESTWg+WKecQ/e5k4nPswjzZD+q2VqZIbr15CoQ==}
|
||||
|
||||
human-signals@2.1.0:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
|
||||
human-signals@4.3.1:
|
||||
resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==}
|
||||
engines: {node: '>=14.18.0'}
|
||||
@@ -4299,6 +4325,10 @@ packages:
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
|
||||
mimic-fn@2.1.0:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
mimic-fn@4.0.0:
|
||||
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -4505,6 +4535,10 @@ packages:
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
onetime@5.1.2:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
onetime@6.0.0:
|
||||
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -4929,6 +4963,10 @@ packages:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
qs@6.11.2:
|
||||
resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
qs@6.13.1:
|
||||
resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==}
|
||||
engines: {node: '>=0.6'}
|
||||
@@ -5330,6 +5368,10 @@ packages:
|
||||
resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
strip-final-newline@2.0.0:
|
||||
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
strip-final-newline@3.0.0:
|
||||
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -5717,6 +5759,9 @@ packages:
|
||||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
url-join@4.0.1:
|
||||
resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==}
|
||||
|
||||
url@0.11.4:
|
||||
resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -9097,6 +9142,8 @@ snapshots:
|
||||
|
||||
comma-separated-tokens@2.0.3: {}
|
||||
|
||||
command-exists@1.2.9: {}
|
||||
|
||||
commander@2.20.3: {}
|
||||
|
||||
commander@7.2.0: {}
|
||||
@@ -9386,6 +9433,20 @@ snapshots:
|
||||
|
||||
electron-to-chromium@1.5.67: {}
|
||||
|
||||
elevenlabs@0.18.1:
|
||||
dependencies:
|
||||
command-exists: 1.2.9
|
||||
execa: 5.1.1
|
||||
form-data: 4.0.1
|
||||
form-data-encoder: 4.0.2
|
||||
formdata-node: 6.0.3
|
||||
node-fetch: 2.7.0
|
||||
qs: 6.11.2
|
||||
readable-stream: 4.5.2
|
||||
url-join: 4.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
email-addresses@3.1.0: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
@@ -9906,6 +9967,18 @@ snapshots:
|
||||
|
||||
eventsource-parser@3.0.0: {}
|
||||
|
||||
execa@5.1.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
get-stream: 6.0.1
|
||||
human-signals: 2.1.0
|
||||
is-stream: 2.0.1
|
||||
merge-stream: 2.0.0
|
||||
npm-run-path: 4.0.1
|
||||
onetime: 5.1.2
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
execa@7.2.0:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
@@ -10033,6 +10106,8 @@ snapshots:
|
||||
|
||||
form-data-encoder@1.7.2: {}
|
||||
|
||||
form-data-encoder@4.0.2: {}
|
||||
|
||||
form-data@4.0.1:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
@@ -10044,6 +10119,8 @@ snapshots:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 4.0.0-beta.3
|
||||
|
||||
formdata-node@6.0.3: {}
|
||||
|
||||
fraction.js@4.3.7: {}
|
||||
|
||||
fresh@0.5.2: {}
|
||||
@@ -10332,6 +10409,8 @@ snapshots:
|
||||
|
||||
httpxy@0.1.5: {}
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
human-signals@4.3.1: {}
|
||||
|
||||
human-signals@5.0.0: {}
|
||||
@@ -11110,6 +11189,8 @@ snapshots:
|
||||
|
||||
mime@4.0.4: {}
|
||||
|
||||
mimic-fn@2.1.0: {}
|
||||
|
||||
mimic-fn@4.0.0: {}
|
||||
|
||||
min-indent@1.0.1: {}
|
||||
@@ -11465,6 +11546,10 @@ snapshots:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
onetime@5.1.2:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
|
||||
onetime@6.0.0:
|
||||
dependencies:
|
||||
mimic-fn: 4.0.0
|
||||
@@ -11852,6 +11937,10 @@ snapshots:
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
qs@6.11.2:
|
||||
dependencies:
|
||||
side-channel: 1.0.6
|
||||
|
||||
qs@6.13.1:
|
||||
dependencies:
|
||||
side-channel: 1.0.6
|
||||
@@ -12341,6 +12430,8 @@ snapshots:
|
||||
|
||||
strip-comments@2.0.1: {}
|
||||
|
||||
strip-final-newline@2.0.0: {}
|
||||
|
||||
strip-final-newline@3.0.0: {}
|
||||
|
||||
strip-indent@3.0.0:
|
||||
@@ -12776,6 +12867,8 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
url-join@4.0.1: {}
|
||||
|
||||
url@0.11.4:
|
||||
dependencies:
|
||||
punycode: 1.4.1
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
const startAt = Date.now()
|
||||
let count = 0
|
||||
|
||||
export default defineEventHandler(() => ({
|
||||
pageview: count++,
|
||||
startAt,
|
||||
}))
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ElevenLabsClient } from 'elevenlabs'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody<{ text: string, apiKey: string }>(event)
|
||||
const client = new ElevenLabsClient({
|
||||
apiKey: body.apiKey,
|
||||
})
|
||||
|
||||
const res = await client.generate({
|
||||
// voice: 'ShanShan',
|
||||
// Quite good for English
|
||||
voice: 'Myriam',
|
||||
// Beatrice is not 'childish' like the others
|
||||
// voice: 'Beatrice',
|
||||
text: body.text,
|
||||
stream: true,
|
||||
model_id: 'eleven_multilingual_v2',
|
||||
voice_settings: {
|
||||
stability: 0.4,
|
||||
similarity_boost: 0.5,
|
||||
},
|
||||
})
|
||||
|
||||
// Set headers for streaming
|
||||
event.node.res.setHeader('Content-Type', 'audio/mpeg')
|
||||
event.node.res.setHeader('Transfer-Encoding', 'chunked')
|
||||
|
||||
// res is NodeJS.ReadableStream
|
||||
return sendStream(event, res)
|
||||
})
|
||||
Reference in New Issue
Block a user