✨ Add ElevenLabs block (#1226)
Closes #853 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced the ElevenLabs block for Typebot, enabling text to speech conversion using ElevenLabs API. - Added live tutorial videos for creating ElevenLabs and Telegram blocks. - Added a `docsUrl` for the OpenAI block to improve accessibility to documentation. - **Documentation** - New guides and integration details for ElevenLabs and Telegram blocks. - **Style** - Added ElevenLabs logos for light and dark themes. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@ -38,6 +38,12 @@ Make sure to check out other blocks implementations in the [packages/forge/block
|
|||||||
|
|
||||||
## Live tutorials
|
## Live tutorials
|
||||||
|
|
||||||
|
### The creation of ElevenLabs block
|
||||||
|
|
||||||
|
<LoomVideo id="c77d5e42bc3b457dbe7bc622e12e0017" />
|
||||||
|
|
||||||
|
### The creation of the Telegram block
|
||||||
|
|
||||||
<LoomVideo id="c49ced0c2c394751b860458b7eb904a4" />
|
<LoomVideo id="c49ced0c2c394751b860458b7eb904a4" />
|
||||||
|
|
||||||
Make sure to join our [Discord community](https://typebot.io/discord) to participate to these weekly office hours.
|
Make sure to join our [Discord community](https://typebot.io/discord) to participate to these weekly office hours.
|
||||||
|
9
apps/docs/editor/blocks/integrations/elevenlabs.mdx
Normal file
9
apps/docs/editor/blocks/integrations/elevenlabs.mdx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: ElevenLabs
|
||||||
|
---
|
||||||
|
|
||||||
|
This block allows you to integrate ElevenLabs API into your typebot.
|
||||||
|
|
||||||
|
## Convert text to speech
|
||||||
|
|
||||||
|
Allows you to transform a text into a speech using ElevenLabs voices and models. This action automatically create a temporary link to the audio file. This link can be used in a [Audio bubble](../bubbles/audio) for example.
|
@ -126,7 +126,8 @@
|
|||||||
"editor/blocks/integrations/meta-pixel",
|
"editor/blocks/integrations/meta-pixel",
|
||||||
"editor/blocks/integrations/openai",
|
"editor/blocks/integrations/openai",
|
||||||
"editor/blocks/integrations/zemantic-ai",
|
"editor/blocks/integrations/zemantic-ai",
|
||||||
"editor/blocks/integrations/mistral"
|
"editor/blocks/integrations/mistral",
|
||||||
|
"editor/blocks/integrations/elevenlabs"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -18693,7 +18693,8 @@
|
|||||||
"chat-node",
|
"chat-node",
|
||||||
"qr-code",
|
"qr-code",
|
||||||
"dify-ai",
|
"dify-ai",
|
||||||
"mistral"
|
"mistral",
|
||||||
|
"elevenlabs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"options": {}
|
"options": {}
|
||||||
|
@ -9263,7 +9263,8 @@
|
|||||||
"chat-node",
|
"chat-node",
|
||||||
"qr-code",
|
"qr-code",
|
||||||
"dify-ai",
|
"dify-ai",
|
||||||
"mistral"
|
"mistral",
|
||||||
|
"elevenlabs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"options": {}
|
"options": {}
|
||||||
|
112
packages/forge/blocks/elevenlabs/actions/convertTextToSpeech.ts
Normal file
112
packages/forge/blocks/elevenlabs/actions/convertTextToSpeech.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { createAction, option } from '@typebot.io/forge'
|
||||||
|
import { auth } from '../auth'
|
||||||
|
import { baseUrl } from '../constants'
|
||||||
|
import { ModelsResponse, VoicesResponse } from '../type'
|
||||||
|
import got, { HTTPError } from 'got'
|
||||||
|
import { uploadFileToBucket } from '@typebot.io/lib/s3/uploadFileToBucket'
|
||||||
|
import { createId } from '@typebot.io/lib/createId'
|
||||||
|
|
||||||
|
export const convertTextToSpeech = createAction({
|
||||||
|
name: 'Convert text to speech',
|
||||||
|
auth,
|
||||||
|
options: option.object({
|
||||||
|
text: option.string.layout({
|
||||||
|
label: 'Text',
|
||||||
|
inputType: 'textarea',
|
||||||
|
placeholder: 'Enter the text to convert to speech',
|
||||||
|
}),
|
||||||
|
voiceId: option.string.layout({
|
||||||
|
fetcher: 'fetchVoices',
|
||||||
|
label: 'Voice ID',
|
||||||
|
placeholder: 'Select a voice',
|
||||||
|
}),
|
||||||
|
modelId: option.string.layout({
|
||||||
|
fetcher: 'fetchModels',
|
||||||
|
label: 'Model ID',
|
||||||
|
placeholder: 'Select a model',
|
||||||
|
defaultValue: 'eleven_monolingual_v1',
|
||||||
|
}),
|
||||||
|
saveUrlInVariableId: option.string.layout({
|
||||||
|
label: 'Save audio URL in variable',
|
||||||
|
placeholder: 'Select a variable',
|
||||||
|
inputType: 'variableDropdown',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
fetchers: [
|
||||||
|
{
|
||||||
|
id: 'fetchVoices',
|
||||||
|
fetch: async ({ credentials }) => {
|
||||||
|
const response = await got
|
||||||
|
.get(baseUrl + '/v1/voices', {
|
||||||
|
headers: {
|
||||||
|
'xi-api-key': credentials.apiKey,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.json<VoicesResponse>()
|
||||||
|
|
||||||
|
return response.voices.map((voice) => ({
|
||||||
|
value: voice.voice_id,
|
||||||
|
label: voice.name,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
dependencies: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'fetchModels',
|
||||||
|
fetch: async ({ credentials }) => {
|
||||||
|
const response = await got
|
||||||
|
.get(baseUrl + '/v1/models', {
|
||||||
|
headers: {
|
||||||
|
'xi-api-key': credentials.apiKey,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.json<ModelsResponse>()
|
||||||
|
|
||||||
|
return response.map((model) => ({
|
||||||
|
value: model.model_id,
|
||||||
|
label: model.name,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
dependencies: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
run: {
|
||||||
|
server: async ({ credentials, options, variables, logs }) => {
|
||||||
|
if (!options.voiceId) return logs.add('Voice ID is missing')
|
||||||
|
if (!options.text) return logs.add('Text is missing')
|
||||||
|
if (!options.saveUrlInVariableId)
|
||||||
|
return logs.add('Save variable is missing')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await got
|
||||||
|
.post(baseUrl + '/v1/text-to-speech/' + options.voiceId, {
|
||||||
|
headers: {
|
||||||
|
Accept: 'audio/mpeg',
|
||||||
|
'xi-api-key': credentials.apiKey,
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
model_id: options.modelId,
|
||||||
|
text: options.text,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.buffer()
|
||||||
|
|
||||||
|
const url = await uploadFileToBucket({
|
||||||
|
file: response,
|
||||||
|
key: `tmp/elevenlabs/audio/${createId() + createId()}.mp3`,
|
||||||
|
mimeType: 'audio/mpeg',
|
||||||
|
})
|
||||||
|
|
||||||
|
variables.set(options.saveUrlInVariableId, url)
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof HTTPError) {
|
||||||
|
return logs.add({
|
||||||
|
status: 'error',
|
||||||
|
description: err.message,
|
||||||
|
details: err.response.body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
15
packages/forge/blocks/elevenlabs/auth.ts
Normal file
15
packages/forge/blocks/elevenlabs/auth.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { option, AuthDefinition } from '@typebot.io/forge'
|
||||||
|
|
||||||
|
export const auth = {
|
||||||
|
type: 'encryptedCredentials',
|
||||||
|
name: 'ElevenLabs account',
|
||||||
|
schema: option.object({
|
||||||
|
apiKey: option.string.layout({
|
||||||
|
label: 'API key',
|
||||||
|
isRequired: true,
|
||||||
|
inputType: 'password',
|
||||||
|
helperText:
|
||||||
|
'You can generate an API key in your ElevenLabs dashboard in the Profile menu.',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
} satisfies AuthDefinition
|
1
packages/forge/blocks/elevenlabs/constants.ts
Normal file
1
packages/forge/blocks/elevenlabs/constants.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const baseUrl = 'https://api.elevenlabs.io'
|
15
packages/forge/blocks/elevenlabs/index.ts
Normal file
15
packages/forge/blocks/elevenlabs/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { createBlock } from '@typebot.io/forge'
|
||||||
|
import { ElevenlabsLogo, ElevenlabsLogoDark } from './logo'
|
||||||
|
import { auth } from './auth'
|
||||||
|
import { convertTextToSpeech } from './actions/convertTextToSpeech'
|
||||||
|
|
||||||
|
export const elevenlabs = createBlock({
|
||||||
|
id: 'elevenlabs',
|
||||||
|
name: 'ElevenLabs',
|
||||||
|
tags: ['ai', 'voice', 'generation'],
|
||||||
|
LightLogo: ElevenlabsLogo,
|
||||||
|
DarkLogo: ElevenlabsLogoDark,
|
||||||
|
auth,
|
||||||
|
actions: [convertTextToSpeech],
|
||||||
|
docsUrl: 'https://docs.typebot.io/forge/blocks/elevenlabs',
|
||||||
|
})
|
16
packages/forge/blocks/elevenlabs/logo.tsx
Normal file
16
packages/forge/blocks/elevenlabs/logo.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export const ElevenlabsLogo = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg viewBox="0 0 100 100" {...props}>
|
||||||
|
<path d="M57.8521 7H73.9999V92.2719H57.8521V7Z" fill="#1D1E1C" />
|
||||||
|
<path d="M42.1059 92.2715H26V7.021H42.1059V92.2715Z" fill="#1D1E1C" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ElevenlabsLogoDark = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg viewBox="0 0 100 100" {...props}>
|
||||||
|
<rect width="100" height="100" rx="8" fill="white" />
|
||||||
|
<path d="M57.8521 7H73.9999V92.2719H57.8521V7Z" fill="#1D1E1C" />
|
||||||
|
<path d="M42.1059 92.2715H26V7.021H42.1059V92.2715Z" fill="#1D1E1C" />
|
||||||
|
</svg>
|
||||||
|
)
|
19
packages/forge/blocks/elevenlabs/package.json
Normal file
19
packages/forge/blocks/elevenlabs/package.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@typebot.io/elevenlabs-block",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.ts",
|
||||||
|
"keywords": [],
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@typebot.io/forge": "workspace:*",
|
||||||
|
"@typebot.io/lib": "workspace:*",
|
||||||
|
"@typebot.io/tsconfig": "workspace:*",
|
||||||
|
"@types/react": "18.2.15",
|
||||||
|
"typescript": "5.3.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"got": "12.6.0",
|
||||||
|
"@typebot.io/lib": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
10
packages/forge/blocks/elevenlabs/tsconfig.json
Normal file
10
packages/forge/blocks/elevenlabs/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "@typebot.io/tsconfig/base.json",
|
||||||
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react"
|
||||||
|
}
|
||||||
|
}
|
11
packages/forge/blocks/elevenlabs/type.ts
Normal file
11
packages/forge/blocks/elevenlabs/type.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export type VoicesResponse = {
|
||||||
|
voices: {
|
||||||
|
name: string
|
||||||
|
voice_id: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModelsResponse = {
|
||||||
|
model_id: string
|
||||||
|
name: string
|
||||||
|
}[]
|
@ -15,4 +15,5 @@ export const openAIBlock = createBlock({
|
|||||||
auth,
|
auth,
|
||||||
options: baseOptions,
|
options: baseOptions,
|
||||||
actions: [createChatCompletion, askAssistant, createSpeech],
|
actions: [createChatCompletion, askAssistant, createSpeech],
|
||||||
|
docsUrl: 'https://docs.typebot.io/forge/blocks/openai',
|
||||||
})
|
})
|
||||||
|
@ -7,4 +7,5 @@ export const enabledBlocks = [
|
|||||||
'qr-code',
|
'qr-code',
|
||||||
'dify-ai',
|
'dify-ai',
|
||||||
'mistral',
|
'mistral',
|
||||||
|
'elevenlabs',
|
||||||
] as const
|
] as const
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
|
import { elevenlabs } from '@typebot.io/elevenlabs-block'
|
||||||
import { difyAi } from '@typebot.io/dify-ai-block'
|
import { difyAi } from '@typebot.io/dify-ai-block'
|
||||||
import { mistral } from '@typebot.io/mistral-block'
|
import { mistral } from '@typebot.io/mistral-block'
|
||||||
import { qrCode } from '@typebot.io/qrcode-block'
|
import { qrCode } from '@typebot.io/qrcode-block'
|
||||||
@ -21,7 +22,8 @@ export const forgedBlocks = [
|
|||||||
chatNode,
|
chatNode,
|
||||||
qrCode,
|
qrCode,
|
||||||
difyAi,
|
difyAi,
|
||||||
mistral
|
mistral,
|
||||||
|
elevenlabs,
|
||||||
] as BlockDefinition<(typeof enabledBlocks)[number], any, any>[]
|
] as BlockDefinition<(typeof enabledBlocks)[number], any, any>[]
|
||||||
|
|
||||||
export type ForgedBlockDefinition = (typeof forgedBlocks)[number]
|
export type ForgedBlockDefinition = (typeof forgedBlocks)[number]
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"@typebot.io/chat-node-block": "workspace:*",
|
"@typebot.io/chat-node-block": "workspace:*",
|
||||||
"@typebot.io/qrcode-block": "workspace:*",
|
"@typebot.io/qrcode-block": "workspace:*",
|
||||||
"@typebot.io/dify-ai-block": "workspace:*",
|
"@typebot.io/dify-ai-block": "workspace:*",
|
||||||
"@typebot.io/mistral-block": "workspace:*"
|
"@typebot.io/mistral-block": "workspace:*",
|
||||||
|
"@typebot.io/elevenlabs-block": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
pnpm-lock.yaml
generated
25
pnpm-lock.yaml
generated
@ -1227,6 +1227,28 @@ importers:
|
|||||||
specifier: 5.3.2
|
specifier: 5.3.2
|
||||||
version: 5.3.2
|
version: 5.3.2
|
||||||
|
|
||||||
|
packages/forge/blocks/elevenlabs:
|
||||||
|
dependencies:
|
||||||
|
'@typebot.io/lib':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../lib
|
||||||
|
got:
|
||||||
|
specifier: 12.6.0
|
||||||
|
version: 12.6.0
|
||||||
|
devDependencies:
|
||||||
|
'@typebot.io/forge':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../core
|
||||||
|
'@typebot.io/tsconfig':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../tsconfig
|
||||||
|
'@types/react':
|
||||||
|
specifier: 18.2.15
|
||||||
|
version: 18.2.15
|
||||||
|
typescript:
|
||||||
|
specifier: 5.3.2
|
||||||
|
version: 5.3.2
|
||||||
|
|
||||||
packages/forge/blocks/mistral:
|
packages/forge/blocks/mistral:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mistralai/mistralai':
|
'@mistralai/mistralai':
|
||||||
@ -1370,6 +1392,9 @@ importers:
|
|||||||
'@typebot.io/dify-ai-block':
|
'@typebot.io/dify-ai-block':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../blocks/difyAi
|
version: link:../blocks/difyAi
|
||||||
|
'@typebot.io/elevenlabs-block':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../blocks/elevenlabs
|
||||||
'@typebot.io/forge':
|
'@typebot.io/forge':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../core
|
version: link:../core
|
||||||
|
Reference in New Issue
Block a user