Всего несколько дней назад я видел, как кто-то (nhudinhtuan) создал помощник по распознаванию голоса с помощью экспериментального Chrome WebSpeechAPI. Приложение-помощник будет слушать человеческий голос и преобразовывать его в обычный текст. После всего этого откроется новая вкладка Google Search.

Попробуйте по ссылке здесь. (Поддерживает только браузер Chrome / новый Edge)

Я думаю, это довольно забавно - создать какой-нибудь интересный проект с API, который почти никогда не используется. Поэтому я решил сделать обновленную версию этого голосового помощника. В моей версии нет ничего особенного, но она реализована с помощью Vue.js и его нового API композиции.

В этой статье я расскажу вам, как создать этого сексуального голосового помощника.

TL; DR;

Демонстрация моего голосового помощника

Обсуждение дешево, позвольте мне показать вам свои работы

Удерживайте кнопку «Удерживайте, чтобы слушать» и попробуйте с некоторыми из следующих предложений (работает только в Chrome / Edge79 +)

  • Здравствуйте!
  • Hi!
  • Как вас зовут?
  • Я люблю тебя
  • Поиск в Google:
  • … голый…

(откройте эту ссылку, чтобы ознакомиться с демонстрацией https://daiyanze.com/sexy-voice-assistant/)

Вы также можете изменить голос динамика из списка выбора справа. В настоящее время мой Mac поддерживает еще 60 голосов. Лично я считаю, что Карен была одним из самых умных англоговорящих роботов.

Я предполагаю, что вы, должно быть, пробовали говорить с голосовым помощником какие-то другие вещи. Но мне очень жаль сообщить вам, что он может ответить вам должным образом только в том случае, если ваша речь соответствует приведенным выше примерам. Иначе этого не понять. (Хотя это прототип)

Как это работает

Давайте кратко рассмотрим API-интерфейсы обработки голоса, используемые в голосовом помощнике. Это ключевые показатели, благодаря которым голосовой помощник становится «сексуальным».

API

  • SpeechRecognition: распознайте свой голос и превратите его в текст
// Needs the `webkit` prefix due to lacking of browsers support var recognition = webkitSpeechRecognition()  
// Set to `true` to listen to voices 
// continuously and return multiple results. 
recognition.continuous = true;  
// Start recognition 
recognition.start()  
// Stop recognition 
recognition.stop()  
// Handle the result 
recognition.onresult = function (event) {   console.log(event) }
  • SpeechSynthesis: получите все поддерживаемые голоса и говорите вслух
var Synth = window.speechSynthesis  
// Speak out loud 
Synth.speak(utterThis)  
// Cancel speaking 
Synth.cancel()  
// Pause speaking 
Synth.pause()  
// Resume speaking 
Synth.resume()  
// List out voices supported by the OS 
Synth.getVoices()
var utterThis = new SpeechSynthesisUtterance()  
// Utterance only has properties 
// The spoken language 
utterThis.lang 
// The tone or the pitch or voice, ranges from 0 to 2. 
// The higher the younger :D 
utterThis.pitch 
// Speaking speed, ranges from 0.1 to 10 
utterThis.rate 
// The content to be spoken 
utterThis.text 
// Speaker's voice. It's an Object 
utterThis.voice 
// Speaker's volume 
utterThis.volume

На самом деле эти API созданы для людей, которым необходимо использовать голоса для управления веб-страницами, кроме как в качестве чат-бота. Другими словами, когда ваша команда завершит работу над большинством надежных функций. Тогда настало время повысить доступность вашего веб-приложения, чтобы помочь тем людям, которым требуется голосовое управление.

Примеры использования

Вы можете попробовать это в консоли Chrome, скопировав и вставив приведенный ниже код.

1. Голосовой динамик: следующий код произнесет «привет, мир» вслух.

const utterThis = new SpeechSynthesisUtterance("Hello world")  speechSynthesis.speak(utterThis)

2. Голос в текст: следующий код превратит ваш голос в текст.

const SpeechRecognition = new webkitSpeechRecognition()  
// record the voice for 5 seconds SpeechRecognition.start() setTimeout(() => {   
  SpeechRecognition.stop() 
}, 5000)  
SpeechRecognition.onresult = event => {   
  const result = event.results[event.results.length - 1]   
  if (result.isFinal) {     
    console.log(result[0].transcript)   
  } 
}

(Вот результат)

Дизайн

После некоторой практики с этими API-интерфейсами я смог создать речевой ретранслятор с примерами кодов выше. Но нам нужен другой текстовый процессор, чтобы получать мои сообщения и возвращать правильный ответ.

