Compare commits
14 Commits
master
...
transforms
Author | SHA1 | Date | |
---|---|---|---|
|
158b287116 | ||
|
fd8686b305 | ||
|
5a1d655ce8 | ||
|
78708856eb | ||
|
230a48e409 | ||
|
fd7accb887 | ||
|
f0c040b533 | ||
|
839753a636 | ||
|
a134e6ed60 | ||
|
0112fcd205 | ||
|
cb5a2befea | ||
|
19b309a29e | ||
|
e357ccea01 | ||
|
97a0accfd1 |
@ -4,7 +4,7 @@ A pure JavaScript implementation of the ITA project's CEStore - called CENode. C
|
|||||||
|
|
||||||
Please visit the project's [home page](http://cenode.io) for more information and for documentation.
|
Please visit the project's [home page](http://cenode.io) for more information and for documentation.
|
||||||
|
|
||||||
**We recommend beginners check out the [Getting Started Guide](https://github.com/willwebberley/CENode/wiki/Getting-Started-Guide) before continuing.**
|
**We recommend beginners check out the [Getting Started Guide](https://github.com/flyingsparx/CENode/wiki/Getting-Started-Guide) before continuing.**
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
@ -33,13 +33,13 @@ const CEModels = require('cenode/models'); // if requred
|
|||||||
const node = new CENode(CEModels.core);
|
const node = new CENode(CEModels.core);
|
||||||
```
|
```
|
||||||
|
|
||||||
See the [Wiki](https://github.com/willwebberley/CENode/wiki) for further guides and the API reference.
|
See the [Wiki](https://github.com/flyingsparx/CENode/wiki) for further guides and the API reference.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Clone the repository
|
Clone the repository
|
||||||
```
|
```
|
||||||
git clone git@github.com:willwebberley/CENode.git
|
git clone git@github.com:flyingsparx/CENode.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Install the necessary dev dependencies.
|
Install the necessary dev dependencies.
|
||||||
@ -54,7 +54,7 @@ npm test
|
|||||||
|
|
||||||
## More Information
|
## More Information
|
||||||
|
|
||||||
See the CENode [Wiki](https://github.com/willwebberley/CENode/wiki) for more information, guides, and the API reference.
|
See the CENode [Wiki](https://github.com/flyingsparx/CENode/wiki) for more information, guides, and the API reference.
|
||||||
|
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
module.exports = [
|
module.exports = [
|
||||||
"conceptualise an ~ entity ~ E",
|
"conceptualise an ~ entity ~ E",
|
||||||
|
"conceptualise a ~ transform ~ T that has the value W as ~ output ~ and has the value F as ~ transform function ~",
|
||||||
|
"conceptualise a ~ transformer ~ T that ~ uses ~ the transform U",
|
||||||
"conceptualise an ~ imageable thing ~ I that has the value V as ~ image ~",
|
"conceptualise an ~ imageable thing ~ I that has the value V as ~ image ~",
|
||||||
"conceptualise a ~ timestamp ~ T that is an entity",
|
"conceptualise a ~ timestamp ~ T that is a transformer and has the value V as ~ date ~",
|
||||||
"conceptualise an ~ agent ~ A that is an entity and has the value V as ~ address ~",
|
"conceptualise an ~ agent ~ A that is an entity and has the value V as ~ address ~",
|
||||||
"conceptualise an ~ individual ~ I that is an ~ agent ~",
|
"conceptualise an ~ individual ~ I that is an ~ agent ~",
|
||||||
"conceptualise a ~ card ~ C that is an entity and has the timestamp T as ~ timestamp ~ and has the value V as ~ content ~ and has the value W as ~ linked content ~ and has the value V as ~ number of keystrokes ~ and has the timestamp T as ~ start time ~ and has the value W as ~ submit time ~ and has the value L as ~ latitude ~ and has the value M as ~ longitude ~",
|
"conceptualise a ~ card ~ C that is an entity and has the timestamp T as ~ timestamp ~ and has the value V as ~ content ~ and has the value W as ~ linked content ~ and has the value V as ~ number of keystrokes ~ and has the timestamp T as ~ start time ~ and has the value W as ~ submit time ~ and has the value L as ~ latitude ~ and has the value M as ~ longitude ~",
|
||||||
@ -18,9 +20,9 @@ module.exports = [
|
|||||||
"conceptualise a ~ policy ~ P that is an entity and has the value V as ~ enabled ~ and has the agent A as ~ target ~",
|
"conceptualise a ~ policy ~ P that is an entity and has the value V as ~ enabled ~ and has the agent A as ~ target ~",
|
||||||
"conceptualise a ~ tell policy ~ P that is a policy",
|
"conceptualise a ~ tell policy ~ P that is a policy",
|
||||||
"conceptualise an ~ ask policy ~ P that is a policy",
|
"conceptualise an ~ ask policy ~ P that is a policy",
|
||||||
"conceptualise a ~ gist policy ~ P that is a policy",
|
|
||||||
"conceptualise a ~ listen policy ~ P that is a policy",
|
"conceptualise a ~ listen policy ~ P that is a policy",
|
||||||
"conceptualise a ~ listen onbehalfof policy ~ P that is a policy",
|
"conceptualise a ~ listen onbehalfof policy ~ P that is a policy",
|
||||||
"conceptualise a ~ forwardall policy ~ P that is a policy and has the timestamp T as ~ start time ~ and has the value V as ~ all agents ~",
|
"conceptualise a ~ forwardall policy ~ P that is a policy and has the timestamp T as ~ start time ~ and has the value V as ~ all agents ~",
|
||||||
"conceptualise a ~ feedback policy ~ P that is a policy and has the value V as ~ acknowledgement ~"
|
"conceptualise a ~ feedback policy ~ P that is a policy and has the value V as ~ acknowledgement ~",
|
||||||
|
"conceptualise a ~ transform policy ~ P that is a policy and has the value V as ~ transform type ~ and ~ uses ~ the transform U"
|
||||||
];
|
];
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "cenode",
|
"name": "cenode",
|
||||||
"version": "3.0.12",
|
"version": "3.0.7",
|
||||||
"description": "A pure JavaScript implementation of the ITA project's CEStore - called CENode. CENode is able to understand the basic sentence types parsed by the CEStore, such as conceptualising and instance creation and modification.",
|
"description": "A pure JavaScript implementation of the ITA project's CEStore - called CENode. CENode is able to understand the basic sentence types parsed by the CEStore, such as conceptualising and instance creation and modification.",
|
||||||
"homepage": "http://cenode.io",
|
"homepage": "http://cenode.io",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"author": "Will Webberley & Alun Preece",
|
"author": "Will Webberley & Alun Preece",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/willwebberley/CENode"
|
"url": "https://github.com/flyingsparx/CENode"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
|
@ -158,6 +158,7 @@ class CEInstance {
|
|||||||
if (propagate !== false) {
|
if (propagate !== false) {
|
||||||
this.node.ruleEngine.enactRules(this, 'value', valueInstance, source);
|
this.node.ruleEngine.enactRules(this, 'value', valueInstance, source);
|
||||||
}
|
}
|
||||||
|
this.node.transformEngine.enactTransforms(this, label, valueInstance, source);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -197,6 +198,7 @@ class CEInstance {
|
|||||||
if (propagate !== false) {
|
if (propagate !== false) {
|
||||||
this.node.ruleEngine.enactRules(this, 'relationship', relationshipInstance, source);
|
this.node.ruleEngine.enactRules(this, 'relationship', relationshipInstance, source);
|
||||||
}
|
}
|
||||||
|
this.node.transformEngine.enactTransforms(this, label, relationshipInstance, source);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ const CEParser = require('./CEParser.js');
|
|||||||
const QuestionParser = require('./QuestionParser.js');
|
const QuestionParser = require('./QuestionParser.js');
|
||||||
const NLParser = require('./NLParser.js');
|
const NLParser = require('./NLParser.js');
|
||||||
const RuleEngine = require('./RuleEngine.js');
|
const RuleEngine = require('./RuleEngine.js');
|
||||||
|
const TransformEngine = require('./TransformEngine.js');
|
||||||
|
|
||||||
class CENode {
|
class CENode {
|
||||||
|
|
||||||
@ -65,9 +66,8 @@ class CENode {
|
|||||||
|
|
||||||
getInstanceByName(name, concept) {
|
getInstanceByName(name, concept) {
|
||||||
if (!name) { return null; }
|
if (!name) { return null; }
|
||||||
const possibleConcepts = concept ? [concept.id].concat(concept.ancestors.map(ancestor => ancestor.id)).concat(concept.descendants.map(descendant => descendant.id)) : [];
|
|
||||||
for (const instance of this.instances) {
|
for (const instance of this.instances) {
|
||||||
if (instance && (concept ? possibleConcepts.indexOf(instance.concept.id) > -1 : true)) {
|
if (instance && (concept ? concept.id === instance.concept.id : true)) {
|
||||||
if (instance.name.toLowerCase() === name.toLowerCase()) {
|
if (instance.name.toLowerCase() === name.toLowerCase()) {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@ -137,12 +137,12 @@ class CENode {
|
|||||||
*/
|
*/
|
||||||
addSentence(sentence, source) {
|
addSentence(sentence, source) {
|
||||||
const ceResult = this.addCE(sentence, false, source);
|
const ceResult = this.addCE(sentence, false, source);
|
||||||
if (!ceResult.error) {
|
if (ceResult.success) {
|
||||||
return ceResult;
|
return ceResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
const questionResult = this.askQuestion(sentence);
|
const questionResult = this.askQuestion(sentence);
|
||||||
if (!questionResult.error) {
|
if (questionResult.success) {
|
||||||
return questionResult;
|
return questionResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +170,13 @@ class CENode {
|
|||||||
* Returns: {success: bool, type: str, data: str}
|
* Returns: {success: bool, type: str, data: str}
|
||||||
*/
|
*/
|
||||||
addCE(sentence, source) {
|
addCE(sentence, source) {
|
||||||
return this.ceParser.parse(sentence.trim().replace('{now}', new Date().getTime()).replace('{uid}', this.newCardId()), source);
|
const success = this.ceParser.parse(sentence.trim().replace('{now}', new Date().getTime()).replace('{uid}', this.newCardId()), source);
|
||||||
|
return {
|
||||||
|
success: success[0],
|
||||||
|
type: 'gist',
|
||||||
|
data: success[1],
|
||||||
|
result: success[2] || undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -180,7 +186,12 @@ class CENode {
|
|||||||
* Returns: {success: bool, type: str, data: str}
|
* Returns: {success: bool, type: str, data: str}
|
||||||
*/
|
*/
|
||||||
askQuestion(sentence) {
|
askQuestion(sentence) {
|
||||||
return this.questionParser.parse(sentence);
|
const success = this.questionParser.parse(sentence);
|
||||||
|
return {
|
||||||
|
success: success[0],
|
||||||
|
type: success[0] ? 'gist' : undefined,
|
||||||
|
data: success[0] ? success[1] : undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -190,7 +201,11 @@ class CENode {
|
|||||||
* Returns: {type: str, data: str}
|
* Returns: {type: str, data: str}
|
||||||
*/
|
*/
|
||||||
addNL(sentence) {
|
addNL(sentence) {
|
||||||
return this.nlParser.parse(sentence);
|
const success = this.nlParser.parse(sentence);
|
||||||
|
return {
|
||||||
|
type: success[0] ? 'confirm' : 'gist',
|
||||||
|
data: success[1],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -234,6 +249,7 @@ class CENode {
|
|||||||
this.questionParser = new QuestionParser(this);
|
this.questionParser = new QuestionParser(this);
|
||||||
this.nlParser = new NLParser(this);
|
this.nlParser = new NLParser(this);
|
||||||
this.ruleEngine = new RuleEngine(this);
|
this.ruleEngine = new RuleEngine(this);
|
||||||
|
this.transformEngine = new TransformEngine(this);
|
||||||
this.concepts = [];
|
this.concepts = [];
|
||||||
this.instances = [];
|
this.instances = [];
|
||||||
this.conceptDict = {};
|
this.conceptDict = {};
|
||||||
|
@ -30,14 +30,6 @@ const quotes = {
|
|||||||
|
|
||||||
class CEParser {
|
class CEParser {
|
||||||
|
|
||||||
static error(message, concerns) {
|
|
||||||
return { error: true, response: { message, type: 'gist', concerns } };
|
|
||||||
}
|
|
||||||
|
|
||||||
static success(message, concerns) {
|
|
||||||
return { error: false, response: { message, type: 'gist', concerns } };
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Submit CE to be processed by node.
|
* Submit CE to be processed by node.
|
||||||
* This may result in
|
* This may result in
|
||||||
@ -50,22 +42,17 @@ class CEParser {
|
|||||||
* Returns: [bool, str] (bool = success, str = error or parsed string)
|
* Returns: [bool, str] (bool = success, str = error or parsed string)
|
||||||
*/
|
*/
|
||||||
parse(input, source) {
|
parse(input, source) {
|
||||||
try {
|
const t = input.replace(/\s+/g, ' ').replace(/\.+$/, '').trim(); // Whitespace -> single space
|
||||||
// Whitespace -> single space:
|
if (t.match(/^conceptualise an?/i)) {
|
||||||
const t = input.replace(/\s+/g, ' ').replace(/\.+$/, '').trim();
|
return this.newConcept(t, source);
|
||||||
if (t.match(/^conceptualise an?/i)) {
|
} else if (t.match(/^conceptualise the ([a-zA-Z0-9 ]*) ([A-Z0-9]+) (?:has|is|~)/i)) {
|
||||||
return this.newConcept(t, source);
|
return this.modifyConcept(t, source);
|
||||||
} else if (t.match(/^conceptualise the ([a-zA-Z0-9 ]*) ([A-Z0-9]+) (?:has|is|~)/i)) {
|
} else if (t.match(/^there is an? ([a-zA-Z0-9 ]*) named/i)) {
|
||||||
return this.modifyConcept(t, source);
|
return this.newInstance(t, source);
|
||||||
} else if (t.match(/^there is an? ([a-zA-Z0-9 ]*) named/i)) {
|
} else if (t.match(/^the ([a-zA-Z0-9 ]*)/i)) {
|
||||||
return this.newInstance(t, source);
|
return this.modifyInstance(t, source);
|
||||||
} else if (t.match(/^the ([a-zA-Z0-9 ]*)/i)) {
|
|
||||||
return this.modifyInstance(t, source);
|
|
||||||
}
|
|
||||||
return CEParser.error('Input not a valid CE sentence.');
|
|
||||||
} catch (err) {
|
|
||||||
return CEParser.error(`There was a problem parsing the CE. ${err}.`);
|
|
||||||
}
|
}
|
||||||
|
return [false, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
newConcept(t, source) {
|
newConcept(t, source) {
|
||||||
@ -74,7 +61,7 @@ class CEParser {
|
|||||||
const storedConcept = this.node.getConceptByName(conceptName);
|
const storedConcept = this.node.getConceptByName(conceptName);
|
||||||
let concept = null;
|
let concept = null;
|
||||||
if (storedConcept) {
|
if (storedConcept) {
|
||||||
return CEParser.error('This concept already exists');
|
return [false, 'This concept already exists.'];
|
||||||
}
|
}
|
||||||
concept = new CEConcept(this.node, conceptName, source);
|
concept = new CEConcept(this.node, conceptName, source);
|
||||||
|
|
||||||
@ -83,19 +70,19 @@ class CEParser {
|
|||||||
for (const fact of facts) {
|
for (const fact of facts) {
|
||||||
this.processConceptFact(concept, fact, source);
|
this.processConceptFact(concept, fact, source);
|
||||||
}
|
}
|
||||||
return CEParser.success(t, concept);
|
return [true, t, concept];
|
||||||
}
|
}
|
||||||
|
|
||||||
modifyConcept(t, source) {
|
modifyConcept(t, source) {
|
||||||
const conceptInfo = t.match(/^conceptualise the ([a-zA-Z0-9 ]*) ([A-Z0-9]+) (?:has|is|~)/);
|
const conceptInfo = t.match(/^conceptualise the ([a-zA-Z0-9 ]*) ([A-Z0-9]+) (?:has|is|~)/);
|
||||||
if (!conceptInfo) {
|
if (!conceptInfo) {
|
||||||
return CEParser.error('Unable to parse sentence');
|
return [false, 'Unable to parse sentence'];
|
||||||
}
|
}
|
||||||
const conceptName = conceptInfo[1];
|
const conceptName = conceptInfo[1];
|
||||||
const conceptVar = conceptInfo[2];
|
const conceptVar = conceptInfo[2];
|
||||||
const concept = this.node.getConceptByName(conceptName);
|
const concept = this.node.getConceptByName(conceptName);
|
||||||
if (!concept) {
|
if (!concept) {
|
||||||
return CEParser.error(`Concept ${conceptInfo[1]} not known.`);
|
return [false, `Concept ${conceptInfo[1]} not known.`];
|
||||||
}
|
}
|
||||||
|
|
||||||
const remainderRegex = new RegExp(`^conceptualise the ${conceptName} ${conceptVar}`, 'i');
|
const remainderRegex = new RegExp(`^conceptualise the ${conceptName} ${conceptVar}`, 'i');
|
||||||
@ -104,7 +91,7 @@ class CEParser {
|
|||||||
for (const fact of facts) {
|
for (const fact of facts) {
|
||||||
this.processConceptFact(concept, fact, source);
|
this.processConceptFact(concept, fact, source);
|
||||||
}
|
}
|
||||||
return CEParser.success(t, concept);
|
return [true, t, concept];
|
||||||
}
|
}
|
||||||
|
|
||||||
processConceptFact(concept, fact, source) {
|
processConceptFact(concept, fact, source) {
|
||||||
@ -148,27 +135,27 @@ class CEParser {
|
|||||||
let names = t.match(/^there is an? ([a-zA-Z0-9 ]*) named '([^'\\]*(?:\\.[^'\\]*)*)'/i);
|
let names = t.match(/^there is an? ([a-zA-Z0-9 ]*) named '([^'\\]*(?:\\.[^'\\]*)*)'/i);
|
||||||
if (!names) {
|
if (!names) {
|
||||||
names = t.match(/^there is an? ([a-zA-Z0-9 ]*) named ([a-zA-Z0-9_]*)/i);
|
names = t.match(/^there is an? ([a-zA-Z0-9 ]*) named ([a-zA-Z0-9_]*)/i);
|
||||||
if (!names) { return CEParser.error('Unable to determine name of instance.'); }
|
if (!names) { return [false, 'Unable to determine name of instance.']; }
|
||||||
}
|
}
|
||||||
const conceptName = names[1];
|
const conceptName = names[1];
|
||||||
const instanceName = names[2].replace(/\\/g, '');
|
const instanceName = names[2].replace(/\\/g, '');
|
||||||
const concept = this.node.getConceptByName(conceptName);
|
const concept = this.node.getConceptByName(conceptName);
|
||||||
const currentInstance = this.node.getInstanceByName(instanceName, concept);
|
const currentInstance = this.node.getInstanceByName(instanceName, concept);
|
||||||
if (!concept) {
|
if (!concept) {
|
||||||
return CEParser.error(`Instance type unknown: ${conceptName}`);
|
return [false, `Instance type unknown: ${conceptName}`];
|
||||||
}
|
}
|
||||||
if (currentInstance && currentInstance.type.id === concept.id) {
|
if (currentInstance && currentInstance.type.id === concept.id) {
|
||||||
return CEParser.error('There is already an instance of this type with this name.', currentInstance);
|
return [false, 'There is already an instance of this type with this name.', currentInstance];
|
||||||
}
|
}
|
||||||
const instance = new CEInstance(this.node, concept, instanceName, source);
|
const instance = new CEInstance(this.node, concept, instanceName, source);
|
||||||
instance.sentences.push(t);
|
instance.sentences.push(t);
|
||||||
|
|
||||||
const remainder = t.replace(/^there is an? (?:[a-zA-Z0-9 ]*) named (?:[a-zA-Z0-9_]*|'[a-zA-Z0-9_ ]*') that/, '');
|
const remainder = t.replace(/^there is an? (?:[a-zA-Z0-9 ]*) named (?:[a-zA-Z0-9_]*|'[a-zA-Z0-9_ ]*') that/, '');
|
||||||
const facts = remainder.replace(/\band\b/g, '+').match(/(?:'(?:\\.|[^'])*'|[^+])+/g);
|
const facts = remainder.replace(/\band\b/g, '!+').match(/(?:'(?:\\.|[^'])*'|[^!+])+/g);
|
||||||
for (const fact of facts) {
|
for (const fact of facts) {
|
||||||
this.processInstanceFact(instance, fact, source);
|
this.processInstanceFact(instance, fact, source);
|
||||||
}
|
}
|
||||||
return CEParser.success(t, instance);
|
return [true, t, instance];
|
||||||
}
|
}
|
||||||
|
|
||||||
modifyInstance(t, source) {
|
modifyInstance(t, source) {
|
||||||
@ -196,23 +183,23 @@ class CEParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!concept || !instance) {
|
if (!concept || !instance) {
|
||||||
return CEParser.error(`Unknown concept/instance combination in: ${t}`);
|
return [false, `Unknown concept/instance combination in: ${t}`];
|
||||||
}
|
}
|
||||||
instance.sentences.push(t);
|
instance.sentences.push(t);
|
||||||
const tokens = t.split(' ');
|
const tokens = t.split(' ');
|
||||||
tokens.splice(0, 1 + concept.name.split(' ').length + instanceName.split(' ').length);
|
tokens.splice(0, 1 + concept.name.split(' ').length + instanceName.split(' ').length);
|
||||||
const remainder = tokens.join(' ');
|
const remainder = tokens.join(' ');
|
||||||
const facts = remainder.replace(/\band\b/g, '+').match(/(?:'(?:\\.|[^'])*'|[^+])+/g);
|
const facts = remainder.replace(/\band\b/g, '!+').match(/(?:'(?:\\.|[^'])*'|[^!+])+/g);
|
||||||
if (facts) {
|
if (facts) {
|
||||||
for (const fact of facts) {
|
for (const fact of facts) {
|
||||||
this.processInstanceFact(instance, fact, source);
|
this.processInstanceFact(instance, fact, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return CEParser.success(t, instance);
|
return [true, t, instance];
|
||||||
}
|
}
|
||||||
|
|
||||||
processInstanceFact(instance, fact, source) {
|
processInstanceFact(instance, fact, source) {
|
||||||
const input = fact.trim().replace(/\+/g, 'and');
|
const input = fact.trim().replace(/!\+/g, 'and');
|
||||||
if (input.match(/^(?!has)([a-zA-Z0-9 ]*) the ([a-zA-Z0-9 ]*) ([a-zA-Z0-9_' ]*)/)) {
|
if (input.match(/^(?!has)([a-zA-Z0-9 ]*) the ([a-zA-Z0-9 ]*) ([a-zA-Z0-9_' ]*)/)) {
|
||||||
const re = /^(?!has)([a-zA-Z0-9 ]*) the ([a-zA-Z0-9 ]*) ([a-zA-Z0-9_' ]*)/;
|
const re = /^(?!has)([a-zA-Z0-9 ]*) the ([a-zA-Z0-9 ]*) ([a-zA-Z0-9_' ]*)/;
|
||||||
const match = re.exec(input);
|
const match = re.exec(input);
|
||||||
@ -220,7 +207,6 @@ class CEParser {
|
|||||||
const relConceptName = match[2];
|
const relConceptName = match[2];
|
||||||
const relInstanceName = match[3].replace(/'/g, '');
|
const relInstanceName = match[3].replace(/'/g, '');
|
||||||
const relConcept = this.node.getConceptByName(relConceptName);
|
const relConcept = this.node.getConceptByName(relConceptName);
|
||||||
|
|
||||||
if (relConcept) {
|
if (relConcept) {
|
||||||
let relInstance = this.node.getInstanceByName(relInstanceName, relConcept);
|
let relInstance = this.node.getInstanceByName(relInstanceName, relConcept);
|
||||||
if (!relInstance) {
|
if (!relInstance) {
|
||||||
|
@ -116,22 +116,14 @@ class CEServer {
|
|||||||
},
|
},
|
||||||
'/instance': (request, response) => {
|
'/instance': (request, response) => {
|
||||||
const idRegex = decodeURIComponent(request.url).match(/id=(.*)/);
|
const idRegex = decodeURIComponent(request.url).match(/id=(.*)/);
|
||||||
const nameRegex = decodeURIComponent(request.url).match(/name=(.*)/);
|
const id = idRegex ? idRegex[1] : null;
|
||||||
const idQuery = idRegex ? idRegex[1] : null;
|
const instance = this.node.getInstanceById(id);
|
||||||
const nameQuery = nameRegex ? nameRegex[1] : null;
|
|
||||||
let instance;
|
|
||||||
if (idQuery) {
|
|
||||||
instance = this.node.getInstanceById(idQuery);
|
|
||||||
} else if (nameQuery) {
|
|
||||||
instance = this.node.getInstanceByName(nameQuery);
|
|
||||||
}
|
|
||||||
if (instance) {
|
if (instance) {
|
||||||
const body = {
|
const body = {
|
||||||
name: instance.name,
|
name: instance.name,
|
||||||
conceptName: instance.concept.name,
|
conceptName: instance.concept.name,
|
||||||
conceptId: instance.concept.id,
|
conceptId: instance.concept.id,
|
||||||
ce: instance.ce,
|
ce: instance.ce,
|
||||||
gist: instance.gist,
|
|
||||||
synonyms: instance.synonyms,
|
synonyms: instance.synonyms,
|
||||||
subConcepts: [],
|
subConcepts: [],
|
||||||
values: [],
|
values: [],
|
||||||
@ -154,7 +146,7 @@ class CEServer {
|
|||||||
return response.end(JSON.stringify(body));
|
return response.end(JSON.stringify(body));
|
||||||
}
|
}
|
||||||
response.writeHead(404);
|
response.writeHead(404);
|
||||||
return response.end('Unable to find the instance.');
|
return response.end('Concept not found');
|
||||||
},
|
},
|
||||||
'/info': (request, response) => {
|
'/info': (request, response) => {
|
||||||
const body = { recentInstances: [], recentConcepts: [], instanceCount: this.node.instances.length, conceptCount: this.node.concepts.length };
|
const body = { recentInstances: [], recentConcepts: [], instanceCount: this.node.instances.length, conceptCount: this.node.concepts.length };
|
||||||
@ -254,12 +246,7 @@ class CEServer {
|
|||||||
if (request.method in this.handlers) {
|
if (request.method in this.handlers) {
|
||||||
const path = request.url.indexOf('?') > 1 ? request.url.slice(0, request.url.indexOf('?')) : request.url;
|
const path = request.url.indexOf('?') > 1 ? request.url.slice(0, request.url.indexOf('?')) : request.url;
|
||||||
if (path in this.handlers[request.method]) {
|
if (path in this.handlers[request.method]) {
|
||||||
try {
|
this.handlers[request.method][path](request, response);
|
||||||
this.handlers[request.method][path](request, response);
|
|
||||||
} catch (err) {
|
|
||||||
response.writeHead(500);
|
|
||||||
response.end(`500: ${err}.`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
response.writeHead(404);
|
response.writeHead(404);
|
||||||
response.end(`404: Resource not found for method ${request.method}.`);
|
response.end(`404: Resource not found for method ${request.method}.`);
|
||||||
|
@ -38,9 +38,9 @@ class CardHandler {
|
|||||||
// Prepare the response 'tell card' and add this back to the node
|
// Prepare the response 'tell card' and add this back to the node
|
||||||
let urls;
|
let urls;
|
||||||
let c;
|
let c;
|
||||||
if (data.response.message) {
|
if (data.data) {
|
||||||
urls = data.response.message.match(/(https?:\/\/[a-zA-Z0-9./\-+_&=?!%]*)/gi);
|
urls = data.data.match(/(https?:\/\/[a-zA-Z0-9./\-+_&=?!%]*)/gi);
|
||||||
c = `there is a ${data.response.type} card named 'msg_{uid}' that is from the agent '${this.agent.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has '${data.response.message.replace(/'/g, "\\'")}' as content`;
|
c = `there is a ${data.type} card named 'msg_{uid}' that is from the agent '${this.agent.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has '${data.data.replace(/'/g, "\\'")}' as content`;
|
||||||
} else {
|
} else {
|
||||||
c = `there is a gist card named 'msg_{uid}' that is from the agent '${this.agent.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has 'Sorry; your question was not understood.' as content`;
|
c = `there is a gist card named 'msg_{uid}' that is from the agent '${this.agent.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has 'Sorry; your question was not understood.' as content`;
|
||||||
}
|
}
|
||||||
@ -63,12 +63,12 @@ class CardHandler {
|
|||||||
// Add the CE sentence to the node
|
// Add the CE sentence to the node
|
||||||
const data = this.node.addCE(card.content, card.is_from && card.is_from.name);
|
const data = this.node.addCE(card.content, card.is_from && card.is_from.name);
|
||||||
|
|
||||||
if (data.error && card.is_from) {
|
if (!data.success && card.is_from) {
|
||||||
// If unsuccessful, write an error back
|
// If unsuccessful, write an error back
|
||||||
return this.node.addSentence(`there is a gist card named 'msg_{uid}' that is from the agent '${this.agent.name.replace(/'/g, "\\'")}' and is to the ${card.is_from.type.name} '${card.is_from.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has 'Sorry. Your input was not understood.' as content and is in reply to the card '${card.name}'.`);
|
return this.node.addSentence(`there is a gist card named 'msg_{uid}' that is from the agent '${this.agent.name.replace(/'/g, "\\'")}' and is to the ${card.is_from.type.name} '${card.is_from.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has 'Sorry. Your input was not understood.' as content and is in reply to the card '${card.name}'.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.error) {
|
if (data.success === true) {
|
||||||
// Add sentence to any active tell policy queues
|
// Add sentence to any active tell policy queues
|
||||||
for (const policy of this.node.getInstances('tell policy')) {
|
for (const policy of this.node.getInstances('tell policy')) {
|
||||||
if (policy.enabled === 'true' && policy.target && policy.target.name) {
|
if (policy.enabled === 'true' && policy.target && policy.target.name) {
|
||||||
@ -86,12 +86,12 @@ class CardHandler {
|
|||||||
const ack = policy.acknowledgement;
|
const ack = policy.acknowledgement;
|
||||||
if (policy.target.name.toLowerCase() === card.is_from.name.toLowerCase()) {
|
if (policy.target.name.toLowerCase() === card.is_from.name.toLowerCase()) {
|
||||||
let c;
|
let c;
|
||||||
if (ack === 'basic') { c = 'OK.'; } else if (data.response.type === 'tell') {
|
if (ack === 'basic') { c = 'OK.'; } else if (data.type === 'tell') {
|
||||||
c = `OK. I added this to my knowledge base: ${data.response.message}`;
|
c = `OK. I added this to my knowledge base: ${data.data}`;
|
||||||
} else if (data.response.type === 'ask' || data.response.type === 'confirm' || data.response.type === 'gist') {
|
} else if (data.type === 'ask' || data.type === 'confirm' || data.type === 'gist') {
|
||||||
c = data.response.message;
|
c = data.data;
|
||||||
}
|
}
|
||||||
return this.node.addSentence(`there is a ${data.response.type} card named 'msg_{uid}' that is from the agent '${this.agent.name.replace(/'/g, "\\'")}' and is to the ${card.is_from.type.name} '${card.is_from.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has '${c.replace(/'/g, "\\'")}' as content and is in reply to the card '${card.name}'.`);
|
return this.node.addSentence(`there is a ${data.type} card named 'msg_{uid}' that is from the agent '${this.agent.name.replace(/'/g, "\\'")}' and is to the ${card.is_from.type.name} '${card.is_from.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has '${c.replace(/'/g, "\\'")}' as content and is in reply to the card '${card.name}'.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,28 +102,17 @@ class CardHandler {
|
|||||||
'nl card': (card) => {
|
'nl card': (card) => {
|
||||||
let data = this.node.addCE(card.content, card.is_from && card.is_from.name);
|
let data = this.node.addCE(card.content, card.is_from && card.is_from.name);
|
||||||
// If valid CE, then replicate the nl card as a tell card ('autoconfirm')
|
// If valid CE, then replicate the nl card as a tell card ('autoconfirm')
|
||||||
if (!data.error) {
|
if (data.success) {
|
||||||
return this.node.addSentence(`there is a tell card named 'msg_{uid}' that is from the ${card.is_from.type.name} '${card.is_from.name.replace(/'/g, "\\'")}' and is to the agent '${this.agent.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has '${card.content.replace(/'/g, "\\'")}' as content.`);
|
return this.node.addSentence(`there is a tell card named 'msg_{uid}' that is from the ${card.is_from.type.name} '${card.is_from.name.replace(/'/g, "\\'")}' and is to the agent '${this.agent.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has '${card.content.replace(/'/g, "\\'")}' as content.`);
|
||||||
}
|
}
|
||||||
data = this.node.askQuestion(card.content);
|
data = this.node.askQuestion(card.content);
|
||||||
// If question was success replicate as ask card ('autoask')
|
// If question was success replicate as ask card ('autoask')
|
||||||
if (!data.error) {
|
if (data.success) {
|
||||||
return this.node.addSentence(`there is an ask card named 'msg_{uid}' that is from the ${card.is_from.type.name} '${card.is_from.name.replace(/'/g, "\\'")}' and is to the agent '${this.agent.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has '${card.content.replace(/'/g, "\\'")}' as content.`);
|
return this.node.addSentence(`there is an ask card named 'msg_{uid}' that is from the ${card.is_from.type.name} '${card.is_from.name.replace(/'/g, "\\'")}' and is to the agent '${this.agent.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has '${card.content.replace(/'/g, "\\'")}' as content.`);
|
||||||
}
|
}
|
||||||
// If question not understood then place the response to the NL card in a new response
|
// If question not understood then place the response to the NL card in a new response
|
||||||
data = this.node.addNL(card.content);
|
data = this.node.addNL(card.content);
|
||||||
return this.node.addSentence(`there is a ${data.response.type} card named 'msg_{uid}' that is from the agent '${this.agent.name.replace(/'/g, "\\'")}' and is to the ${card.is_from.type.name} '${card.is_from.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has '${data.response.message.replace(/'/g, "\\'")}' as content and is in reply to the card '${card.name}'.`);
|
return this.node.addSentence(`there is a ${data.type} card named 'msg_{uid}' that is from the agent '${this.agent.name.replace(/'/g, "\\'")}' and is to the ${card.is_from.type.name} '${card.is_from.name.replace(/'/g, "\\'")}' and has the timestamp '{now}' as timestamp and has '${data.data.replace(/'/g, "\\'")}' as content and is in reply to the card '${card.name}'.`);
|
||||||
},
|
|
||||||
|
|
||||||
'gist card': (card) => {
|
|
||||||
// Add sentence to any active gist policy queues
|
|
||||||
for (const policy of this.node.getInstances('gist policy')) {
|
|
||||||
if (policy.enabled === 'true' && policy.target && policy.target.name) {
|
|
||||||
const targetName = policy.target.name;
|
|
||||||
if (!(targetName in this.agent.policyHandler.unsentGistCards)) { this.agent.policyHandler.unsentGistCards[targetName] = []; }
|
|
||||||
this.agent.policyHandler.unsentGistCards[targetName].push(card);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,6 @@
|
|||||||
|
|
||||||
class NLParser {
|
class NLParser {
|
||||||
|
|
||||||
static error(message) {
|
|
||||||
return { error: true, response: { message, type: 'gist' } };
|
|
||||||
}
|
|
||||||
|
|
||||||
static success(message) {
|
|
||||||
return { error: false, response: { message, type: 'confirm' } };
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Submit natural language to be processed by node.
|
* Submit natural language to be processed by node.
|
||||||
* This results in
|
* This results in
|
||||||
@ -130,7 +122,7 @@ class NLParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (facts.length > 0) {
|
if (facts.length > 0) {
|
||||||
return NLParser.success(ce + facts.join(' and '));
|
return [true, ce + facts.join(' and ')];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,12 +140,12 @@ class NLParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newInstanceName && newInstanceName.length) {
|
if (newInstanceName && newInstanceName.length) {
|
||||||
return NLParser.success(`there is a ${this.node.concepts[i].name} named '${newInstanceName.trim()}'`);
|
return [true, `there is a ${this.node.concepts[i].name} named '${newInstanceName.trim()}'`];
|
||||||
}
|
}
|
||||||
return NLParser.success(`there is a ${this.node.concepts[i].name} named '${this.node.concepts[i].name} ${this.node.instances.length}${1}'`);
|
return [true, `there is a ${this.node.concepts[i].name} named '${this.node.concepts[i].name} ${this.node.instances.length}${1}'`];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NLParser.error(`Un-parseable input: ${t}`);
|
return [false, `Un-parseable input: ${t}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -86,7 +86,6 @@ class PolicyHandler {
|
|||||||
this.node = agent.node;
|
this.node = agent.node;
|
||||||
this.unsentTellCards = {};
|
this.unsentTellCards = {};
|
||||||
this.unsentAskCards = {};
|
this.unsentAskCards = {};
|
||||||
this.unsentGistCards = {};
|
|
||||||
this.lastSuccessfulRequest = 0;
|
this.lastSuccessfulRequest = 0;
|
||||||
this.handlers = {
|
this.handlers = {
|
||||||
|
|
||||||
@ -160,36 +159,6 @@ class PolicyHandler {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
'gist policy': (policy) => {
|
|
||||||
// For each gist policy in place, send all currently-untold cards to each target
|
|
||||||
// multiple cards to be sent to one target line-separated
|
|
||||||
if (policy.target && policy.target.name && policy.target.address) {
|
|
||||||
if (!(policy.target.name in this.unsentGistCards)) {
|
|
||||||
this.unsentGistCards[policy.target.name] = [];
|
|
||||||
}
|
|
||||||
let data = '';
|
|
||||||
for (const card of this.unsentGistCards[policy.target.name]) {
|
|
||||||
if (card.is_tos && card.is_from.name.toLowerCase() !== policy.target.name.toLowerCase()) { // Don't send back a card sent from target agent
|
|
||||||
// Make sure target is not already a recipient
|
|
||||||
let inCard = false;
|
|
||||||
for (const to of card.is_tos) {
|
|
||||||
if (to.id === policy.target.id) { inCard = true; break; }
|
|
||||||
}
|
|
||||||
if (!inCard) {
|
|
||||||
card.addRelationship('is to', policy.target);
|
|
||||||
}
|
|
||||||
data += `${card.ce}\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.length) {
|
|
||||||
net.makeRequest('POST', policy.target.address, POST_SENTENCES_ENDPOINT, data, () => {
|
|
||||||
this.lastSuccessfulRequest = new Date().getTime();
|
|
||||||
this.unsentGistCards[policy.target.name] = [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'listen policy': (policy) => {
|
'listen policy': (policy) => {
|
||||||
// Make request to target to get cards addressed to THIS agent
|
// Make request to target to get cards addressed to THIS agent
|
||||||
if (policy.target && policy.target.address) {
|
if (policy.target && policy.target.address) {
|
||||||
@ -244,7 +213,17 @@ class PolicyHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
'transform policy': (policy) => {
|
||||||
|
if (policy.transform_type && policy.uses){
|
||||||
|
for (const instance of this.node.getInstances(policy.transform_type)) {
|
||||||
|
if (!instance.uses) {
|
||||||
|
instance.addRelationship('uses', policy.uses, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handle(policy) {
|
handle(policy) {
|
||||||
|
@ -18,14 +18,6 @@
|
|||||||
|
|
||||||
class QuestionParser {
|
class QuestionParser {
|
||||||
|
|
||||||
static error(message) {
|
|
||||||
return { error: true, response: { message, type: 'gist' } };
|
|
||||||
}
|
|
||||||
|
|
||||||
static success(message) {
|
|
||||||
return { error: false, response: { message, type: 'gist' } };
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Submit a who/what/where question to be processed by node.
|
* Submit a who/what/where question to be processed by node.
|
||||||
* This may result in
|
* This may result in
|
||||||
@ -36,25 +28,21 @@ class QuestionParser {
|
|||||||
* Returns: [bool, str] (bool = success, str = error or response)
|
* Returns: [bool, str] (bool = success, str = error or response)
|
||||||
*/
|
*/
|
||||||
parse(t) {
|
parse(t) {
|
||||||
try {
|
const input = t.trim();
|
||||||
const input = t.trim();
|
if (t.match(/^where (is|are)/i)) {
|
||||||
if (t.match(/^where (is|are)/i)) {
|
return this.whereIs(input);
|
||||||
return this.whereIs(input);
|
} else if (t.match(/^(\bwho\b|\bwhat\b) is(?: \bin?\b | \bon\b | \bat\b)/i)) {
|
||||||
} else if (t.match(/^(\bwho\b|\bwhat\b) is(?: \bin?\b | \bon\b | \bat\b)/i)) {
|
return this.whatIsIn(input);
|
||||||
return this.whatIsIn(input);
|
} else if (t.match(/^(\bwho\b|\bwhat\b) (?:is|are)/i)) {
|
||||||
} else if (t.match(/^(\bwho\b|\bwhat\b) (?:is|are)/i)) {
|
return this.whatIs(input);
|
||||||
return this.whatIs(input);
|
} else if (t.match(/^(\bwho\b|\bwhat\b) does/i)) {
|
||||||
} else if (t.match(/^(\bwho\b|\bwhat\b) does/i)) {
|
return this.whatDoes(input);
|
||||||
return this.whatDoes(input);
|
} else if (t.match(/^(\bwho\b|\bwhat\b)/i)) {
|
||||||
} else if (t.match(/^(\bwho\b|\bwhat\b)/i)) {
|
return this.whatRelationship(input);
|
||||||
return this.whatRelationship(input);
|
} else if (t.match(/^list (\ball\b|\binstances\b)/i)) {
|
||||||
} else if (t.match(/^list (\ball\b|\binstances\b)/i)) {
|
return this.listInstances(input);
|
||||||
return this.listInstances(input);
|
|
||||||
}
|
|
||||||
return QuestionParser.error('Input is not a valid question');
|
|
||||||
} catch (err) {
|
|
||||||
return QuestionParser.error(`There was a problem with the question. ${err}.`);
|
|
||||||
}
|
}
|
||||||
|
return [false, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
whereIs(t) {
|
whereIs(t) {
|
||||||
@ -62,7 +50,8 @@ class QuestionParser {
|
|||||||
const instance = this.node.getInstanceByName(thing);
|
const instance = this.node.getInstanceByName(thing);
|
||||||
let message;
|
let message;
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
return QuestionParser.success(`I don't know what ${thing} is.`);
|
message = `I don't know what ${thing} is.`;
|
||||||
|
return [true, message];
|
||||||
}
|
}
|
||||||
const locatableInstances = this.node.getInstances('location', true);
|
const locatableInstances = this.node.getInstances('location', true);
|
||||||
const locatableIds = [];
|
const locatableIds = [];
|
||||||
@ -91,7 +80,8 @@ class QuestionParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!placeFound) {
|
if (!placeFound) {
|
||||||
return QuestionParser.success(`I don't know where ${instance.name} is.`);
|
message = `I don't know where ${instance.name} is.`;
|
||||||
|
return [true, message];
|
||||||
}
|
}
|
||||||
message = instance.name;
|
message = instance.name;
|
||||||
for (const place in places) {
|
for (const place in places) {
|
||||||
@ -103,7 +93,7 @@ class QuestionParser {
|
|||||||
message += ' and';
|
message += ' and';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return QuestionParser.success(`${message.substring(0, message.length - 4)}.`);
|
return [true, `${message.substring(0, message.length - 4)}.`];
|
||||||
}
|
}
|
||||||
|
|
||||||
whatIsIn(t) {
|
whatIsIn(t) {
|
||||||
@ -116,7 +106,7 @@ class QuestionParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
return QuestionParser.success(`${thing} is not an instance of type location.`);
|
return [true, `${thing} is not an instance of type location.`];
|
||||||
}
|
}
|
||||||
const things = {};
|
const things = {};
|
||||||
let thingFound = false;
|
let thingFound = false;
|
||||||
@ -143,7 +133,7 @@ class QuestionParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!thingFound) {
|
if (!thingFound) {
|
||||||
return QuestionParser.success(`I don't know what is located in/on/at the ${instance.type.name} ${instance.name}.`);
|
return [true, `I don't know what is located in/on/at the ${instance.type.name} ${instance.name}.`];
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = '';
|
let message = '';
|
||||||
@ -154,7 +144,7 @@ class QuestionParser {
|
|||||||
}
|
}
|
||||||
message += ' and';
|
message += ' and';
|
||||||
}
|
}
|
||||||
return QuestionParser.success(`${message.substring(0, message.length - 4)}.`);
|
return [true, `${message.substring(0, message.length - 4)}.`];
|
||||||
}
|
}
|
||||||
|
|
||||||
whatIs(input) {
|
whatIs(input) {
|
||||||
@ -166,7 +156,7 @@ class QuestionParser {
|
|||||||
if (name) {
|
if (name) {
|
||||||
instance = this.node.getInstanceByName(name[2]);
|
instance = this.node.getInstanceByName(name[2]);
|
||||||
if (instance) {
|
if (instance) {
|
||||||
return QuestionParser.success(instance.gist);
|
return [true, instance.gist];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,46 +187,44 @@ class QuestionParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (possibilities.length > 0) {
|
if (possibilities.length > 0) {
|
||||||
return QuestionParser.success(`'${name}' ${possibilities.join(' and ')}.`);
|
return [true, `'${name}' ${possibilities.join(' and ')}.`];
|
||||||
}
|
}
|
||||||
|
|
||||||
// If nothing found, do fuzzy search
|
// If nothing found, do fuzzy search
|
||||||
const searchReturn = this.fuzzySearch(t);
|
const searchReturn = this.fuzzySearch(t);
|
||||||
let fuzzyGist = 'I know about ';
|
let fuzzyGist = 'I know about ';
|
||||||
let fuzzyFound = false;
|
let fuzzyFound = false;
|
||||||
if (searchReturn) {
|
for (const key in searchReturn) {
|
||||||
for (const key in searchReturn) {
|
if (searchReturn[key].length > 1) {
|
||||||
if (searchReturn[key].length > 1) {
|
for (let i = 0; i < searchReturn[key].length; i += 1) {
|
||||||
for (let i = 0; i < searchReturn[key].length; i += 1) {
|
if (i === 0) {
|
||||||
if (i === 0) {
|
if (!fuzzyFound) {
|
||||||
if (!fuzzyFound) {
|
fuzzyGist += `the ${key}s '${searchReturn[key][i]}'`;
|
||||||
fuzzyGist += `the ${key}s '${searchReturn[key][i]}'`;
|
|
||||||
} else {
|
|
||||||
fuzzyGist += `The ${key}s '${searchReturn[key][i]}'`;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
fuzzyGist += `, '${searchReturn[key][i]}'`;
|
fuzzyGist += `The ${key}s '${searchReturn[key][i]}'`;
|
||||||
}
|
|
||||||
if (i === searchReturn[key].length - 1) {
|
|
||||||
fuzzyGist += '. ';
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
fuzzyGist += `, '${searchReturn[key][i]}'`;
|
||||||
|
}
|
||||||
|
if (i === searchReturn[key].length - 1) {
|
||||||
|
fuzzyGist += '. ';
|
||||||
}
|
}
|
||||||
} else if (!fuzzyFound) {
|
|
||||||
fuzzyGist += `the ${key} '${searchReturn[key][0]}'. `;
|
|
||||||
} else {
|
|
||||||
fuzzyGist += `The ${key} '${searchReturn[key][0]}'. `;
|
|
||||||
}
|
}
|
||||||
fuzzyFound = true;
|
} else if (!fuzzyFound) {
|
||||||
|
fuzzyGist += `the ${key} '${searchReturn[key][0]}'. `;
|
||||||
|
} else {
|
||||||
|
fuzzyGist += `The ${key} '${searchReturn[key][0]}'. `;
|
||||||
}
|
}
|
||||||
|
fuzzyFound = true;
|
||||||
}
|
}
|
||||||
if (fuzzyFound) {
|
if (fuzzyFound) {
|
||||||
return QuestionParser.success(fuzzyGist);
|
return [true, fuzzyGist];
|
||||||
}
|
}
|
||||||
return QuestionParser.success('I don\'t know who or what that is.');
|
return [true, 'I don\'t know who or what that is.'];
|
||||||
}
|
}
|
||||||
return QuestionParser.success(concept.gist);
|
return [true, concept.gist];
|
||||||
}
|
}
|
||||||
return QuestionParser.success(instance.gist);
|
return [true, instance.gist];
|
||||||
}
|
}
|
||||||
|
|
||||||
whatDoes(t) {
|
whatDoes(t) {
|
||||||
@ -268,21 +256,20 @@ class QuestionParser {
|
|||||||
property = instance.property(fixedPropertyName);
|
property = instance.property(fixedPropertyName);
|
||||||
}
|
}
|
||||||
if (property) {
|
if (property) {
|
||||||
return QuestionParser.success(`${instance.name} ${fixedPropertyName} the ${property.type.name} ${property.name}.`);
|
return [true, `${instance.name} ${fixedPropertyName} the ${property.type.name} ${property.name}.`];
|
||||||
}
|
}
|
||||||
return QuestionParser.success(`Sorry - I don't know that property about the ${instance.type.name} ${instance.name}.`);
|
return [true, `Sorry - I don't know that property about the ${instance.type.name} ${instance.name}.`];
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return QuestionParser.success('Sorry - I can\'t work out what you\'re asking.');
|
return [false, 'Sorry - I can\'t work out what you\'re asking.'];
|
||||||
}
|
}
|
||||||
return QuestionParser.success('Sorry - I can\'t work out what you\'re asking about.');
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
whatRelationship(t) {
|
whatRelationship(t) {
|
||||||
const data = t.match(/^(\bwho\b|\bwhat\b) ([a-zA-Z0-9_ ]*)/i);
|
const data = t.match(/^(\bwho\b|\bwhat\b) ([a-zA-Z0-9_ ]*)/i);
|
||||||
const body = data[2].replace(/\ban\b/gi, '').replace(/\bthe\b/gi, '').replace(/\ba\b/gi, '');
|
const body = data[2].replace(/\ban\b/gi, '').replace(/\bthe\b/gi, '').replace(/\ba\b/gi, '');
|
||||||
const tokens = body.split(' ');
|
const tokens = body.split(' ');
|
||||||
const uniqueResponses = new Set([]);
|
|
||||||
let instance;
|
let instance;
|
||||||
for (let i = 0; i < tokens.length; i += 1) {
|
for (let i = 0; i < tokens.length; i += 1) {
|
||||||
const testString = tokens.slice(tokens.length - (i + 1), tokens.length).join(' ').trim();
|
const testString = tokens.slice(tokens.length - (i + 1), tokens.length).join(' ').trim();
|
||||||
@ -296,7 +283,6 @@ class QuestionParser {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance) {
|
if (instance) {
|
||||||
const propertyName = tokens.splice(0, tokens.length - instance.name.split(' ').length).join(' ').trim();
|
const propertyName = tokens.splice(0, tokens.length - instance.name.split(' ').length).join(' ').trim();
|
||||||
for (let i = 0; i < this.node.instances.length; i += 1) {
|
for (let i = 0; i < this.node.instances.length; i += 1) {
|
||||||
@ -318,16 +304,12 @@ class QuestionParser {
|
|||||||
property = subject.property(fixedPropertyName);
|
property = subject.property(fixedPropertyName);
|
||||||
}
|
}
|
||||||
if (property && property.name === instance.name) {
|
if (property && property.name === instance.name) {
|
||||||
uniqueResponses.add(`${subject.name} ${fixedPropertyName} the ${property.type.name} ${property.name}.`);
|
return [true, `${subject.name} ${fixedPropertyName} the ${property.type.name} ${property.name}.`];
|
||||||
}
|
|
||||||
const responsesArray = Array.from(uniqueResponses);
|
|
||||||
if (responsesArray.length > 0 && i === this.node.instances.length - 1) {
|
|
||||||
return QuestionParser.success(responsesArray.join(' '));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return QuestionParser.success(`Sorry - I don't know that property about the ${instance.type.name} ${instance.name}.`);
|
return [true, `Sorry - I don't know that property about the ${instance.type.name} ${instance.name}.`];
|
||||||
}
|
}
|
||||||
return QuestionParser.success('Sorry - I don\'t know the instance you\'re referring to.');
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
listInstances(t) {
|
listInstances(t) {
|
||||||
@ -346,16 +328,17 @@ class QuestionParser {
|
|||||||
s = 'All instances:';
|
s = 'All instances:';
|
||||||
}
|
}
|
||||||
if (ins.length === 0) {
|
if (ins.length === 0) {
|
||||||
return QuestionParser.success('I could not find any instances matching your query.');
|
return [true, 'I could not find any instances matching your query.'];
|
||||||
}
|
}
|
||||||
const names = [];
|
const names = [];
|
||||||
for (let i = 0; i < ins.length; i += 1) {
|
for (let i = 0; i < ins.length; i += 1) {
|
||||||
names.push(ins[i].name);
|
names.push(ins[i].name);
|
||||||
}
|
}
|
||||||
return QuestionParser.success(`${s} ${names.join(', ')}`);
|
return [true, `${s} ${names.join(', ')}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
*
|
||||||
* Search the knowledge base for an instance name similar to the one asked about.
|
* Search the knowledge base for an instance name similar to the one asked about.
|
||||||
*/
|
*/
|
||||||
fuzzySearch(sentence) {
|
fuzzySearch(sentence) {
|
||||||
@ -365,10 +348,12 @@ class QuestionParser {
|
|||||||
let instancesFiltered = [];
|
let instancesFiltered = [];
|
||||||
|
|
||||||
if (searchFor.indexOf(' ')) {
|
if (searchFor.indexOf(' ')) {
|
||||||
|
// if theres spaces then split
|
||||||
multipleSearch = searchFor.split(' ');
|
multipleSearch = searchFor.split(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multipleSearch) {
|
if (multipleSearch) {
|
||||||
|
// loop through to create return string
|
||||||
for (let x = 0; x < multipleSearch.length; x += 1) {
|
for (let x = 0; x < multipleSearch.length; x += 1) {
|
||||||
const instancesFilteredTemp = instances.filter((input) => {
|
const instancesFilteredTemp = instances.filter((input) => {
|
||||||
if (input.name.toUpperCase().includes(multipleSearch[x].toUpperCase())) {
|
if (input.name.toUpperCase().includes(multipleSearch[x].toUpperCase())) {
|
||||||
@ -379,6 +364,7 @@ class QuestionParser {
|
|||||||
instancesFiltered = instancesFiltered.concat(instancesFilteredTemp);
|
instancesFiltered = instancesFiltered.concat(instancesFilteredTemp);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// single search term
|
||||||
instancesFiltered = instances.filter((input) => {
|
instancesFiltered = instances.filter((input) => {
|
||||||
if (input.name.toUpperCase().includes(searchFor.toUpperCase())) {
|
if (input.name.toUpperCase().includes(searchFor.toUpperCase())) {
|
||||||
return input;
|
return input;
|
||||||
|
@ -64,15 +64,16 @@ class RuleEngine {
|
|||||||
if (typeof objectInstance === 'string') {
|
if (typeof objectInstance === 'string') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const ruleInstance of this.node.getInstances('rule')) {
|
const rules = this.node.getInstances('rule');
|
||||||
const rule = RuleEngine.parseRule(ruleInstance.instruction);
|
for (let i = 0; i < rules.length; i += 1) {
|
||||||
|
const rule = RuleEngine.parseRule(rules[i].instruction);
|
||||||
if (!rule) { return; }
|
if (!rule) { return; }
|
||||||
if (rule.if.concept === subjectInstance.type.name) {
|
if (rule.if.concept === subjectInstance.type.name) {
|
||||||
if ((propertyType === 'relationship' && rule.if.relationship) || (propertyType === 'value' && rule.if.value)) {
|
if ((propertyType === 'relationship' && rule.if.relationship) || (propertyType === 'value' && rule.if.value)) {
|
||||||
const ancestorConcepts = objectInstance.concept.ancestors;
|
const ancestorConcepts = objectInstance.type.ancestors;
|
||||||
ancestorConcepts.push(objectInstance.concept);
|
ancestorConcepts.push(objectInstance.type);
|
||||||
for (const ancestorConcept of ancestorConcepts) {
|
for (let j = 0; j < ancestorConcepts.length; j += 1) {
|
||||||
if (ancestorConcept.name.toLowerCase() === rule.if[propertyType].type.toLowerCase()) {
|
if (ancestorConcepts[j].name.toLowerCase() === rule.if[propertyType].type.toLowerCase()) {
|
||||||
if (rule.then.relationship && rule.then.relationship.type === subjectInstance.type.name) {
|
if (rule.then.relationship && rule.then.relationship.type === subjectInstance.type.name) {
|
||||||
objectInstance.addRelationship(rule.then.relationship.label, subjectInstance, false, source);
|
objectInstance.addRelationship(rule.then.relationship.label, subjectInstance, false, source);
|
||||||
} else if (rule.then.value && rule.then.value.type === subjectInstance.type.name) {
|
} else if (rule.then.value && rule.then.value.type === subjectInstance.type.name) {
|
||||||
|
95
src/TransformEngine.js
Normal file
95
src/TransformEngine.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 W.M. Webberley & A.D. Preece (Cardiff University)
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
class TransformEngine {
|
||||||
|
|
||||||
|
enable () {
|
||||||
|
this.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
disable () {
|
||||||
|
this.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate (instance, func) {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return 'Transforms disabled';
|
||||||
|
}
|
||||||
|
if (func.indexOf('require') > -1){
|
||||||
|
return 'Invalid function';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sandbox = {
|
||||||
|
name: instance.name,
|
||||||
|
type: instance.concept.name
|
||||||
|
};
|
||||||
|
for (const attr of instance.values.concat(instance.relationships)) {
|
||||||
|
const value = attr.instance.name ? attr.instance.name : attr.instance;
|
||||||
|
sandbox[attr.label.toLowerCase().replace(/ /g, '_').replace(/'/g, '')] = value;
|
||||||
|
}
|
||||||
|
if (typeof window !== 'undefined' && window.document) {
|
||||||
|
return function () {
|
||||||
|
return eval(func);
|
||||||
|
}.call(sandbox);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const vm = require('vm');
|
||||||
|
const script = new vm.Script(func);
|
||||||
|
const context = new vm.createContext(sandbox);
|
||||||
|
return script.runInContext(context, {timeout: 30});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err){
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enactTransforms(instance, label, targetInstance, source) {
|
||||||
|
let doTransform = false;
|
||||||
|
let transforms = [];
|
||||||
|
|
||||||
|
// Find out if the target property is a transform
|
||||||
|
if (targetInstance.concept && targetInstance.concept.name === 'transform'){
|
||||||
|
transforms = [targetInstance];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise check if the 'label' is a dependency of any existing transforms on this instance
|
||||||
|
else {
|
||||||
|
const newLabel = 'this.' + label.toLowerCase().replace(/ /g, '_').replace(/'/g, '');
|
||||||
|
for (const rel of instance.relationships){
|
||||||
|
if (rel.instance.concept && rel.instance.concept.name === 'transform' && rel.instance.transform_function && rel.instance.transform_function.indexOf(newLabel) > -1){
|
||||||
|
transforms.push(rel.instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const transform of transforms) {
|
||||||
|
const e = this.evaluate(instance, transform.transform_function);
|
||||||
|
if (e){
|
||||||
|
instance.addValue(transform.output, e.toString(), true, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(node) {
|
||||||
|
this.node = node;
|
||||||
|
this.enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = TransformEngine;
|
@ -150,6 +150,38 @@ describe('CEParser', function() {
|
|||||||
expect(node.instances.betty_hughes.lives_in.name).to.be('Camden');
|
expect(node.instances.betty_hughes.lives_in.name).to.be('Camden');
|
||||||
expect(node.instances.sally.lives_in.name).to.be('Kensington and Chelsea');
|
expect(node.instances.sally.lives_in.name).to.be('Kensington and Chelsea');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allow for transforms', () => {
|
||||||
|
node = new CENode(CEModels.core);
|
||||||
|
node.addCE('conceptualise a ~ person ~ P that is a transformer and has the value A as ~ first name ~ and has the value B as ~ last name ~ and has the value C as ~ full name ~');
|
||||||
|
node.addCE('there is a transform named t1 that has \'full name\' as output and has \'this.first_name + " " + this.last_name\' as transform function');
|
||||||
|
node.addCE('there is a person named p1 that has Jane as first name and has Smith as last name and uses the transform t1');
|
||||||
|
expect(node.instances.p1.full_name).to.be('Jane Smith');
|
||||||
|
node.addCE('the person p1 has Harry as first name');
|
||||||
|
expect(node.instances.p1.first_name).to.be('Harry');
|
||||||
|
expect(node.instances.p1.full_name).to.be('Harry Smith');
|
||||||
|
|
||||||
|
node.addCE('there is a transform named t2 that has date as output and has \'new Date(parseInt(this.name))\' as transform function');
|
||||||
|
node.addCE('there is a timestamp named \'715263762315\' that uses the transform t2');
|
||||||
|
expect(node.getInstanceByName('715263762315').date).to.contain('1992');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allow for transform chaining', () => {
|
||||||
|
const node = new CENode(CEModels.core);
|
||||||
|
node.addSentence("conceptualise a ~ person ~ P that is a transformer and has the value A as ~ first name ~ and has the value B as ~ last name ~ and has the value C as ~ full name ~ and has the value D as ~ username ~")
|
||||||
|
|
||||||
|
node.addSentence("there is a transform named t1 that has 'full name' as output and has 'this.first_name + \\' \\' + this.last_name' as transform function")
|
||||||
|
node.addSentence("there is a transform named t2 that has 'username' as output and has 'this.full_name.toLowerCase().replace(/ /g, \\'_\\')' as transform function");
|
||||||
|
|
||||||
|
node.addSentence("there is a person named p1 that has 'Jane' as first name and has 'Smith' as last name and uses the transform t1 and uses the transform t2");
|
||||||
|
|
||||||
|
expect(node.instances.p1.full_name).to.be('Jane Smith');
|
||||||
|
expect(node.instances.p1.username).to.be('jane_smith');
|
||||||
|
|
||||||
|
node.addSentence('the person p1 has \'Harry\' as first name');
|
||||||
|
expect(node.instances.p1.full_name).to.be('Harry Smith');
|
||||||
|
expect(node.instances.p1.username).to.be('harry_smith');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Specific Examples', function() {
|
describe('Specific Examples', function() {
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
const CENode = require('../src/CENode.js');
|
|
||||||
const CEModels = require('../models/index.js');
|
|
||||||
const expect = require('expect.js');
|
|
||||||
const myName = 'User'
|
|
||||||
const PLANETS_MODEL = [
|
|
||||||
"there is a rule named 'r1' that has 'if the planet C ~ orbits ~ the star D then the star D ~ is orbited by ~ the planet C' as instruction.",
|
|
||||||
"there is a rule named 'r2' that has 'if the planet C ~ is orbited by ~ the moon D then the moon D ~ orbits ~ the planet C' as instruction.",
|
|
||||||
"conceptualise a ~ celestial body ~ C.",
|
|
||||||
"conceptualise the celestial body C ~ orbits ~ the celestial body D and ~ is orbited by ~ the celestial body E.",
|
|
||||||
"conceptualise a ~ planet ~ P that is a celestial body and is an imageable thing.",
|
|
||||||
"conceptualise a ~ star ~ S that is a celestial body.",
|
|
||||||
"there is a star named sun.",
|
|
||||||
"there is a planet named Venus that orbits the star 'sun' and has 'media/Venus.jpg' as image.",
|
|
||||||
"there is a planet named Mercury that orbits the star 'sun' and has 'media/Mercury.jpg' as image."
|
|
||||||
]
|
|
||||||
|
|
||||||
let node;
|
|
||||||
describe('CEQuestionParser', function() {
|
|
||||||
describe('What relation questions', function () {
|
|
||||||
this.timeout(2050);
|
|
||||||
before(function() {
|
|
||||||
node = new CENode(CEModels.core, PLANETS_MODEL);
|
|
||||||
node.attachAgent();
|
|
||||||
node.agent.setName('agent1');
|
|
||||||
});
|
|
||||||
it('returns the correct number of responses', (done) => {
|
|
||||||
const message = 'what orbits the sun?';
|
|
||||||
const askCard = "there is a nl card named '{uid}' that is to the agent 'agent1' and is from the individual '" + myName + "' and has the timestamp '{now}' as timestamp and has '" + message.replace(/'/g, "\\'")+"' as content.";
|
|
||||||
|
|
||||||
node.addSentence(askCard);
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
const cards = node.concepts.card.allInstances;
|
|
||||||
const card = cards[cards.length - 1];
|
|
||||||
expect(card.content).to.equal('Venus orbits the star sun. Mercury orbits the star sun.');
|
|
||||||
done();
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in New Issue
Block a user