Skip to content

Commit

Permalink
✨ Add ElevenLabs block (#1226)
Browse files Browse the repository at this point in the history
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 -->
  • Loading branch information
baptisteArno committed Feb 7, 2024
1 parent 066fabc commit 2f6de8e
Show file tree
Hide file tree
Showing 18 changed files with 252 additions and 5 deletions.
6 changes: 6 additions & 0 deletions apps/docs/contribute/guides/create-block.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ Make sure to check out other blocks implementations in the [packages/forge/block

## Live tutorials

### The creation of ElevenLabs block

<LoomVideo id="c77d5e42bc3b457dbe7bc622e12e0017" />

### The creation of the Telegram block

<LoomVideo id="c49ced0c2c394751b860458b7eb904a4" />

Make sure to join our [Discord community](https://typebot.io/discord) to participate to these weekly office hours.
9 changes: 9 additions & 0 deletions apps/docs/editor/blocks/integrations/elevenlabs.mdx
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 2 additions & 1 deletion apps/docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@
"editor/blocks/integrations/meta-pixel",
"editor/blocks/integrations/openai",
"editor/blocks/integrations/zemantic-ai",
"editor/blocks/integrations/mistral"
"editor/blocks/integrations/mistral",
"editor/blocks/integrations/elevenlabs"
]
}
]
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/openapi/builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -18693,7 +18693,8 @@
"chat-node",
"qr-code",
"dify-ai",
"mistral"
"mistral",
"elevenlabs"
]
},
"options": {}
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/openapi/viewer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9263,7 +9263,8 @@
"chat-node",
"qr-code",
"dify-ai",
"mistral"
"mistral",
"elevenlabs"
]
},
"options": {}
Expand Down
112 changes: 112 additions & 0 deletions packages/forge/blocks/elevenlabs/actions/convertTextToSpeech.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions packages/forge/blocks/elevenlabs/auth.ts
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions packages/forge/blocks/elevenlabs/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const baseUrl = 'https://api.elevenlabs.io'
15 changes: 15 additions & 0 deletions packages/forge/blocks/elevenlabs/index.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions packages/forge/blocks/elevenlabs/logo.tsx
Original file line number Diff line number Diff line change
@@ -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 changes: 19 additions & 0 deletions packages/forge/blocks/elevenlabs/package.json
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions packages/forge/blocks/elevenlabs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 0 deletions packages/forge/blocks/elevenlabs/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type VoicesResponse = {
voices: {
name: string
voice_id: string
}[]
}

export type ModelsResponse = {
model_id: string
name: string
}[]
1 change: 1 addition & 0 deletions packages/forge/blocks/openai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export const openAIBlock = createBlock({
auth,
options: baseOptions,
actions: [createChatCompletion, askAssistant, createSpeech],
docsUrl: 'https://docs.typebot.io/forge/blocks/openai',
})
1 change: 1 addition & 0 deletions packages/forge/repository/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export const enabledBlocks = [
'qr-code',
'dify-ai',
'mistral',
'elevenlabs',
] as const
4 changes: 3 additions & 1 deletion packages/forge/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Do not edit this file manually
import { elevenlabs } from '@typebot.io/elevenlabs-block'
import { difyAi } from '@typebot.io/dify-ai-block'
import { mistral } from '@typebot.io/mistral-block'
import { qrCode } from '@typebot.io/qrcode-block'
Expand All @@ -21,7 +22,8 @@ export const forgedBlocks = [
chatNode,
qrCode,
difyAi,
mistral
mistral,
elevenlabs,
] as BlockDefinition<(typeof enabledBlocks)[number], any, any>[]

export type ForgedBlockDefinition = (typeof forgedBlocks)[number]
Expand Down
3 changes: 2 additions & 1 deletion packages/forge/schemas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@typebot.io/chat-node-block": "workspace:*",
"@typebot.io/qrcode-block": "workspace:*",
"@typebot.io/dify-ai-block": "workspace:*",
"@typebot.io/mistral-block": "workspace:*"
"@typebot.io/mistral-block": "workspace:*",
"@typebot.io/elevenlabs-block": "workspace:*"
}
}
25 changes: 25 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2f6de8e

Please sign in to comment.