🚸 Improve audio clip status change and feedback
This commit is contained in:
@ -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",
|
||||||
|
@ -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,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -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
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"noEmitOnError": true,
|
"noEmitOnError": true
|
||||||
"sourceMap": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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: {
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"noEmitOnError": true,
|
"noEmitOnError": true
|
||||||
"sourceMap": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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: {
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"noEmitOnError": true,
|
"noEmitOnError": true
|
||||||
"sourceMap": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user