2
0

Add audio clips option on text input block

Closes #157
This commit is contained in:
Baptiste Arnaud
2024-08-20 14:35:20 +02:00
parent 984c2bf387
commit 135251d3f7
55 changed files with 1535 additions and 366 deletions

View File

@ -31,6 +31,13 @@ export default defineConfig({
locale: 'en-US',
baseURL: process.env.NEXTAUTH_URL,
storageState: './src/test/storageState.json',
permissions: ['microphone'],
launchOptions: {
args: [
'--use-fake-ui-for-media-stream',
'--use-fake-device-for-media-stream',
],
},
},
projects: [
{

View File

@ -16,6 +16,10 @@ export const TextInputNodeContent = ({ options }: Props) => {
typebot &&
options?.attachments?.isEnabled &&
options?.attachments.saveVariableId
const audioClipVariableId =
typebot &&
options?.audioClip?.isEnabled &&
options?.audioClip.saveVariableId
if (options?.variableId)
return (
<Stack w="calc(100% - 25px)">
@ -29,6 +33,12 @@ export const TextInputNodeContent = ({ options }: Props) => {
variableId={attachmentVariableId}
/>
)}
{audioClipVariableId && (
<SetVariableLabel
variables={typebot.variables}
variableId={audioClipVariableId}
/>
)}
</Stack>
)
return (
@ -43,6 +53,12 @@ export const TextInputNodeContent = ({ options }: Props) => {
variableId={attachmentVariableId}
/>
)}
{audioClipVariableId && (
<SetVariableLabel
variables={typebot.variables}
variableId={audioClipVariableId}
/>
)}
</Stack>
)
}

View File

@ -49,6 +49,26 @@ export const TextInputSettings = ({ options, onOptionsChange }: Props) => {
attachments: { ...options?.attachments, visibility },
})
const updateAudioClipEnabled = (isEnabled: boolean) =>
onOptionsChange({
...options,
audioClip: { ...options?.audioClip, isEnabled },
})
const updateAudioClipSaveVariableId = (variable?: Pick<Variable, 'id'>) =>
onOptionsChange({
...options,
audioClip: { ...options?.audioClip, saveVariableId: variable?.id },
})
const updateAudioClipVisibility = (
visibility: (typeof fileVisibilityOptions)[number]
) =>
onOptionsChange({
...options,
audioClip: { ...options?.audioClip, visibility },
})
return (
<Stack spacing={4}>
<SwitchWithLabel
@ -71,6 +91,34 @@ export const TextInputSettings = ({ options, onOptionsChange }: Props) => {
}
onChange={updateButtonLabel}
/>
<SwitchWithRelatedSettings
label={'Allow audio clip'}
initialValue={
options?.audioClip?.isEnabled ??
defaultTextInputOptions.audioClip.isEnabled
}
onCheckChange={updateAudioClipEnabled}
>
<Stack>
<FormLabel mb="0" htmlFor="variable">
Save the URL in a variable:
</FormLabel>
<VariableSearchInput
initialVariableId={options?.audioClip?.saveVariableId}
onSelectVariable={updateAudioClipSaveVariableId}
/>
</Stack>
<DropdownList
label="Visibility:"
moreInfoTooltip='This setting determines who can see the uploaded files. "Public" means that anyone who has the link can see the files. "Private" means that only a members of this workspace can see the files.'
currentItem={
options?.audioClip?.visibility ??
defaultTextInputOptions.audioClip.visibility
}
onItemSelect={updateAudioClipVisibility}
items={fileVisibilityOptions}
/>
</SwitchWithRelatedSettings>
<SwitchWithRelatedSettings
label={'Allow attachments'}
initialValue={

View File

@ -39,7 +39,7 @@ test.describe.parallel('Text input block', () => {
await expect(page.getByRole('button', { name: 'Go' })).toBeVisible()
})
test('hey boy', async ({ page }) => {
test('attachments should work', async ({ page }) => {
const typebotId = createId()
await createTypebots([
{
@ -82,4 +82,31 @@ test.describe.parallel('Text input block', () => {
).toBeVisible()
await expect(page.getByText('Help me with these')).toBeVisible()
})
test('audio clips should work', async ({ page }) => {
const typebotId = createId()
await createTypebots([
{
id: typebotId,
...parseDefaultGroupWithBlock({
type: InputBlockType.TEXT,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click(`text=${defaultTextInputOptions.labels.placeholder}`)
await page.getByText('Allow audio clip').click()
await page.locator('[data-testid="variables-input"]').first().click()
await page.getByText('var1').click()
await page.getByRole('button', { name: 'Test' }).click()
await page.getByRole('button', { name: 'Record voice' }).click()
await page.waitForTimeout(1000)
await page.getByRole('button', { name: 'Send' }).click()
await expect(page.locator('audio')).toHaveAttribute(
'src',
/http:\/\/localhost:9000/
)
})
})

View File

@ -12986,6 +12986,37 @@
"workspaceId",
"name"
]
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"apiKey": {
"type": "string"
}
}
},
"type": {
"type": "string",
"enum": [
"segment"
]
},
"workspaceId": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"data",
"type",
"workspaceId",
"name"
]
}
]
},
@ -13106,7 +13137,8 @@
"anthropic",
"together-ai",
"open-router",
"nocodb"
"nocodb",
"segment"
]
}
}
@ -13142,7 +13174,8 @@
"anthropic",
"together-ai",
"open-router",
"nocodb"
"nocodb",
"segment"
]
},
"name": {
@ -13838,6 +13871,37 @@
"type",
"workspaceId"
]
},
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"data": {
"type": "object",
"properties": {
"apiKey": {
"type": "string"
}
}
},
"type": {
"type": "string",
"enum": [
"segment"
]
},
"workspaceId": {
"type": "string"
}
},
"required": [
"name",
"data",
"type",
"workspaceId"
]
}
]
}
@ -16402,6 +16466,25 @@
"isLong": {
"type": "boolean"
},
"audioClip": {
"type": "object",
"properties": {
"isEnabled": {
"type": "boolean"
},
"saveVariableId": {
"type": "string"
},
"visibility": {
"type": "string",
"enum": [
"Auto",
"Public",
"Private"
]
}
}
},
"attachments": {
"type": "object",
"properties": {
@ -22214,6 +22297,176 @@
"id",
"type"
]
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"outgoingEdgeId": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"segment"
]
},
"options": {
"oneOf": [
{
"type": "object",
"properties": {
"credentialsId": {
"type": "string"
}
}
},
{
"type": "object",
"properties": {
"credentialsId": {
"type": "string"
},
"action": {
"type": "string",
"enum": [
"Alias"
]
},
"userId": {
"type": "string"
},
"previousId": {
"type": "string"
}
},
"required": [
"action"
]
},
{
"type": "object",
"properties": {
"credentialsId": {
"type": "string"
},
"action": {
"type": "string",
"enum": [
"Identify User"
]
},
"userId": {
"type": "string"
},
"email": {
"type": "string"
},
"traits": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
}
},
"required": [
"action"
]
},
{
"type": "object",
"properties": {
"credentialsId": {
"type": "string"
},
"action": {
"type": "string",
"enum": [
"Page"
]
},
"userId": {
"type": "string"
},
"name": {
"type": "string"
},
"category": {
"type": "string"
},
"properties": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
}
},
"required": [
"action"
]
},
{
"type": "object",
"properties": {
"credentialsId": {
"type": "string"
},
"action": {
"type": "string",
"enum": [
"Track"
]
},
"eventName": {
"type": "string"
},
"userId": {
"type": "string"
},
"properties": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
}
},
"required": [
"action"
]
}
]
}
},
"required": [
"id",
"type"
]
}
],
"title": "Block"

