Skip to content

Instantly share code, notes, and snippets.

@Davenchy
Last active June 11, 2019 02:11
Show Gist options
  • Save Davenchy/93c3bbbff5e89f5bc5a10ad09386efe9 to your computer and use it in GitHub Desktop.
Save Davenchy/93c3bbbff5e89f5bc5a10ad09386efe9 to your computer and use it in GitHub Desktop.
Javascript Simple AI

AI.js Docs

AI Properties

locale // like variables

models // models added by the train method

recognition // browser 'SpeechRecognition' object

isListening // is the AI listening

tts // browser SpeechSynthesisUtterance object

AI Methods

start() // start the AI

stop() // stop the AI

say(__text__ : string) // text to speech

match(__text__ : string, __list__ : array[string]) // match text with array of text

parseLocale(__text__) // parse text and replace any {KEY} by its value from locale property

random(__list__ : array[string]) // select randomly text from inside the array

train(__test__ : string | array[string] | function return string, __action__ : string | array[string] | function return string)
// train new model, you can add {KEY} example "Welcome, {username}. My name is {myname}"
// the test and the action functions have 'test' argument
// text: It is the recognized text
/*\
|*|-------------------------|
|*| AI.js |
|*| Created by Davenchy |
|*|-------------------------|
\*/
const AI = (function () {
function AI() {
this.locale = {};
this.models = [];
this.recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
this.recognition.onstart = onStart.bind(this);
this.recognition.onend = onEnd.bind(this);
this.recognition.onresult = onResult.bind(this);
this.recognition.continuous = true;
this.isListening = false;
this.tts = new SpeechSynthesisUtterance();
}
// start the speech recognition
AI.prototype.start = function () {
this.recognition.start();
}
// stop the speech recognition
AI.prototype.stop = function () {
this.recognition.stop();
}
// train the AI
AI.prototype.train = function (test, action) {
let a = test, b = action;
if (!a) throw new Error('test is not defined');
else if (typeof test === 'string') a = text => this.match(text, test.split('\n'));
else if (Array.isArray(test)) a = text => this.match(text, test);
if (!b) throw new Error('action is not defined');
else if (typeof action === 'string') b = () => this.random(action.split('\n'));
else if (Array.isArray(action)) b = () => this.random(action);
this.models.push({ test: a, action: b });
}
// on speech recognition start event
function onStart() {
this.isListening = true;
}
// on speech recognition end event
function onEnd() {
this.isListening = false;
}
// on speech recognition result event
function onResult(event) {
const i = event.results.length - 1;
const text = event.results[i][0].transcript.trim();
const models = this.models.map(m => ({
test: m.test(text),
module: m
}));
const selected = models.reduce((a, b) => a.test.average > b.test.average ? a : b);
let answer = selected.module.action(text);
if (Array.isArray(answer)) answer = this.random(answer);
this.say(this.parseLocale(answer));
}
// say something using tts engine
AI.prototype.say = function (text) {
this.tts.text = text;
speechSynthesis.speak(this.tts);
console.log('tts:', text);
}
// match text with list of texts and return { average, match }
AI.prototype.match = function (text, list) {
let ratios = list.map(l => matchRatio(this.parseLocale(l), text));
let match = ratios.filter(l => l > 0).length;
let average = ratios.reduce((a, b) => a + b) / list.length;
return {
average, match
};
};
// parse text and replace locales keys with its values
AI.prototype.parseLocale = function (text) {
let i = 0;
let open = false;
let name = '';
let newText = '';
for(let c of text) {
if (c === '{') {
if (open) throw new Error(`expected '}' @${i} -> "${text}"`);
else open = true;
} else if (c === '}') {
if (!open) throw new Error(`expected '{' @${i} -> "${text}"`);
else {
open = false;
if (name === '') throw new Error(`expected locale name inside the bracket @${i} -> "${text}"`);
else {
let value = this.locale[name];
if (!value) throw new Error(`undefined locale name '${name}' @${i} -> "${text}"`);
newText += value;
name = '';
}
}
} else {
if (open) name += c;
else newText += c;
}
i++;
}
if (open) throw new Error(`expected '}' at the end -> "${text}"`)
return newText;
}
// select random text from the list
AI.prototype.random = function (list) {
const i = Math.round(Math.random() * (list.length - 1));
return list[i];
}
// the ratio between the matching words in a and b
function matchRatio(a, b) {
a = a.trim().toLowerCase().split(' ');
b = b.trim().toLowerCase().split(' ');
let match = 0;
for(let i of a)
for (let j of b)
if (i === j)
match++;
return match / a.length;
}
return AI;
})();
const ai = new AI();
ai.locale.name = 'max';
ai.locale.user = 'bazoka';
ai.train(
['hey {name}', 'daddy is in home'],
['hey, {user}!!!', 'welcome, sir!', 'welcome, {user}', 'Yes, Sir!']
);
/* the same as above
// text: It is the recognized text
ai.train(
(text) => ai.match(text, ['hey {name}', 'daddy is in home']),
['hey, {user}!!!', 'welcome, sir!', 'welcome, {user}', 'Yes, Sir!']
);
*/
ai.train('what time is it now', text => {
const time = new Date();
const h = time.getHours();
const m = time.getMinutes();
let str = `${h}`;
if (m === 0) str += " o'clock";
else str += ` and ${m} minutes`;
if (h > 12) str += ', PM';
else str += ', AM';
ai.locale.time = str;
return ['It is {time}', "It's {time}", '{time}', 'The time is {time}'];
// return ai.random(['It is {time}', "It's {time}", '{time}', 'The time is {time}']); // the same
// return "It is {time}\nIt's {time}\n{time}\nThe time is {time}" // the same
});
ai.start();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment