Skip to content

Commit

Permalink
fix(wakeword): wait for permission to listen; add getWakeword
Browse files Browse the repository at this point in the history
- getWakeword retrieves all utterances from core and chooses one at random
  • Loading branch information
timmywil committed Jul 23, 2020
1 parent ae0065c commit df69455
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 23 deletions.
53 changes: 36 additions & 17 deletions src/components/Wakeword.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import * as theme from '../styles/theme'

import React, { PureComponent } from 'react'
import { getWakeword, uploadWakeword } from '../utils/wakeword'

import Button from './Button'
import Card from './Card'
import { CopyButton } from './EditButtons'
import { MIN_DEFAULT_MEDIA_QUERY } from 'typography-breakpoint-constants'
import SVGIcon from './SVGIcon'
import { capitalize } from 'lodash'
import { css } from '@emotion/core'
import randomChoice from '../utils/randomChoice'
import { record } from 'spokestack/client'
import uploadWakeword from '../utils/uploadWakeword'

const MAX_RECORD_TIME = 2.9

Expand Down Expand Up @@ -38,16 +38,17 @@ function Checkmark(props: React.SVGProps<SVGSVGElement>) {
interface Props {
numRecordings?: number
assistant: string
wakewords: string[]
}

interface State {
error: string
listening: boolean
loading: boolean
uploading: boolean
remaining: number
recorded: number
token: string
wakeword: string
}

export default class Wakeword extends PureComponent<Props, State> {
Expand All @@ -61,20 +62,36 @@ export default class Wakeword extends PureComponent<Props, State> {
state: State = {
error: '',
listening: false,
loading: false,
uploading: false,
remaining: this.props.numRecordings,
recorded: 0,
token: ''
token: '',
wakeword: ''
}

constructor(props: Props) {
super(props)
this.wakeword = randomChoice(props.wakewords)
async componentDidMount() {
const { assistant } = this.props
this.setState({ loading: true })
const [error, wakeword] = await getWakeword(assistant)
if (error) {
this.setState({ error: error.message, loading: false })
return
}
this.setState({ wakeword, loading: false })
}

getMessage() {
const { numRecordings } = this.props
const { error, listening, uploading, remaining, recorded } = this.state
const {
error,
listening,
loading,
uploading,
remaining,
recorded,
wakeword
} = this.state
if (error) {
return error
}
Expand All @@ -84,26 +101,29 @@ export default class Wakeword extends PureComponent<Props, State> {
if (uploading) {
return 'Uploading audio data...'
}
if (loading) {
return 'Loading...'
}
if (recorded === 0) {
return `Say \u201C${this.wakeword}\u201D`
return `Say \u201C${capitalize(wakeword)}\u201D`
}
if (recorded < numRecordings - 1) {
return `Say \u201C${this.wakeword}\u201D again`
return `Say \u201C${capitalize(wakeword)}\u201D again`
}
if (recorded === numRecordings - 1) {
return `Say \u201C${this.wakeword}\u201D one more time`
return `Say \u201C${capitalize(wakeword)}\u201D one more time`
}
return 'Thanks for your help!'
}

upload = async (buffer: AudioBuffer) => {
const { assistant } = this.props
const { recorded } = this.state
const { recorded, wakeword } = this.state
this.setState({ listening: false, uploading: true })
const [uploadError, response] = await uploadWakeword({
buffer,
assistant,
wakeword: this.wakeword
wakeword
})
if (uploadError) {
this.setState({ error: uploadError.message, uploading: false })
Expand Down Expand Up @@ -132,11 +152,10 @@ export default class Wakeword extends PureComponent<Props, State> {
if (recorded >= numRecordings) {
return
}
this.setState({ listening: true })
record({
time: MAX_RECORD_TIME,
onProgress: (remaining) => {
this.setState({ remaining })
this.setState({ listening: true, remaining })
}
})
.then(this.upload)
Expand All @@ -150,7 +169,7 @@ export default class Wakeword extends PureComponent<Props, State> {

render() {
const { numRecordings } = this.props
const { listening, uploading, recorded, token } = this.state
const { error, listening, loading, uploading, recorded, token } = this.state

return (
<div css={styles.container}>
Expand Down Expand Up @@ -183,7 +202,7 @@ export default class Wakeword extends PureComponent<Props, State> {
</div>
) : (
<Button
disabled={uploading || recorded >= numRecordings}
disabled={loading || !!error || recorded >= numRecordings}
submitting={uploading || listening}
onClick={this.record}>
<SVGIcon icon="#mic" extraCss={styles.mic} />
Expand Down
5 changes: 1 addition & 4 deletions src/pages/wakeword/bartender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import Wakeword from '../../components/Wakeword'
export default function WakewordBartender({ location }: PageRendererProps) {
return (
<Layout location={location}>
<Wakeword
assistant="spokestack-bartender"
wakewords={['Hey bartender', 'Bartender']}
/>
<Wakeword assistant="spokestack-bartender" />
</Layout>
)
}
34 changes: 32 additions & 2 deletions src/utils/uploadWakeword.ts → src/utils/wakeword.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import WavEncoder from 'wav-encoder'
import { login } from './auth'
import postToCore from './postToCore'
import randomChoice from './randomChoice'

interface UploadOptions {
buffer: AudioBuffer
assistant: string
wakeword: string
}

export default async function uploadWakeword({
export async function getWakeword(assistant: string) {
const [loginError, token] = await login()
if (loginError || !token) {
return [
loginError ||
new Error('Failed to generate auth token. Please check your network.')
]
}
return fetch(
`${process.env.PYLON_CORE_URL}/speech/v1/${assistant}/wakeword`,
{
headers: {
Authorization: token
}
}
)
.then((res) => {
if (res.ok) {
return res.json()
}
throw new Error(`Error retrieving wakeword utterances: ${res.status}`)
})
.then(({ utterances }) => [null, randomChoice<string>(utterances)])
.catch((error) => {
console.error(error)
return [error]
})
}

export async function uploadWakeword({
buffer,
assistant,
wakeword
Expand Down Expand Up @@ -55,7 +85,7 @@ export default async function uploadWakeword({
.json()
.then((json: Record<string, unknown>) => [null, json])
.catch((error: Error) => {
console.log(error, res)
console.error(error)
return [new Error('Error parsing JSON response')]
})
}

0 comments on commit df69455

Please sign in to comment.