View File

@ -1082,6 +1082,25 @@
"type",
"text"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"audio"
]
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"description": "Can only be provided if current input block is a text input that allows audio clips"
}
],
"description": "Only provide it if your flow starts with an input block and you'd like to directly provide an answer to it."
@ -1502,6 +1521,25 @@
"type",
"text"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"audio"
]
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"description": "Can only be provided if current input block is a text input that allows audio clips"
}
]
},
@ -1872,6 +1910,25 @@
"type",
"text"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"audio"
]
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"description": "Can only be provided if current input block is a text input that allows audio clips"
}
]
},
@ -5928,6 +5985,25 @@
"isLong": {
"type": "boolean"
},
"audioClip": {
"type": "object",
"properties": {
"isEnabled": {
"type": "boolean"
},
"saveVariableId": {
"type": "string"
},
"visibility": {
"type": "string",
"enum": [
"Auto",
"Public",
"Private"
]
}
}
},
"attachments": {
"type": "object",
"properties": {
@ -12505,6 +12581,176 @@
"id",
"type"
]
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"outgoingEdgeId": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"segment"
]
},
"options": {
"oneOf": [
{
"type": "object",
"properties": {
"credentialsId": {
"type": "string"
}
}
},
{
"type": "object",
"properties": {
"credentialsId": {
"type": "string"
},
"action": {
"type": "string",
"enum": [
"Alias"
]
},
"userId": {
"type": "string"
},
"previousId": {
"type": "string"
}
},
"required": [
"action"
]
},
{
"type": "object",
"properties": {
"credentialsId": {
"type": "string"
},
"action": {
"type": "string",
"enum": [
"Identify User"
]
},
"userId": {
"type": "string"
},
"email": {
"type": "string"
},
"traits": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
}
},
"required": [
"action"
]
},
{
"type": "object",
"properties": {
"credentialsId": {
"type": "string"
},
"action": {
"type": "string",
"enum": [
"Page"
]
},
"userId": {
"type": "string"
},
"name": {
"type": "string"
},
"category": {
"type": "string"
},
"properties": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
}
},
"required": [
"action"
]
},
{
"type": "object",
"properties": {
"credentialsId": {
"type": "string"
},
"action": {
"type": "string",
"enum": [
"Track"
]
},
"eventName": {
"type": "string"
},
"userId": {
"type": "string"
},
"properties": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
}
},
"required": [
"action"
]
}
]
}
},
"required": [
"id",
"type"
]
}
],
"title": "Block"

