I'm learning Angular 2, trying to build an expandable tree-view from a (potentially very large) third-party API. The API has an underlying structure like this:
- Home (id: 1053)
- - Rugby League (id: 1054)
- - - Super League (id: 1103)
- - - - Castleford Tigers (id: 1111)
- - - - Catalans Dragons (id: 1110)
- - - - Huddersfield Giants (id: 1116)
- - - - Hull FC (id: 1108)
- - - Championship (id: 1104)
- - - - Batley Bulldogs (id: 1120)
- - - - Bradford Bulls (id: 1118)
- - - - Dewsbury Rams (id: 1124)
- - - - Featherstone Rovers (id: 1121)
- - Football (id: 1056)
- - - Premier League (id: 1057)
- - - - AFC Bournemouth (id: 1059)
- - - - etc
- - - - etc
The API is set up such that I pass an id and it returns a simple JSON array of the children (only) of that node. So, for example I call: http://www.example.com/api/contentNodes/?parentId=1053 and it returns:
[
{"Id":1054,"Name":"Rugby League","HasChildren":true},
{"Id":1056,"Name":"Football","HasChildren":true}
]
(HasChildren represents whether or not the node has child nodes.)
Note, because the data-set will eventually be large I want to 'pull in' more data from the API progressively as the tree branches are opened, rather than dumping the entire data-set in there and rendering that out in my app.
I've set up an Angular 2 app which can be seen here: http://plnkr.co/edit/QQ1OKCbd4pDptpSVbWch?p=preview
The key component is the `app/content-list.component.ts' file:
import {Component, OnInit} from 'angular2/core';
import {ContentNode} from './content-node';
import {ContentService} from './content.service';
#Component({
selector: 'content-list',
template: `
<ol class="tree">
<li *ngFor="#contentNode of contentNodes" class="tree__branch" [ngClass]="{'tree__branch--has-children': contentNode.HasChildren}">
<a *ngIf="contentNode.HasChildren" (click)="toggleBranch(contentNode.Id)" class="toggle">+</a> {{ contentNode.Name }}
</li>
</ol>
<div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
`
})
export class ContentListComponent implements OnInit {
constructor (private _contentService: ContentService) {}
errorMessage: string;
private _startNodeId: number = 1053;
contentNodes: ContentNode[];
ngOnInit() {
this.getContentNodes();
}
getContentNodes() {
this._contentService.getContentNodes(this._startNodeId)
.subscribe(
contentNodes => this.contentNodes = contentNodes,
error => this.errorMessage = <any>error
);
}
toggleBranch(branchId:number){
console.log('branchId: ' + branchId);
}
}
You'll see here that I'm calling my service which returns the JSON as above, by being passed a parentId of 1053.
I've now hit a wall in being able to progressively load the child nodes of the treeview when the + button is clicked, into the nested HTML list (<ol>).
What would be the best approach here, to achieve this in a really neat way?
My next step will be to ensure that the app doesn't make excessive API calls, but my immediate concern is just to get a running treeview hooked up and working.
I've seen this example of a recursive treeview but it seems (a) a little buggy (in that there are empty <ol></ol> elements rendered in the HTML when child nodes are empty etc); and (b) it seems to be set up in a very 'hard-coded' kind of a way and I'm not experienced enough to confidently refactor it.
Many thanks.
Note, for security reasons I can't open up the API to public requests unfortunately, which makes testing this on Plunkr a little difficult, I realise. For the moment my example uses just a static, single level JSON data-set.
in Angular2 you can render directives recursively. This makes rendering the tree very easy.
I've modified your Plunker a little bit just to show the point. It's not the ideal implementation but it works as expected :).
Example:
#Component({
selector: 'tree-view',
template: `
<div *ngFor="#dir of dirs">
<tree-view [dirs]="dir.dirs"></tree-view>
<div>
`,
directives: [TreeView]
})
export class TreeView {
#Input()
private dirs: Array<Directory>;
}
I hope you will like it.
Cheers!
Related
I have an intent as below which will fetch vaccination details from the database. this intent requires two slots(country, vaccine) to be filled, if no slots or at least one slot is filled, I am using different stories to ask the user to fill the slots, then it will show vaccination details at last.
I am using rasa: 1.10.2 for this chatbot.
## intent:vaccination_details
- get vaccination details from [USA](country) for [Covishield](vaccine)
- show vaccination details
- show me [Covishield](vaccine) vaccination details in [USA](country)
- get vaccination details from [USA](country)
- show [Covishield](vaccine) vaccination details
I am using the following stories to fill the slots, here action_select_vaccine and action_select_country are custom actions that fetch a list of available vaccines and country lists from the database as buttons for the user to select.
Since it requires only two slots to be filled from actions, I am not using RASA forms. Can we merge these stories into one or two without using forms?
## Story_1: vaccine,country not given
* vaccination_details
- action_select_vaccine
* select_vaccine{"vaccine":"Covishield"}
- slot{"vaccine":"Covishield"}
- action_select_country
* select_country{"country":"USA"}
- slot{"country":"USA"}
- action_vaccination_details
## Story_2: country not given
* vaccination_details{"vaccine":"Covishield"}
- slot{"vaccine":"Covishield"}
- action_select_country
* select_country{"country":"USA"}
- slot{"country":"USA"}
- action_vaccination_details
## Story_3: vaccine not given
* vaccination_details{"country":"USA"}
- slot{"country":"USA"}
- action_select_vaccine
* select_vaccine{"vaccine":"Covishield"}
- slot{"vaccine":"Covishield"}
- action_vaccination_details
## Story_4: vaccine,country given
* vaccination_details{"country":"USA","vaccine":"Covishield"}
- slot{"country":"USA"}
- slot{"vaccine":"Covishield"}
- action_vaccination_details
am using rasa 1.9.5.
I have a form that gets activated and it will ask for the slots to fill. When it is asking for a slot to fill it's calling utter_slots_name. But my requirement is, I need to call custom action instead like action_slots_name.
I need to call custom action for all slot filling questions.
NLU:
## intent:greet
- Hi
- Hello
- I need a help
- just need help
- can you server me
## intent: greeting_with_name
- Hi I am [sharath](name), I need some services
- Hi myself [sharath](name), Need some services
- Hi this is [sharath](name), need some services
## intent: are_u_bot
- are you a bot?
- are you a human?
- am I talking to a bot?
- am I talking to a human?
## intent: my_name_is
- my name is [sharath](name)
- [sharath](name)
- I am [sharath](name)
- Myself [sharath](name)
Stories:
## bot challenge
* bot_challenge
- utter_iamabot
## happy path
* greet
- action_greeting
- user_details
- form{"name":"user_details"}
- form{"name":null}
- action_askfor_services
Domain:
actions:
- action_askfor_services
- action_slots_name
- user_details
- action_greeting
intents:
- greet
- bot_challenge
- my_name_is
- greeting_with_name
form:
- user_details
entities:
- name
- phoneNo
- tenentId
- language
slots:
language:
type: text
name:
type: text
phoneNo:
type: text
tenentId:
type: text
requested_slot:
type: text
responses:
utter_iamabot:
- text: I am a bot, powered by Rasa.
utter_bye:
- text: bye
session_config:
carry_over_slots_to_new_session: true
session_expiration_time: 60
I'm trying to integrate a form in my rasa chatbot.
In domain.yml, I have included the following:
I declared the slots:
slots:
question1:
type: text
question2:
type: text
question3:
type: text
the questions that the form is supposed to send to the user:
utter_ask_question1:
- text:" first question goes here"
utter_ask_question2:
- text:" second question goes here"
utter_ask_question3:
- text:" third question goes here"
defined the form as:
forms:
user_quiz_form:
question1:
- type: from_text
# entity: date
question2:
- type: from_text
question3:
- type: from_text
the actions section also contains :
actions:
- action_submit
- user_quiz_form
The file rules.yml contains:
- rule: Activate quiz form
steps:
- intent: quiz
- action: utter_quiz
- action: user_quiz_form
- active_loop: user_quiz_form
- rule: Submit quiz form
condition:
- active_loop: user_quiz_form
steps:
- action: user_quiz_form
- active_loop: null
- slot_was_set:
- requested_slot: null
- action: action_submit
and actions.py is:
from typing import Any, Text, Dict, List
from rasa_sdk import Action, Tracker
from rasa_sdk.events import SlotSet, EventType
from rasa_sdk.executor import CollectingDispatcher
import webbrowser
class ValidateForm(Action):
def name(self) -> Text:
return "user_quiz_form"
def run(
self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
) -> List[EventType]:
required_slots = ["question1","question2", "question3"]
for slot_name in required_slots:
if tracker.slots.get(slot_name) is None:
# The slot is not filled yet. Request the user to fill this slot next.
return [SlotSet("requested_slot", slot_name)]
return [SlotSet("requested_slot", None)]
class ActionSubmit(Action):
def name(self) -> Text:
return "action_submit"
def run(
self,
dispatcher,
tracker: Tracker,
domain: "DomainDict",
) -> List[Dict[Text, Any]]:
print("****************SUBMIT*****************")
dispatcher.utter_message(template="utter_quiz_thanks", date=tracker.slots.get("question1"))
return []
When the form is triggered, it doesn't' ask the questions to the user and returns utter_quiz_thanks with None as question1 value.
Is this the same indentation you're using in your domain.yml? If so, you're missing some indents; the slots underneath the form need to be indented (see below for an example). My guess is that you're launching the form correctly, but due to the indent issues your assistant is treating it as a form with 0 questions.
forms:
restaurant_form:
cuisine:
- type: from_entity
entity: cuisine
num_people:
- type: from_entity
entity: number
I followed the Rasa masterclass and have the following setup:
data/nlu.md:
## intent:search_provider
- I want to go to a [hospital] (facilitytype)
- I am sick need to go to a [hospital] (facilitytype)
- Can you tell me how to get to a [hospital] (facilitytype)
rasa train:
2020-01-17 19:06:36 INFO rasa.model - Data (nlu-config) for NLU model changed.
Core stories/configuration did not change. No need to retrain Core model.
Training NLU model...
2020-01-17 19:06:36 INFO rasa.nlu.training_data.loading - Training data format of /var/folders/2p/c7zvhbtj4dz0p053y49fmr_h0000gp/T/tmphtg4ok5r/68c2a3993fe141b199b22b8cac047519_nlu.md is md
2020-01-17 19:06:36 INFO rasa.nlu.training_data.training_data - Training data stats:
- intent examples: 50 (8 distinct intents)
- Found intents: 'goodbye', 'affirm', 'inform', 'search_provider', 'mood_unhappy', 'greet', 'mood_great', 'deny'
**- entity examples: 0 (0 distinct entities)
- found entities:**
When I run the nlp model, it detects the right intent, but cannot extract any entities, not sure what I am missing :
Next message:
I want to go to a hospital
{
"intent": {
"name": "search_provider",
"confidence": 0.9632793664932251
},
"entities": [],
My pipeline has the following line in the config.yml:
pipeline: supervised_embeddings
#user12735193 Can you specify the exact Rasa version you are using?
Also, please don't add a space between the [entity value] and (entity type). It should be like this - [entity value](entity type) and not [entity value] (entity type). I think that should fix it for you.
As mentioned by #dragster, there shouldn't be a space between entity value and type:
## intent:inform
- [NEW YORK](city)
- I am going home to [Detroit](city)
I do not understand how the slot mapping does to know which "utterance" to answer the user to get the "entity" requested.
example of my form class:
class RestaurantForm(FormAction):
"""Example of a custom form action"""
def name(self):
# type: () -> Text
"""Unique identifier of the form"""
return "formaejemplo"
#staticmethod
def required_slots(tracker):
# type: () -> List[Text]
"""A list of required slots that the form has to fill"""
return ["valor1","valor2","valor3"]
def slot_mappings(self):
return {"valor1": self.from_entity(entity="valor1",intent="getvalor1"),
"valor2": self.from_entity(entity="valor2",intent="getvalor2"),
"valor3": self.from_entity(entity="valor3",intent="getvalor3")}
def submit(self, dispatcher, tracker, domain):
dispatcher.utter_template('utter_listo', tracker)
return []
domain.yml:
intents:
- peticion_habitacion:
use_entities: false
- getvalor1
- getvalor2
- getvalor3
entities:
- valor1
- valor2
- valor3
slots:
valor1:
type: unfeaturized
auto_fill: false
valor2:
type: unfeaturized
auto_fill: false
valor3:
type: unfeaturized
auto_fill: false
actions:
- utter_prueba
- utter_completo
templates:
utter_completo:
- text: "listo:\nvalor 1 {valor1} \nvalor 2 {valor2} \nvalor 3 {valor3}"
utter_prueba:
- text: "iniciando prueba:\n"
utter_valor1:
- text: "dame el valor 1 no enteros"
utter_valor2:
- text: "dame el valor 2 no enteros"
utter_valor3:
- text: "dame el valor 3 no enteros"
utter_listo:
- text: "prueba completa"
forms:
- formaejemplo
in the section where you get the value1, value2 etc ... it is according to the Rasa documentation: "valor1": self.from_entity (entity = "valor1", intent = "getvalor1" "the "valor 1" will be obtained from the intent getvalor1."
my question is, at what time or in what part or what file, the action form is told that it will have to send the "utterance" "utter_valor1" or "utter_valor2", because to several Internet examples plus the same examples of bots of rasa, I see that these send the utterance and then recover the value, but I can not get to understand how they send the utterance and then get the value
I assume you mean how the action sdk determines which template it should use to ask for a requested slot, right?
This logic is actually hardcoded here: https://github.com/RasaHQ/rasa_core_sdk/blob/cfffaac0013606f7614ab0f213bc39623ee8b53c/rasa_core_sdk/forms.py#L374
What it does is simply dispatches an utterance which is utter_ask_{the name of slot which should be requested}.
If the user then sends back his answer, the form action is triggered again and the slot value can be extracted.