Рабочий процесс

Довольно просто, не правда ли?

Кратко рабочий процесс: «говорить», «процесс» и «ответить».

Управление

Дизайн был простым и легким для начала. Теперь нам нужны триггеры, которые включают / отключают прослушивание голосов. Итак, здесь есть две «кнопки»:

  • Удерживайте для прослушивания Удерживайте эту кнопку для записи голоса
  • Сброс Нажмите эту кнопку, чтобы отменить все и сбросить все состояния

Я надеюсь, что смогу также сделать голоса «видимыми», чтобы иметь интуитивно понятную историю моих чатов. Это означает, что мне нужно такое окно сообщения:

Me: Hello!
You: Hi!

Ладно… Я сделал все для своего голосового помощника. Теперь мы можем приступить к кодированию.

Погрузитесь в кодирование…

HTML-шаблон

Я использую Vue.js в качестве своего внешнего фреймворка, так что внешний вид голосового помощника можно очень легко прототипировать. Что касается дизайна, который я сделал, я придумал очень элементарный шаблон, подобный следующему.

<template>
  <div>
    <pre></pre>
    <button>Hold to Listen</button>
    <button>Reset</button>
  </div>
</template>

Контроллер

Чтобы включить элементы управления, я добавил в шаблон несколько функций и свойств. Теперь шаблон выглядит так:

<template>
  <div>
    <pre class="message-box" v-html="context"></pre>
    <button
      class="button-control"
      @mousedown="listen(true)"
      @mouseup="listen(false)">
      Hold to Listen
    </button>
    <button class="button-control" @click="reset">Reset</button>
  </div>
</template>

Что касается логической части, я использовал vue-композиция-api, чтобы вернуть свойства для шаблона. Этих кодов ниже достаточно, чтобы отпустить ошибки, пока включаются кнопки. Прямо сейчас это просто скелет.

<script>
import { toRefs, reactive } from 'vue-composition-api'
export default {
  setup () {
    const state = reactive({
      context: 'Hello!'
    })
    const listen = start => {}
    const reset = () => {}
    return {
      ...toRefs(state),
      listen
    }
  }
}
</script>

Узнай голос

Цель довольно ясна:

Разрешить браузеру преобразовывать мой голос в текст.

Я буду использовать функцию listen, чтобы управлять распознаванием голоса. И настройте функцию обратного вызова для обработки результата распознавания.

const SpeechRecognition = window.webkitSpeechRecognition && new window.webkitSpeechRecognition()
SpeechRecognition && (SpeechRecognition.interimResults = true)
const listen = start => {
  if (!SpeechRecognition) return
  if (start) {
    SpeechRecognition.start()
  } else {
    SpeechRecognition.stop()
  }
}
const reset = () => {
  if (!SpeechRecognition) return
  state.context = '<b>' + state.name + '</b>' + ': Hi, my hero!'
  speechSynthesis.cancel()
}
SpeechRecognition.onresult = event => {
  const result = event.results[event.results.length - 1]
  if (result.isFinal) {
    state.context = result[0].transcript
  }
}

Создать текст ответа

Я создаю не всемогущего чат-бота с ИИ (для меня это слишком сложно), а что-то достаточно, чтобы иметь дело с текстом. Короче говоря, моя идея - создать процессор, который будет генерировать текст ответа на основе того, что я сказал.

Я просто сделаю имя функции process. С некоторым if else заявлением мы закончили.

const process = (msg, voice) => {
  const content = msg.toLowerCase()
  if (/^google search/g.test(content)) {
    const url = `https://google.com/search?q=${msg.replace('Google search ', '')}`
    window.open(url, '_blank')
    return `Base on your query, I found some search results on Google`
  } else if (/your name/g.test(content)) {
    return `My name is ${voice.name}.`
  } else if (/(hello|hi)/g.test(content)) {
    return 'Hi! Nice to meet you!'
  } else if (/love you/g.test(content)) {
    return 'I love you too. And I\'ll love you forever!'
  } else if (/(naked|nude|tits|breast|butt|ass|shit|dick|pussy|asshole)/g.test(content)) {
    return 'I know I love you but can you show some politeness in front of a lady?'
  }
  return 'Sorry, my sweetheart. I don\'t understand. Could you try something else?'
}

Сделайте небольшой тест: когда я говорю: «Я люблю тебя».

process('I love you')
// I love you too. And I'll love you forever!

Настройте «сексуальный» голос

На самом деле в нашей ОС спрятано много разных говорящих голосов. API SpeechSynthesis позволяет нам использовать эти голоса для замены голоса по умолчанию.