View File

@ -85,7 +85,9 @@ export const generateUploadUrl = publicProcedure
if (
block?.type !== InputBlockType.FILE &&
(block.type !== InputBlockType.TEXT ||
!block.options?.attachments?.isEnabled)
!block.options?.attachments?.isEnabled) &&
(block.type !== InputBlockType.TEXT ||
!block.options?.audioClip?.isEnabled)
)
throw new TRPCError({
code: 'BAD_REQUEST',

View File

@ -27,7 +27,10 @@ test('should work as expected', async ({ page, browser }) => {
await page.goto(`${env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
await expect(page.getByRole('link', { name: 'api.json' })).toHaveAttribute(
'href',
/.+\/api\.json/
/.+\/api\.json/,
{
timeout: 10000,
}
)
await expect(
page.getByRole('link', { name: 'fileUpload.json' })

View File

@ -17,7 +17,9 @@ test('Big groups should work as expected', async ({ page }) => {
await page.locator('input').press('Enter')
await page.getByRole('button', { name: 'Yes' }).click()
await page.goto(`${env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
await expect(page.locator('text="Baptiste"')).toBeVisible()
await expect(page.locator('text="Baptiste"')).toBeVisible({
timeout: 10000,
})
await expect(page.locator('text="26"')).toBeVisible()
await expect(page.locator('text="Yes"')).toBeVisible()
await page.hover('tbody > tr')