visit
- Node.js 8.10+
- npm 5.2+
npx create-react-app speechly-voice-filter --typescript
cd speechly-voice-filter
npm i
ts
export type Repository = {
name: string;
description: string;
language: string;
followers: number;
stars: number;
forks: number;
};
export const repositories: Repository[] = [
{
name: "microsoft/typescript",
description:
"TypeScript is a superset of JavaScript that compiles to clean JavaScript output",
language: "TypeScript",
followers: 2200,
stars: 65000,
forks: 8700,
},
{
name: "nestjs/nest",
description:
"A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications on top of TypeScript & JavaScript (ES6, ES7, ES8)",
language: "TypeScript",
followers: 648,
stars: 30900,
forks: 2800,
},
{
name: "microsoft/vscode",
description: "Visual Studio Code",
language: "TypeScript",
followers: 3000,
stars: 105000,
forks: 16700,
},
{
name: "denoland/deno",
description: "A secure JavaScript and TypeScript runtime",
language: "TypeScript",
followers: 1700,
stars: 68000,
forks: 3500,
},
{
name: "kubernetes/kubernetes",
description: "Production-Grade Container Scheduling and Management",
language: "Go",
followers: 3300,
stars: 70700,
forks: 25500,
},
{
name: "moby/moby",
description:
"Moby Project - a collaborative project for the container ecosystem to assemble container-based systems",
language: "Go",
followers: 3200,
stars: 58600,
forks: 16900,
},
{
name: "gohugoio/hugo",
description: "The world’s fastest framework for building websites",
language: "Go",
followers: 1000,
stars: 47200,
forks: 5400,
},
{
name: "grafana/grafana",
description:
"The tool for beautiful monitoring and metric analytics & dashboards for Graphite, InfluxDB & Prometheus & More",
language: "Go",
followers: 1300,
stars: 37500,
forks: 7600,
},
{
name: "pytorch/pytorch",
description:
"Tensors and Dynamic neural networks in Python with strong GPU acceleration",
language: "Python",
followers: 1600,
stars: 43000,
forks: 11200,
},
{
name: "tensorflow/tensorflow",
description: "An Open Source Machine Learning Framework for Everyone",
language: "Python",
followers: 8300,
stars: 149000,
forks: 82900,
},
{
name: "django/django",
description: "The Web framework for perfectionists with deadlines",
language: "Python",
followers: 2300,
stars: 52800,
forks: 22800,
},
{
name: "apache/airflow",
description:
"Apache Airflow - A platform to programmatically author, schedule, and monitor workflows",
language: "Python",
followers: 716,
stars: 18500,
forks: 7200,
},
];
```
We can display this data in a simple table, so let's add a component for that under `src/RepoList.tsx`:
```tsx
import React from "react";
import { Repository } from "./data";
type Props = {
repos: Repository[];
};
export const RepoList = ({ repos }: Props): JSX.Element => {
return (
<div className="block">
<table>
<thead>
<tr>
<th>Name</th>
<th>Language</th>
<th>Description</th>
<th>Followers</th>
<th>Stars</th>
<th>Forks</th>
</tr>
</thead>
<tbody>
{repos.map((repo) => (
<RepoRow repo={repo} key={repo.name} />
))}
</tbody>
</table>
</div>
);
};
const RepoRow = React.memo(
({ repo }: { repo: Repository }): JSX.Element => {
return (
<tr>
<td>{repo.name}</td>
<td>{repo.language}</td>
<td>{repo.description}</td>
<td>{repo.followers}</td>
<td>{repo.stars}</td>
<td>{repo.forks}</td>
</tr>
);
}
);
import React from "react";
import { repositories } from "./data";
import { RepoList } from "./RepoList";
export const SpeechApp: React.FC = (): JSX.Element => {
return (
<div>
<RepoList repos={repositories} />
</div>
);
};
```
Now let's add it to our top-level component:
```tsx
import React from "react";
import { SpeechProvider } from "@speechly/react-client";
import "./App.css";
import { SpeechApp } from "./SpeechApp";
function App(): JSX.Element {
return (
<div className="App">
<SpeechApp />
</div>
);
}
export default App;
import React from "react";
import {
Word as SpeechWord,
SpeechSegment,
SpeechState,
} from "@speechly/react-client";
type Props = {
segment?: SpeechSegment;
state: SpeechState;
onRecord: () => Promise<void>;
};
export const Microphone = React.memo(
({ state, segment, onRecord }: Props): JSX.Element => {
let enabled = false;
let text = "Error";
switch (state) {
case SpeechState.Idle:
case SpeechState.Ready:
enabled = true;
text = "Start";
break;
case SpeechState.Recording:
enabled = true;
text = "Stop";
break;
case SpeechState.Connecting:
case SpeechState.Loading:
enabled = false;
text = "Loading...";
break;
}
return (
<div>
<button onClick={onRecord} disabled={!enabled}>
{text}
</button>
<Transcript segment={segment} />
</div>
);
}
);
const Transcript = React.memo(
({ segment }: { segment?: SpeechSegment }): JSX.Element => {
if (segment === undefined) {
return (
<div>
<em>Waiting for speech input...</em>
</div>
);
}
return (
<div>
{segment.words.map((w) => (
<Word word={w} key={w.index} />
))}
</div>
);
}
);
const Word = React.memo(
({ word }: { word: SpeechWord }): JSX.Element => {
if (word.isFinal) {
return <strong>{`${word.value} `}</strong>;
}
return <span>{`${word.value} `}</span>;
}
);
import React from "react";
import { useSpeechContext } from "@speechly/react-client";
import { repositories } from "./data";
import { RepoList } from "./RepoList";
import { Microphone } from "./Microphone";
export const SpeechApp: React.FC = (): JSX.Element => {
const { toggleRecording, speechState, segment } = useSpeechContext();
return (
<div>
<Microphone
segment={segment}
state={speechState}
onRecord={toggleRecording}
/>
<RepoList repos={repositories} />
</div>
);
};
# Which languages we can filter by
languages = [
Go
TypeScript
Python
]
# Which fields we can sort by
sort_fields = [
name
description
language
followers
stars
forks
]
# Synonyms for "repo"
results = [
items
results
repos
repositories
]
# A couple of commands for filtering.
#
# This will expand into e.g. following examples (not exhaustive):
# "Show all Go repos"
# "Show me only TypeScript repositories"
# "Show Python results"
# etc.
#
# Words in curly brackets ("{me}") are optional.
# Square brackets are for lists (e.g. one option from the list may be used)
*filter show {me} {[all | only]} $languages(language) {$results}
*filter filter {$results} by $languages(language) {language}
# A command for sorting, e.g.:
# "Sort the repos by name"
# "Order results by forks"
# etc.
*sort [sort | order] {the} {$results} by $sort_fields(sort_field)
# A command for resetting the filters, e.g.:
# "Reset all filters to default"
# "Remove the filters"
# "Reset to default"
# etc.
*reset [reset | remove] {[the | all]} {filters} {to default}
ts
import { SpeechSegment } from "@speechly/react-client";
export enum IntentType {
Unknown = "unknown",
Sort = "sort",
Filter = "filter",
Reset = "reset",
}
export enum EntityType {
Language = "language",
SortField = "sort_field",
}
export enum SortEntityType {
Unknown = "unknown",
Name = "name",
Description = "description",
Language = "language",
Followers = "followers",
Stars = "stars",
Forks = "forks",
}
const SpeechIntentValues = Object.values(IntentType) as string[];
const SortTypeValues = Object.values(SortEntityType) as string[];
export function parseIntent(segment: SpeechSegment): IntentType {
const { intent } = segment;
if (SpeechIntentValues.includes(intent.intent)) {
return intent.intent as IntentType;
}
return IntentType.Unknown;
}
export function parseLanguageEntity(segment: SpeechSegment): string[] {
const langs: string[] = [];
for (const e of segment.entities) {
if (e.type === EntityType.Language) {
langs.push(e.value.toLowerCase());
}
}
return langs;
}
export function parseSortEntity(segment: SpeechSegment): SortEntityType {
let s = SortEntityType.Unknown;
for (const e of segment.entities) {
const val = e.value.toLowerCase();
if (e.type === EntityType.SortField && SortTypeValues.includes(val)) {
s = val as SortEntityType;
}
}
return s;
}
import React, { useEffect } from "react";
import { SpeechSegment, useSpeechContext } from "@speechly/react-client";
import { repositories } from "./data";
import {
IntentType,
SortEntityType,
parseIntent,
parseLanguageEntity,
parseSortEntity,
} from "./parser";
import { RepoList } from "./RepoList";
import { Microphone } from "./Microphone";
export const SpeechApp: React.FC = (): JSX.Element => {
const { toggleRecording, speechState, segment } = useSpeechContext();
useEffect(() => {
if (segment === undefined) {
return;
}
parseSegment(segment);
}, [segment]);
return (
<div>
<Microphone
segment={segment}
state={speechState}
onRecord={toggleRecording}
/>
<RepoList repos={repositories} />
</div>
);
};
function parseSegment(segment: SpeechSegment) {
const intent = parseIntent(segment);
switch (intent) {
case IntentType.Filter:
const languages = parseLanguageEntity(segment);
console.log("Filtering by languages", languages);
break;
case IntentType.Sort:
const sortBy = parseSortEntity(segment);
if (sortBy !== SortEntityType.Unknown) {
console.log("Sorting by field", sortBy);
}
break;
case IntentType.Reset:
console.log("Resetting the filters");
break;
}
}
tsx
import React, { useEffect, useState } from "react";
import { SpeechSegment, useSpeechContext } from "@speechly/react-client";
import { repositories, Repository } from "./data";
import { Filter, filterRepos } from "./filter";
import {
IntentType,
SortEntityType,
parseIntent,
parseLanguageEntity,
parseSortEntity,
} from "./parser";
import { RepoList } from "./RepoList";
import { Microphone } from "./Microphone";
export const SpeechApp: React.FC = (): JSX.Element => {
const [filter, setFilter] = useState<Filter>(defaultFilter);
const [repos, setRepos] = useState<Repository[]>(repositories);
const { toggleRecording, speechState, segment } = useSpeechContext();
useEffect(() => {
if (segment === undefined) {
return;
}
const nextFilter = {
...filter,
...parseSegment(segment),
};
setFilter(nextFilter);
setRepos(filterRepos(repositories, nextFilter));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [segment]);
return (
<div>
<Microphone
segment={segment}
state={speechState}
onRecord={toggleRecording}
/>
<RepoList repos={repos} />
</div>
);
};
const emptyFilter: Filter = {};
const defaultFilter: Filter = {
languages: [],
sortBy: SortEntityType.Name,
};
function parseSegment(segment: SpeechSegment): Filter {
const intent = parseIntent(segment);
switch (intent) {
case IntentType.Filter:
const languages = parseLanguageEntity(segment);
if (languages.length === 0) {
return emptyFilter;
}
return {
languages,
};
case IntentType.Sort:
const sortBy = parseSortEntity(segment);
if (sortBy !== SortEntityType.Unknown) {
return {
sortBy,
};
}
return emptyFilter;
case IntentType.Reset:
return defaultFilter;
default:
return emptyFilter;
}
}
Also published