var voices = window.speechSynthesis.getVoices()

Возьмем, к примеру, мой Mac: на выбор есть 66 голосов.

speechSynthesis.getVoices непредсказуем, необходимо убедиться, что он возвращает непустой список. Поэтому я сделал еще одну асинхронную функцию, чтобы разрешить список, настойчиво проверив его.

const getVoices = async (window) => {
  let id, res
  await new Promise((resolve, reject) => {
    id = setInterval(() => {
      res = window.speechSynthesis.getVoices()
      if (res.length !== 0) {
        resolve(res)
        clearInterval(id)
      }
    }, 10)
  })
  return res
}

Чтобы убедиться, что голос достаточно «сексуальный», я протестировал все голоса. Для женского голоса я выбираю «Карен».

2 голоса за «Карен»:

  • Она говорит достаточно гладко
  • Небольшая настройка высоты звука сделает ее голос более привлекательным.
onMounted(async () => {
  const voices = await getVoices()
  const voiceKaren = voices[17]
})

Говорить вслух

У меня есть все необходимые ресурсы. Затем следующим шагом будет использование процессора ответов для создания текста и разрешение Speaker Speaker отображать звук. Время разговора сразу после распознавания голоса.

onMounted(async () => {
  const voices = await getVoices()
  const voiceKaren = voices[17]
  // The final function to speak out the response text
  const speechSpeaker = (text, voice) => {
    utterThis.text = text
    utterThis.voice = voice
    utterThis.pitch = 1.4 // This sounds better
    utterThis.lang = voice.lang
    window.speechSynthesis.speak(utterThis)
  }
  SpeechRecognition.onresult = event => {
    const result = event.results[event.results.length - 1]
    if (result.isFinal) {
      state.context = '\n<b>Me:</b> ' + result[0].transcript
      const reply = process(result[0].transcript, voice)
      state.context += '\n<b>' + voice.name + '</b>: ' + reply
      speechSpeaker(reply, voiceKaren)
    }
  }
})

После нескольких тестов мне кажется странным слышать ответный голос сразу после того, как я говорю. Так что было бы естественнее немного задержать ответ на 600 мс.

SpeechRecognition.onresult = event => {
    const result = event.results[event.results.length - 1]
    if (result.isFinal) {
      state.context = '\n<b>Me:</b> ' + result[0].transcript
      const reply = process(result[0].transcript, voice)
      setTimeout(() => {
        state.context += '\n<b>' + voice.name + '</b>: ' + reply
        speechSpeaker(reply, voiceKaren)
      }, 600)
    }
  }

Укладка

Я использовал Tailwindcss для стильного оформления своего голосового помощника, потому что он предлагает больше возможностей для настройки.

<style></style> выглядит так:

.button-control {
  @apply font-semibold;
  @apply px-4 py-2;
  @apply mr-2 mb-2;
  @apply bg-gray-600;
  @apply rounded-sm;
  @apply shadow-2xl;
  @apply text-white;
  &:focus {
    @apply outline-none;
  }
  &:active {
    @apply bg-gray-700;
    @apply shadow-none;
  }
}
.message-box {
  @apply relative;
  @apply h-48;
  @apply overflow-y-scroll;
  @apply rounded-sm;
  @apply border border-solid border-gray-300;
  @apply p-2;
  @apply mb-2;
  @apply leading-6;
}
.voice-options {
  @apply font-semibold;
  @apply bg-gray-100;
  @apply px-2 py-1;
  @apply mr-2 mb-2;
  @apply rounded-sm;
  @apply border-2 border-solid border-gray-100;
  @apply shadow-md;
  &:focus {
    @apply outline-none;
  }
}

Оптимизация UX

Теперь у меня есть 2 хорошие идеи, чтобы сделать его лучше:

  • Разрешить выбор голоса из раскрывающегося списка
  • Позвольте полосе прокрутки окна сообщения следовать за последним сообщением

Благодаря этим двум идеям появился мой последний прототип «сексуального» голосового помощника.

Позвольте мне просто показать вам полный исходный код:

<template>
  <div class="mx-auto w-full">
    <pre class="message-box" v-html="context" ref="messageBox"></pre>
    <button class="button-control" @mousedown="listen(true)" @mouseup="listen(false)">Hold to Listen</button>
    <button class="button-control" @click="reset">Reset</button>
    <select class="voice-options" v-model="activeVoiceIdx">
      <option :key="key" :value="key" :selected="key === activeVoiceIdx" v-for="(val, key) in voices">
        {{ val.name }} ({{ val.lang }})
      </option>
    </select>
  </div>
