2
0

🚸 Improve audio clip status change and feedback

This commit is contained in:
Baptiste Arnaud
2024-08-20 15:38:11 +02:00
parent e67f3bc9e9
commit 37ef8fe240
11 changed files with 27 additions and 29 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@typebot.io/js", "name": "@typebot.io/js",
"version": "0.3.9", "version": "0.3.10",
"description": "Javascript library to display typebots on your website", "description": "Javascript library to display typebots on your website",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",

View File

@ -21,7 +21,6 @@ const indexConfig = {
output: { output: {
file: 'dist/index.js', file: 'dist/index.js',
format: 'es', format: 'es',
sourcemap: true,
}, },
onwarn, onwarn,
watch: { watch: {
@ -64,7 +63,6 @@ const configs = [
output: { output: {
file: 'dist/web.js', file: 'dist/web.js',
format: 'es', format: 'es',
sourcemap: true,
}, },
}, },
] ]

View File

@ -41,7 +41,9 @@ export const TextInput = (props: Props) => {
{ fileIndex: number; progress: number } | undefined { fileIndex: number; progress: number } | undefined
>(undefined) >(undefined)
const [isDraggingOver, setIsDraggingOver] = createSignal(false) const [isDraggingOver, setIsDraggingOver] = createSignal(false)
const [isRecording, setIsRecording] = createSignal(false) const [recordingStatus, setRecordingStatus] = createSignal<
'started' | 'asking' | 'stopped'
>('stopped')
let inputRef: HTMLInputElement | HTMLTextAreaElement | undefined let inputRef: HTMLInputElement | HTMLTextAreaElement | undefined
let mediaRecorder: MediaRecorder | undefined let mediaRecorder: MediaRecorder | undefined
let recordedChunks: Blob[] = [] let recordedChunks: Blob[] = []
@ -52,7 +54,7 @@ export const TextInput = (props: Props) => {
inputRef?.value !== '' && inputRef?.reportValidity() inputRef?.value !== '' && inputRef?.reportValidity()
const submit = async () => { const submit = async () => {
if (isRecording() && mediaRecorder) { if (recordingStatus() === 'started' && mediaRecorder) {
mediaRecorder.stop() mediaRecorder.stop()
return return
} }
@ -157,17 +159,17 @@ export const TextInput = (props: Props) => {
} }
const recordVoice = () => { const recordVoice = () => {
setIsRecording(true) setRecordingStatus('asking')
} }
const handleRecordingStart = (stream: MediaStream) => { const handleRecordingConfirmed = (stream: MediaStream) => {
mediaRecorder = new MediaRecorder(stream) mediaRecorder = new MediaRecorder(stream)
mediaRecorder.ondataavailable = (event) => { mediaRecorder.ondataavailable = (event) => {
if (event.data.size === 0) return if (event.data.size === 0) return
recordedChunks.push(event.data) recordedChunks.push(event.data)
} }
mediaRecorder.onstop = async () => { mediaRecorder.onstop = async () => {
if (!isRecording() || recordedChunks.length === 0) return if (recordingStatus() !== 'started' || recordedChunks.length === 0) return
const audioFile = new File( const audioFile = new File(
recordedChunks, recordedChunks,
`rec-${props.block.id}-${Date.now()}.mp3`, `rec-${props.block.id}-${Date.now()}.mp3`,
@ -200,11 +202,12 @@ export const TextInput = (props: Props) => {
}) })
} }
mediaRecorder.start() mediaRecorder.start()
setRecordingStatus('started')
} }
const handleRecordingAbort = () => { const handleRecordingAbort = () => {
setIsRecording(false)
mediaRecorder?.stop() mediaRecorder?.stop()
setRecordingStatus('stopped')
mediaRecorder = undefined mediaRecorder = undefined
recordedChunks = [] recordedChunks = []
} }
@ -227,12 +230,12 @@ export const TextInput = (props: Props) => {
)} )}
> >
<VoiceRecorder <VoiceRecorder
isRecording={isRecording()} recordingStatus={recordingStatus()}
buttonsTheme={props.context.typebot.theme.chat?.buttons} buttonsTheme={props.context.typebot.theme.chat?.buttons}
onRecordingStart={handleRecordingStart} onRecordingConfirmed={handleRecordingConfirmed}
onAbortRecording={handleRecordingAbort} onAbortRecording={handleRecordingAbort}
/> />
<Show when={!isRecording()}> <Show when={recordingStatus() !== 'started'}>
<Show when={selectedFiles().length}> <Show when={selectedFiles().length}>
<div <div
class="p-2 flex gap-2 border-gray-100 overflow-auto" class="p-2 flex gap-2 border-gray-100 overflow-auto"
@ -304,7 +307,7 @@ export const TextInput = (props: Props) => {
<Match <Match
when={ when={
!inputValue() && !inputValue() &&
!isRecording() && recordingStatus() !== 'started' &&
props.block.options?.audioClip?.isEnabled props.block.options?.audioClip?.isEnabled
} }
> >

View File

@ -13,10 +13,10 @@ let offset = 0
const initBarsHeightPercent = 10 const initBarsHeightPercent = 10
type Props = { type Props = {
isRecording: boolean recordingStatus: 'asking' | 'started' | 'stopped'
buttonsTheme: NonNullable<Theme['chat']>['buttons'] buttonsTheme: NonNullable<Theme['chat']>['buttons']
onAbortRecording: () => void onAbortRecording: () => void
onRecordingStart: (stream: MediaStream) => void onRecordingConfirmed: (stream: MediaStream) => void
} }
export const VoiceRecorder = (props: Props) => { export const VoiceRecorder = (props: Props) => {
@ -89,7 +89,7 @@ export const VoiceRecorder = (props: Props) => {
stream = await navigator.mediaDevices.getUserMedia({ audio: true }) stream = await navigator.mediaDevices.getUserMedia({ audio: true })
props.onRecordingStart(stream) props.onRecordingConfirmed(stream)
audioContext = new AudioContext() audioContext = new AudioContext()
volumeNode = await loadVolumeProcessorWorklet(audioContext) volumeNode = await loadVolumeProcessorWorklet(audioContext)
@ -126,9 +126,9 @@ export const VoiceRecorder = (props: Props) => {
} }
createEffect(() => { createEffect(() => {
if (props.isRecording) { if (props.recordingStatus === 'asking') {
startRecording() startRecording()
} else { } else if (props.recordingStatus === 'stopped') {
stopRecording() stopRecording()
} }
}) })
@ -141,7 +141,9 @@ export const VoiceRecorder = (props: Props) => {
<div <div
class={clsx( class={clsx(
'w-full gap-2 items-center transition-opacity px-2 typebot-recorder', 'w-full gap-2 items-center transition-opacity px-2 typebot-recorder',
props.isRecording ? 'opacity-1 flex' : 'opacity-0 hidden' props.recordingStatus === 'started'
? 'opacity-1 flex'
: 'opacity-0 hidden'
)} )}
> >
<button <button

View File

@ -16,7 +16,6 @@
"outDir": "dist", "outDir": "dist",
"noEmit": false, "noEmit": false,
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"noEmitOnError": true, "noEmitOnError": true
"sourceMap": true
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@typebot.io/nextjs", "name": "@typebot.io/nextjs",
"version": "0.3.9", "version": "0.3.10",
"description": "Convenient library to display typebots on your Next.js website", "description": "Convenient library to display typebots on your Next.js website",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

View File

@ -16,7 +16,6 @@ const indexConfig = {
output: { output: {
dir: './dist', dir: './dist',
format: 'es', format: 'es',
sourcemap: true,
}, },
external: ['next/dynamic.js', 'react', 'react/jsx-runtime'], external: ['next/dynamic.js', 'react', 'react/jsx-runtime'],
watch: { watch: {

View File

@ -11,7 +11,6 @@
"declarationMap": true, "declarationMap": true,
"noEmit": false, "noEmit": false,
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"noEmitOnError": true, "noEmitOnError": true
"sourceMap": true
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@typebot.io/react", "name": "@typebot.io/react",
"version": "0.3.9", "version": "0.3.10",
"description": "Convenient library to display typebots on your React app", "description": "Convenient library to display typebots on your React app",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

View File

@ -16,7 +16,6 @@ const indexConfig = {
output: { output: {
file: './dist/index.js', file: './dist/index.js',
format: 'es', format: 'es',
sourcemap: true,
}, },
external: ['react', 'react/jsx-runtime'], external: ['react', 'react/jsx-runtime'],
watch: { watch: {

View File

@ -11,7 +11,6 @@
"declarationMap": true, "declarationMap": true,
"noEmit": false, "noEmit": false,
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"noEmitOnError": true, "noEmitOnError": true
"sourceMap": true
} }
} }