mirror of
https://github.com/Picovoice/porcupine.git
synced 2022-01-28 03:27:53 +03:00
update vue binding to use typescript and added vue types (#585)
This commit is contained in:
94
README.md
94
README.md
@@ -1384,60 +1384,60 @@ npm install @picovoice/porcupine-web-vue
|
||||
```
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div class="voice-widget">
|
||||
<Porcupine
|
||||
v-bind:porcupineFactoryArgs="{
|
||||
accessKey: '${ACCESS_KEY}' // AccessKey obtained from [Picovoice Console](https://picovoice.ai/console/),
|
||||
<script lang="ts">
|
||||
import porcupineMixin from "@picovoice/porcupine-web-vue";
|
||||
import { PorcupineWorkerFactoryEn } from "@picovoice/porcupine-web-en-worker";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
mixins: [porcupineMixin],
|
||||
data: function() {
|
||||
return {
|
||||
detections: [] as string[],
|
||||
isError: false,
|
||||
isLoaded: false,
|
||||
factory: PorcupineWorkerFactoryEn,
|
||||
factoryArgs: {
|
||||
accessKey: '${ACCESS_KEY}', // AccessKey obtained from Picovoice Console(https://picovoice.ai/console/)
|
||||
keywords: [
|
||||
{ builtin: 'Grasshopper', sensitivity: 0.5 },
|
||||
{ builtin: 'Grapefruit', sensitivity: 0.6 },
|
||||
],
|
||||
}"
|
||||
v-bind:porcupineFactory="factory"
|
||||
v-on:ppn-ready="ppnReadyFn"
|
||||
v-on:ppn-keyword="ppnKeywordFn"
|
||||
v-on:ppn-error="ppnErrorFn"
|
||||
/>
|
||||
<h3>Keyword Detections:</h3>
|
||||
<ul v-if="detections.length > 0">
|
||||
<li v-for="(item, index) in detections" :key="index">{{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Porcupine from "@picovoice/porcupine-web-vue";
|
||||
import { PorcupineWorkerFactoryEn } from "@picovoice/porcupine-web-en-worker";
|
||||
|
||||
export default {
|
||||
name: "VoiceWidget",
|
||||
components: {
|
||||
Porcupine,
|
||||
}
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
await this.$porcupine.init(
|
||||
this.factoryArgs, // Porcupine factory arguments
|
||||
this.factory, // Porcupine Web Worker component
|
||||
this.ppnKeywordFn, // Callback invoked after detection of keyword
|
||||
this.ppnReadyFn, // Callback invoked after loading Porcupine
|
||||
this.ppnErrorFn // Callback invoked in an error occurs while initializing Porcupine
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
start: function () {
|
||||
if (this.$porcupine.start()) {
|
||||
this.isListening = !this.isListening;
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
detections: [],
|
||||
isError: null,
|
||||
isLoaded: false,
|
||||
factory: PorcupineWorkerFactoryEn,
|
||||
};
|
||||
pause: function () {
|
||||
if (this.$porcupine.pause()) {
|
||||
this.isListening = !this.isListening;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
ppnReadyFn: function () {
|
||||
this.isLoaded = true;
|
||||
},
|
||||
ppnKeywordFn: function (data) {
|
||||
this.detections = [...this.detections, data.keywordLabel];
|
||||
},
|
||||
ppnErrorFn: function (data) {
|
||||
this.isError = true;
|
||||
this.errorMessage = data.toString();
|
||||
},
|
||||
initEngine: function () {
|
||||
this.$refs.porcupine.initEngine();
|
||||
},
|
||||
ppnReadyFn: function() {
|
||||
this.isLoaded = true;
|
||||
},
|
||||
};
|
||||
ppnKeywordFn: function(data: string) {
|
||||
this.detections = [...this.detections, data.keywordLabel];
|
||||
},
|
||||
ppnErrorFn: function(error: Error) {
|
||||
this.isError = true;
|
||||
this.errorMessage = error.toString();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# porcupine-web-vue
|
||||
|
||||
Renderless Vue component for Porcupine for Web.
|
||||
Vue mixin for Porcupine Web.
|
||||
|
||||
## Porcupine
|
||||
|
||||
@@ -12,7 +12,9 @@ applications.
|
||||
|
||||
## Compatibility
|
||||
|
||||
This library is compatible with Vue 3.
|
||||
This library is compatible with Vue:
|
||||
- Vue.js 2.6.11+
|
||||
- Vue.js 3.0.0+
|
||||
|
||||
The Picovoice SDKs for Web are powered by WebAssembly (WASM), the Web Audio API, and Web Workers.
|
||||
|
||||
@@ -41,79 +43,73 @@ To obtain your `AccessKey`:
|
||||
|
||||
## Usage
|
||||
|
||||
Import the Porcupine component and the Porcupine Web Worker component. Bind the worker to Porcupine like the demo `.vue` file below.
|
||||
Import the Porcupine mixin and the Porcupine Web Worker component. When a component adds the `porcupineMixin`, the variable `$porcupine` is computed with the following functions:
|
||||
|
||||
In this example we're passing in two keywords: "Grasshopper" and "Grapefruit" with sensitivities 0.65 and 0.4, respectively. The demo maintains an array of detections which is updated every time the Porcupine `ppn-keyword` event is fired.
|
||||
- `init`: initializes Porcupine.
|
||||
- `start`: starts processing audio and detecting keywords.
|
||||
- `pause`: stops processing audio.
|
||||
- `delete`: cleans up used resources.
|
||||
|
||||
In this example we're passing in two keywords: "Grasshopper" and "Grapefruit" with sensitivities 0.65 and 0.4, respectively. The demo maintains an array of detections which is updated every time the Porcupine detects a keyword.
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div class="voice-widget">
|
||||
<Porcupine
|
||||
v-bind:porcupineFactoryArgs="{
|
||||
accessKey: 'AccessKey obtained from Picovoice Console(https://picovoice.ai/console/)',
|
||||
keywords: [
|
||||
{ builtin: 'Grasshopper', sensitivity: 0.5 },
|
||||
{ builtin: 'Grapefruit', sensitivity: 0.6 },
|
||||
],
|
||||
}"
|
||||
v-bind:porcupineFactory="factory"
|
||||
v-on:ppn-ready="ppnReadyFn"
|
||||
v-on:ppn-keyword="ppnKeywordFn"
|
||||
v-on:ppn-error="ppnErrorFn"
|
||||
/>
|
||||
<h3>Keyword Detections:</h3>
|
||||
<ul v-if="detections.length > 0">
|
||||
<li v-for="(item, index) in detections" :key="index">{{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
```javascript
|
||||
<script>
|
||||
import Porcupine from "@picovoice/porcupine-web-vue";
|
||||
<script lang="ts">
|
||||
import porcupineMixin from "@picovoice/porcupine-web-vue";
|
||||
import { PorcupineWorkerFactoryEn } from "@picovoice/porcupine-web-en-worker";
|
||||
|
||||
export default {
|
||||
name: "VoiceWidget",
|
||||
components: {
|
||||
Porcupine,
|
||||
},
|
||||
mixins: [porcupineMixin],
|
||||
data: function() {
|
||||
return {
|
||||
detections: [],
|
||||
isError: null,
|
||||
detections: [] as string[],
|
||||
isError: false,
|
||||
isLoaded: false,
|
||||
factory: PorcupineWorkerFactoryEn,
|
||||
factoryArgs: {
|
||||
accessKey: '${ACCESS_KEY}', // AccessKey obtained from Picovoice Console(https://picovoice.ai/console/)
|
||||
keywords: [
|
||||
{ builtin: 'Grasshopper', sensitivity: 0.5 },
|
||||
{ builtin: 'Grapefruit', sensitivity: 0.6 },
|
||||
],
|
||||
}
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
await this.$porcupine.init(
|
||||
this.factoryArgs, // Porcupine factory arguments
|
||||
this.factory, // Porcupine Web Worker component
|
||||
this.ppnKeywordFn, // Callback invoked after detection of keyword
|
||||
this.ppnReadyFn, // Callback invoked after loading Porcupine
|
||||
this.ppnErrorFn // Callback invoked in an error occurs while initializing Porcupine
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
start: function () {
|
||||
if (this.$porcupine.start()) {
|
||||
this.isListening = !this.isListening;
|
||||
}
|
||||
},
|
||||
pause: function () {
|
||||
if (this.$porcupine.pause()) {
|
||||
this.isListening = !this.isListening;
|
||||
}
|
||||
},
|
||||
ppnReadyFn: function() {
|
||||
this.isLoaded = true;
|
||||
},
|
||||
ppnKeywordFn: function(data) {
|
||||
ppnKeywordFn: function(data: string) {
|
||||
this.detections = [...this.detections, data.keywordLabel];
|
||||
},
|
||||
ppnErrorFn: function(data) {
|
||||
ppnErrorFn: function(error: Error) {
|
||||
this.isError = true;
|
||||
this.errorMessage = data.toString();
|
||||
this.errorMessage = error.toString();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
The Porcupine component will emit the following events:
|
||||
|
||||
| Event | Data | Description |
|
||||
| ------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| "ppn-loading" | | Porcupine has begun loading |
|
||||
| "ppn-ready" | | Porcupine has finished loading & the user has granted microphone permission: ready to process voice |
|
||||
| "ppn-keyword" | The label of the keyword (e.g. "Grasshopper") | Porcupine has detected a keyword |
|
||||
| "ppn-error" | The error that was caught (e.g. "NotAllowedError: Permission denied") | An error occurred while Porcupine or the WebVoiceProcessor was loading |
|
||||
|
||||
### Custom wake words
|
||||
|
||||
Each language includes a set of built-in keywords. The quickest way to get started is to use one of those. The builtin keywords are licensed under Apache-2.0 and are completely free to use.
|
||||
@@ -122,27 +118,13 @@ Custom wake words are generated using [Picovoice Console](https://picovoice.ai/c
|
||||
|
||||
The `.zip` file contains a `.ppn` file and a `_b64.txt` file which contains the binary model encoded with Base64. Copy the base64 and provide it as an argument to Porcupine as below. You will need to also provide a label so that Porcupine can tell you which keyword occurred ("Deep Sky Blue", in this case):
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div class="voice-widget">
|
||||
<Porcupine
|
||||
v-bind:porcupineFactoryArgs="{
|
||||
accessKey: 'AccessKey obtained from Picovoice Console(https://picovoice.ai/console/)',
|
||||
keywords: [
|
||||
{ base64: '/* Base64 representation of deep_sky_blue.ppn */', custom: 'Deep Sky Blue', sensitivity: 0.65 },
|
||||
],
|
||||
}"
|
||||
v-bind:porcupineFactory="factory"
|
||||
v-on:ppn-ready="ppnReadyFn"
|
||||
v-on:ppn-keyword="ppnKeywordFn"
|
||||
v-on:ppn-error="ppnErrorFn"
|
||||
/>
|
||||
<h3>Keyword Detections:</h3>
|
||||
<ul v-if="detections.length > 0">
|
||||
<li v-for="(item, index) in detections" :key="index">{{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
```typescript
|
||||
factoryArgs: {
|
||||
accessKey: 'AccessKey obtained from Picovoice Console(https://picovoice.ai/console/)',
|
||||
keywords: [
|
||||
{ custom: base64: '/* Base64 representation of deep_sky_blue.ppn */', custom: 'Deep Sky Blue', sensitivity: 0.65 },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
You may wish to store the base64 string in a separate JavaScript file and export it to keep your application code separate.
|
||||
|
||||
@@ -1,11 +1,45 @@
|
||||
{
|
||||
"name": "@picovoice/porcupine-web-vue",
|
||||
"version": "2.0.0",
|
||||
"description": "Vue component (renderless) for Porcupine Web SDK",
|
||||
"entry": "src/index.js",
|
||||
"module": "dist/esm/index.js",
|
||||
"version": "2.0.1",
|
||||
"description": "Vue mixin for Porcupine Web SDK",
|
||||
"author": "Picovoice Inc",
|
||||
"entry": "src/index.ts",
|
||||
"iife": "dist/iife/index.js",
|
||||
"license": "Apache-2.0",
|
||||
"module": "dist/esm/index.js",
|
||||
"types": "dist/types/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "npm-run-all --parallel build:**",
|
||||
"lint": "eslint . --ext .js,.ts",
|
||||
"build:all": "rollup --config",
|
||||
"build:types": "tsc --declaration --declarationMap --emitDeclarationOnly --outDir ./dist/types",
|
||||
"format": "prettier --write \"**/*.{js,ts,json}\"",
|
||||
"prepack": "npm-run-all build",
|
||||
"start": "cross-env TARGET='debug' rollup --config --watch",
|
||||
"watch": "rollup --config --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.13",
|
||||
"@babel/plugin-transform-runtime": "^7.12.15",
|
||||
"@babel/preset-env": "^7.12.13",
|
||||
"@babel/runtime": "^7.12.13",
|
||||
"@picovoice/web-voice-processor": "^2.1.2",
|
||||
"@rollup/plugin-babel": "^5.2.3",
|
||||
"@rollup/plugin-commonjs": "^17.1.0",
|
||||
"@rollup/plugin-node-resolve": "^11.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^7.19.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.2.1",
|
||||
"rollup": "^2.38.5",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.29.0",
|
||||
"rollup-plugin-web-worker-loader": "^1.6.0",
|
||||
"typescript": "~4.1.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@picovoice/web-voice-processor": "^2.1.2",
|
||||
"vue": "^2.6.11 || ^3.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"porcupine",
|
||||
"web",
|
||||
@@ -21,36 +55,5 @@
|
||||
"vue",
|
||||
"renderless"
|
||||
],
|
||||
"author": "Picovoice Inc",
|
||||
"scripts": {
|
||||
"build:all": "rollup --config",
|
||||
"build": "npm-run-all --parallel build:**",
|
||||
"lint": "eslint . --ext .js,.ts",
|
||||
"prepack": "npm-run-all build",
|
||||
"start": "cross-env TARGET='debug' rollup --config --watch",
|
||||
"watch": "rollup --config --watch",
|
||||
"format": "prettier --write \"**/*.{js,ts,json}\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.13",
|
||||
"@babel/plugin-transform-runtime": "^7.12.15",
|
||||
"@babel/preset-env": "^7.12.13",
|
||||
"@babel/runtime": "^7.12.13",
|
||||
"@picovoice/web-voice-processor": "^2.1.2",
|
||||
"@rollup/plugin-babel": "^5.2.3",
|
||||
"@rollup/plugin-commonjs": "^17.1.0",
|
||||
"@rollup/plugin-node-resolve": "^11.1.1",
|
||||
"@vue/compiler-sfc": "^3.0.7",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^7.19.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.2.1",
|
||||
"rollup": "^2.38.5",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-vue": "^6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0",
|
||||
"@picovoice/web-voice-processor": "^2.1.2"
|
||||
}
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
const path = require('path');
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const typescript = require('rollup-plugin-typescript2');
|
||||
const workerLoader = require('rollup-plugin-web-worker-loader');
|
||||
const pkg = require('./package.json');
|
||||
const { babel } = require('@rollup/plugin-babel');
|
||||
const terser = require('rollup-plugin-terser').terser;
|
||||
const vue = require('rollup-plugin-vue');
|
||||
const { DEFAULT_EXTENSIONS } = require('@babel/core');
|
||||
|
||||
const extensions = DEFAULT_EXTENSIONS;
|
||||
const extensions = [...DEFAULT_EXTENSIONS, '.ts'];
|
||||
|
||||
console.log(process.env.TARGET);
|
||||
console.log(extensions);
|
||||
@@ -26,7 +27,6 @@ console.log(iifeBundleName);
|
||||
|
||||
export default {
|
||||
input: [path.resolve(__dirname, pkg.entry)],
|
||||
external: ['vue'],
|
||||
output: [
|
||||
{
|
||||
file: path.resolve(__dirname, pkg['module']),
|
||||
@@ -60,9 +60,14 @@ export default {
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
vue(),
|
||||
nodeResolve({ extensions }),
|
||||
commonjs(),
|
||||
workerLoader({ targetPlatform: 'browser', sourcemap: false }),
|
||||
typescript({
|
||||
typescript: require('typescript'),
|
||||
cacheRoot: path.resolve(__dirname, '.rts2_cache'),
|
||||
clean: true,
|
||||
}),
|
||||
babel({
|
||||
extensions: extensions,
|
||||
babelHelpers: 'runtime',
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { WebVoiceProcessor } from '@picovoice/web-voice-processor';
|
||||
|
||||
/**
|
||||
* Porcupine Vue Component.
|
||||
*
|
||||
* Props
|
||||
* - porcupineFactoryArgs: Arguments for PorcupineWorkerFactory.
|
||||
* - PorcupineWorkerFactory: The language-specific worker factory.
|
||||
*
|
||||
* Events
|
||||
* - ppn-ready: A method invoked after component has initialized.
|
||||
* - ppn-keyword: A method invoked upon detection of the keywords.
|
||||
* - ppn-error: A method invoked if an error occurs within `PorcupineWorkerFactory`.
|
||||
*/
|
||||
export default {
|
||||
name: 'Porcupine',
|
||||
props: {
|
||||
porcupineFactoryArgs: [Object],
|
||||
porcupineFactory: [Function],
|
||||
},
|
||||
data: function () {
|
||||
return { webVp: null, ppnWorker: null };
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Method to start processing audio.
|
||||
*/
|
||||
start() {
|
||||
if (this.webVp !== null) {
|
||||
this.webVp.start();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Method to stop processing audio.
|
||||
*/
|
||||
pause() {
|
||||
if (this.webVp !== null) {
|
||||
this.webVp.pause();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
keywordCallback(label) {
|
||||
this.$emit('ppn-keyword', label);
|
||||
},
|
||||
errorCallback(error) {
|
||||
this.$emit('ppn-error', error);
|
||||
},
|
||||
/**
|
||||
* Method to initialize PorcupineWorker.
|
||||
*/
|
||||
async initEngine() {
|
||||
try {
|
||||
const { accessKey, keywords } = this.porcupineFactoryArgs;
|
||||
this.ppnWorker = await this.porcupineFactory.create(
|
||||
accessKey,
|
||||
JSON.parse(JSON.stringify(keywords)),
|
||||
this.keywordCallback,
|
||||
this.errorCallback
|
||||
);
|
||||
this.webVp = await WebVoiceProcessor.init({
|
||||
engines: [this.ppnWorker],
|
||||
});
|
||||
this.$emit('ppn-ready');
|
||||
} catch (error) {
|
||||
this.$emit('ppn-error', error);
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeUnmount: function () {
|
||||
if (this.webVp !== null) {
|
||||
this.webVp.release();
|
||||
}
|
||||
if (this.ppnWorker !== null) {
|
||||
this.ppnWorker.postMessage({ command: 'release' });
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,28 +0,0 @@
|
||||
// Import vue component
|
||||
import component from './Porcupine.vue';
|
||||
|
||||
// Declare install function executed by Vue.use()
|
||||
export function install(Vue) {
|
||||
if (install.installed) return;
|
||||
install.installed = true;
|
||||
Vue.component('Porcupine', component);
|
||||
}
|
||||
|
||||
// Create module definition for Vue.use()
|
||||
const plugin = {
|
||||
install,
|
||||
};
|
||||
|
||||
// Auto-install when vue is found (eg. in browser via <script> tag)
|
||||
let GlobalVue = null;
|
||||
if (typeof window !== 'undefined') {
|
||||
GlobalVue = window.Vue;
|
||||
} else if (typeof global !== 'undefined') {
|
||||
GlobalVue = global.Vue;
|
||||
}
|
||||
if (GlobalVue) {
|
||||
GlobalVue.use(plugin);
|
||||
}
|
||||
|
||||
// To allow use as module (npm/webpack/etc.) export component
|
||||
export default component;
|
||||
44
binding/vue/src/index.ts
Normal file
44
binding/vue/src/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import porcupineMixin from './porcupine';
|
||||
|
||||
import {
|
||||
PorcupineKeyword,
|
||||
PorcupineKeywordCustom,
|
||||
PorcupineKeywordBuiltin,
|
||||
PorcupineWorkerFactoryArgs,
|
||||
PorcupineWorkerFactory,
|
||||
PorcupineVue
|
||||
} from './porcupine_types';
|
||||
|
||||
// Create module definition for Vue.use()
|
||||
const plugin = {
|
||||
install: function(Vue: any) {
|
||||
Vue.mixin(porcupineMixin);
|
||||
}
|
||||
};
|
||||
|
||||
// // Auto-install when vue is found (eg. in browser via <script> tag)
|
||||
let GlobalVue = null;
|
||||
if (typeof window !== 'undefined') {
|
||||
// @ts-ignore
|
||||
GlobalVue = window.Vue;
|
||||
// @ts-ignore
|
||||
} else if (typeof global !== 'undefined') {
|
||||
// @ts-ignore
|
||||
GlobalVue = global.Vue;
|
||||
}
|
||||
if (GlobalVue) {
|
||||
GlobalVue.use(plugin);
|
||||
}
|
||||
|
||||
// To allow use as module (npm/webpack/etc.) export component
|
||||
export default porcupineMixin;
|
||||
|
||||
// export types
|
||||
export {
|
||||
PorcupineKeyword,
|
||||
PorcupineKeywordCustom,
|
||||
PorcupineKeywordBuiltin,
|
||||
PorcupineWorkerFactoryArgs,
|
||||
PorcupineWorkerFactory,
|
||||
PorcupineVue
|
||||
};
|
||||
83
binding/vue/src/porcupine.ts
Normal file
83
binding/vue/src/porcupine.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { WebVoiceProcessor } from '@picovoice/web-voice-processor';
|
||||
import { PorcupineVue } from './porcupine_types';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
/**
|
||||
* Porcupine Vue Mixin.
|
||||
*/
|
||||
$porcupine(): PorcupineVue {
|
||||
return {
|
||||
$_ppnWorker_: null as Worker | null,
|
||||
$_webVp_: null as WebVoiceProcessor | null,
|
||||
/**
|
||||
* Init function for Porcupine.
|
||||
*
|
||||
* @param porcupineFactoryArgs Arguments for PorcupineWorkerFactory.
|
||||
* @param porcupineFactory The language-specific worker factory
|
||||
* @param keywordCallback A method invoked upon detection of the keywords.
|
||||
* @param readyCallback A method invoked after component has initialized.
|
||||
* @param errorCallback A method invoked if an error occurs within `PorcupineWorkerFactory`.
|
||||
*/
|
||||
async init(
|
||||
porcupineFactoryArgs,
|
||||
porcupineFactory,
|
||||
keywordCallback = (_: string) => {},
|
||||
readyCallback = () => {},
|
||||
errorCallback = (error: Error) => {console.error(error)}
|
||||
) {
|
||||
try {
|
||||
const { accessKey, keywords } = porcupineFactoryArgs;
|
||||
this.$_ppnWorker_ = await porcupineFactory.create(
|
||||
accessKey,
|
||||
JSON.parse(JSON.stringify(keywords)),
|
||||
keywordCallback,
|
||||
errorCallback
|
||||
);
|
||||
this.$_webVp_ = await WebVoiceProcessor.init({
|
||||
engines: [this.$_ppnWorker_!],
|
||||
});
|
||||
readyCallback();
|
||||
} catch (error) {
|
||||
errorCallback(error as Error);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Start processing audio.
|
||||
*/
|
||||
start() {
|
||||
if (this.$_webVp_ !== null) {
|
||||
this.$_webVp_.start();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Stop processing audio.
|
||||
*/
|
||||
pause() {
|
||||
if (this.$_webVp_ !== null) {
|
||||
this.$_webVp_.pause();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Delete used resources.
|
||||
*/
|
||||
delete() {
|
||||
this.$_webVp_?.release();
|
||||
this.$_ppnWorker_?.postMessage({ command: 'release' });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Vue 3 method to clean resources.
|
||||
beforeUnmount(this: any) {
|
||||
this.$porcupine.delete();
|
||||
},
|
||||
// Vue 2 method to clean resources.
|
||||
beforeDestory(this: any) {
|
||||
this.$porcupine.delete();
|
||||
}
|
||||
};
|
||||
64
binding/vue/src/porcupine_types.ts
Normal file
64
binding/vue/src/porcupine_types.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { WebVoiceProcessor } from '@picovoice/web-voice-processor';
|
||||
|
||||
/**
|
||||
* Type alias for the Porcupine keywords.
|
||||
*/
|
||||
export type PorcupineKeyword = PorcupineKeywordCustom | PorcupineKeywordBuiltin;
|
||||
|
||||
/**
|
||||
* Type alias for builtin keywords.
|
||||
*/
|
||||
export type PorcupineKeywordBuiltin = {
|
||||
builtin: string;
|
||||
sensitivity?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type alias for custom keywords.
|
||||
*/
|
||||
export type PorcupineKeywordCustom = {
|
||||
base64: string;
|
||||
custom: string;
|
||||
sensitivity?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type alias for PorcupineWorkerFactory arguments.
|
||||
*/
|
||||
export type PorcupineWorkerFactoryArgs = {
|
||||
accessKey: string;
|
||||
keywords: Array<PorcupineKeyword | string> | PorcupineKeyword | string;
|
||||
start?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* The language-specific worker factory, imported as { PorcupineWorkerFactory } from the
|
||||
* @picovoice/porcupine-web-xx-worker series of packages, where xx is the two-letter language code.
|
||||
*/
|
||||
export interface PorcupineWorkerFactory extends Object {
|
||||
create: (
|
||||
accessKey: string,
|
||||
keywords: Array<PorcupineKeyword | string> | PorcupineKeyword | string,
|
||||
keywordDetectionCallback?: CallableFunction,
|
||||
processErrorCallback?: CallableFunction,
|
||||
start?: boolean) => Promise<Worker>,
|
||||
};
|
||||
|
||||
/**
|
||||
* Type alias for Porcupine Vue Mixin.
|
||||
* Use with `Vue as VueConstructor extends {$porcupine: PorcupineVue}` to get types in typescript.
|
||||
*/
|
||||
export interface PorcupineVue {
|
||||
$_ppnWorker_: Worker | null;
|
||||
$_webVp_: WebVoiceProcessor | null;
|
||||
init: (
|
||||
porcupineFactoryArgs: PorcupineWorkerFactoryArgs,
|
||||
porcupineFactory: PorcupineWorkerFactory,
|
||||
keywordCallback: (label: string) => void,
|
||||
readyCallback: () => void,
|
||||
errorCallback: (error: Error) => void) => void;
|
||||
start: () => boolean;
|
||||
pause: () => boolean;
|
||||
delete: () => void;
|
||||
}
|
||||
|
||||
37
binding/vue/tsconfig.json
Normal file
37
binding/vue/tsconfig.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
# porcupine-web-vue-demo
|
||||
|
||||
This demo application includes a sample `VoiceWidget` Vue component which uses the `Porcupine` renderless Vue component service to allow listening for keywords. Porcupine keyword detections are handled via the `ppn-keyword` event. Our VoiceWidget subscribes to this event and displays the results.
|
||||
This demo application includes a sample `VoiceWidget` Vue component which uses the `porcupineMixin` Vue mixin which integrates components to allow listening for keywords. Porcupine keyword detections are handled via the `keywordCallback` function.
|
||||
|
||||
The demo uses dynamic imports to split the VoiceWidget away from the main application bundle. This means that the initial download size of the Vue app will not be impacted by the ~1-2 MB requirement of Porcupine. While small for all-in-one offline Voice AI, the size is large for an initial web app load.
|
||||
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@picovoice/porcupine-web-en-worker": "^2.0.1",
|
||||
"@picovoice/porcupine-web-vue": "^2.0.0",
|
||||
"@picovoice/porcupine-web-vue": "^2.0.1",
|
||||
"@picovoice/web-voice-processor": "^2.1.2",
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^3.0.0"
|
||||
"vue": "^2.6.11",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
@@ -24,7 +25,6 @@
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"eslint": "^6.7.2",
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
<template>
|
||||
<h1>Porcupine Web + Vue ("Porcupine" Renderless Component)</h1>
|
||||
<button v-on:click="toggle">
|
||||
Toggle VoiceWidget <span v-if="show">"OFF"</span><span v-else>"ON"</span>
|
||||
</button>
|
||||
<br />
|
||||
<br />
|
||||
<VoiceWidget v-if="show" />
|
||||
<div>
|
||||
<h1>Porcupine Web + Vue ("Porcupine" Renderless Component)</h1>
|
||||
<button v-on:click="toggle">
|
||||
Toggle VoiceWidget <span v-if="show">"OFF"</span><span v-else>"ON"</span>
|
||||
</button>
|
||||
<br />
|
||||
<br />
|
||||
<VoiceWidget v-if="show" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent } from "vue";
|
||||
import VoiceWidget from "./components/VoiceWidget.vue";
|
||||
import Vue from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
export default Vue.extend({
|
||||
name: "App",
|
||||
components: {
|
||||
VoiceWidget: defineAsyncComponent(
|
||||
() => import("./components/VoiceWidget.vue")
|
||||
VoiceWidget: Vue.component(
|
||||
'VoiceWidget',
|
||||
async () => await import("./components/VoiceWidget.vue")
|
||||
),
|
||||
},
|
||||
data: function () {
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
<template>
|
||||
<div class="voice-widget">
|
||||
<Porcupine
|
||||
ref="porcupine"
|
||||
v-bind:porcupineFactoryArgs="factoryArgs"
|
||||
v-bind:porcupineFactory="factory"
|
||||
v-on:ppn-ready="ppnReadyFn"
|
||||
v-on:ppn-keyword="ppnKeywordFn"
|
||||
v-on:ppn-error="ppnErrorFn"
|
||||
/>
|
||||
<h2>VoiceWidget</h2>
|
||||
<h3>
|
||||
<label>
|
||||
@@ -42,19 +34,20 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Porcupine from "@picovoice/porcupine-web-vue";
|
||||
import { PorcupineWorkerFactory as PorcupineWorkerFactoryEn } from "@picovoice/porcupine-web-en-worker";
|
||||
<script lang="ts">
|
||||
import Vue, { VueConstructor } from 'vue';
|
||||
|
||||
export default {
|
||||
import { PorcupineWorkerFactory as PorcupineWorkerFactoryEn } from "@picovoice/porcupine-web-en-worker";
|
||||
import porcupineMixin, { PorcupineVue } from "@picovoice/porcupine-web-vue";
|
||||
|
||||
const VoiceWidget = (Vue as VueConstructor<Vue & {$porcupine: PorcupineVue}>).extend({
|
||||
name: "VoiceWidget",
|
||||
components: {
|
||||
Porcupine,
|
||||
},
|
||||
data: function () {
|
||||
mixins: [porcupineMixin],
|
||||
data() {
|
||||
return {
|
||||
detections: [],
|
||||
detections: [] as string[],
|
||||
isError: false,
|
||||
errorMessage: null as string | null,
|
||||
isLoaded: false,
|
||||
isListening: false,
|
||||
factory: PorcupineWorkerFactoryEn,
|
||||
@@ -69,36 +62,44 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
start: function () {
|
||||
if (this.$refs.porcupine.start()) {
|
||||
if (this.$porcupine.start()) {
|
||||
this.isListening = !this.isListening;
|
||||
}
|
||||
},
|
||||
pause: function () {
|
||||
if (this.$refs.porcupine.pause()) {
|
||||
if (this.$porcupine.pause()) {
|
||||
this.isListening = !this.isListening;
|
||||
}
|
||||
},
|
||||
initEngine: function (event) {
|
||||
initEngine: function (event: any) {
|
||||
this.factoryArgs.accessKey = event.target.value;
|
||||
this.isError = false;
|
||||
this.isLoaded = false;
|
||||
this.isListening = false;
|
||||
this.$refs.porcupine.initEngine();
|
||||
},
|
||||
this.$porcupine.init(
|
||||
this.factoryArgs,
|
||||
this.factory,
|
||||
this.ppnKeywordFn,
|
||||
this.ppnReadyFn,
|
||||
this.ppnErrorFn
|
||||
);
|
||||
},
|
||||
ppnReadyFn: function () {
|
||||
this.isLoaded = true;
|
||||
this.isListening = true;
|
||||
},
|
||||
ppnKeywordFn: function (keywordLabel) {
|
||||
ppnKeywordFn: function (keywordLabel: string) {
|
||||
console.log(keywordLabel);
|
||||
this.detections = [...this.detections, keywordLabel];
|
||||
},
|
||||
ppnErrorFn: function (error) {
|
||||
ppnErrorFn: function (error: Error) {
|
||||
this.isError = true;
|
||||
this.errorMessage = error.toString();
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default VoiceWidget;
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { createApp } from "vue";
|
||||
import Vue from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
const porcupineDemoApp = createApp(App)
|
||||
porcupineDemoApp.component('VoiceWidget',
|
||||
() => import('./components/VoiceWidget.vue')
|
||||
)
|
||||
porcupineDemoApp.mount("#app");
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
}).$mount('#app');
|
||||
|
||||
3170
demo/vue/yarn.lock
3170
demo/vue/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user