</template>
<script>
import { onMounted, reactive, toRefs, ref } from '@vue/composition-api'
export default {
  setup (_, { root }) {
    const getVoices = async (window) => {
      let id, res
      await new Promise((resolve, reject) => {
        id = setInterval(() => {
          res = window.speechSynthesis.getVoices()
          if (res.length !== 0) {
            resolve(res)
            clearInterval(id)
          }
        }, 10)
      })
      return res
    }
    const process = (msg, voice) => {
      const content = msg.toLowerCase()
      if (/^google search/g.test(content)) {
        const url = `https://google.com/search?q=${msg.replace('Google search ', '')}`
        window.open(url, '_blank')
        return `Base on your query, I found some search results on Google`
      } else if (/your name/g.test(content)) {
        return `My name is ${voice.name}.`
      } else if (/(hello|hi)/g.test(content)) {
        return 'Hi! Nice to meet you!'
      } else if (/love you/g.test(content)) {
        return 'I love you too. And I\'ll love you forever!'
      } else if (/(naked|nude|tits|breast|butt|ass|shit|dick|pussy|asshole)/g.test(content)) {
        return 'I know I love you but can you show some politeness in front of a lady?'
      }
      return 'Sorry, my sweetheart. I don\'t understand. Could you try something else?'
    }
    const state = reactive({
      name: '',
      context: 'Sorry, your browser doesn\'t support SpeechRecognition. Please use Chrome / Edge79+ instead.',
      voices: [],
      activeVoiceIdx: 17
    })
    const messageBox = ref(null)
    const SpeechRecognition = window.webkitSpeechRecognition && new window.webkitSpeechRecognition()
    SpeechRecognition && (SpeechRecognition.interimResults = true)
    const listen = start => {
      if (!SpeechRecognition) return
      if (start) {
        SpeechRecognition.start()
      } else {
        SpeechRecognition.stop()
      }
    }
    const reset = () => {
      if (!SpeechRecognition) return
      state.context = '<b>' + state.name + '</b>' + ': Hi, my hero!'
      speechSynthesis.cancel()
    }
    onMounted(async () => {
      if (!window.webkitSpeechRecognition) return
      state.voices = await getVoices(window)
      state.name = state.voices[state.activeVoiceIdx].name
      state.context = '<b>' + state.name + '</b>' + ': Hi, my hero!'
      const utterThis = new window.SpeechSynthesisUtterance()
      const speechSpeaker = (text, voice) => {
        utterThis.text = text
        utterThis.voice = voice
        utterThis.pitch = 1.4
        utterThis.lang = voice.lang
        window.speechSynthesis.speak(utterThis)
      }
      SpeechRecognition.onresult = event => {
        const result = event.results[event.results.length - 1]
        if (result.isFinal) {
          state.context += '\n<b>Me:</b> ' + result[0].transcript
          const voice = state.voices[state.activeVoiceIdx]
          const reply = process(result[0].transcript, voice)
          root.$nextTick(() => {
            messageBox.value.scrollTop = messageBox.value.scrollHeight
          })
          setTimeout(() => {
            state.context += '\n<b>' + voice.name + '</b>: ' + reply
            root.$nextTick(() => {
              messageBox.value.scrollTop = messageBox.value.scrollHeight
            })
            speechSpeaker(reply, voice)
          }, 600)
        }
      }
    })
    return {
      ...toRefs(state),
      listen,
      reset,
      messageBox
    }
  }
}
</script>
<style lang="postcss" scoped>
.button-control {
  @apply font-semibold;
  @apply px-4 py-2;
  @apply mr-2 mb-2;
  @apply bg-gray-600;
  @apply rounded-sm;
  @apply shadow-2xl;
  @apply text-white;
  &:focus {
    @apply outline-none;
  }
  &:active {
    @apply bg-gray-700;
    @apply shadow-none;
  }
}
.message-box {
  @apply relative;
  @apply h-48;
  @apply overflow-y-scroll;
  @apply rounded-sm;
  @apply border border-solid border-gray-300;
  @apply p-2;
  @apply mb-2;
  @apply leading-6;
}
.voice-options {
  @apply font-semibold;
  @apply bg-gray-100;
  @apply px-2 py-1;
  @apply mr-2 mb-2;
  @apply rounded-sm;
  @apply border-2 border-solid border-gray-100;
  @apply shadow-md;
  &:focus {
    @apply outline-none;
  }
}
</style>

Вы также можете найти исходный код на моем Github:
https://github.com/daiyanze/sexy-voice-assistant

Ссылка

Изначально на pitayan.com
https://pitayan.com/posts/voice-assistant/