-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
285 file sharing #59
285 file sharing #59
Changes from all commits
c38a602
be40a7a
a87b6ec
0ac7491
17e19ed
173a8e7
e5fe6d0
db6277c
00bd761
150e84e
8d03a8c
1684263
06a28c4
533f1d5
a6cfbda
73342ac
11b00cb
a631193
9acab2a
3f727d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
import logging | ||
|
||
from celery import Celery | ||
from niceday_client import NicedayClient | ||
from rasa_sdk import Action | ||
from rasa_sdk.events import FollowupAction | ||
from .definitions import REDIS_URL | ||
|
||
from rasa_sdk.events import FollowupAction, SlotSet | ||
from .definitions import REDIS_URL, NICEDAY_API_ENDPOINT | ||
|
||
celery = Celery(broker=REDIS_URL) | ||
|
||
|
@@ -37,3 +37,60 @@ async def run(self, dispatcher, tracker, domain): | |
logging.info("no celery error") | ||
|
||
return [] | ||
|
||
|
||
class SendMetadata(Action): | ||
def name(self): | ||
return "action_send_metadata" | ||
|
||
async def run(self, dispatcher, tracker, domain): | ||
""" | ||
Sends the text message specified in the 'text' value of the json_message, | ||
and sends the image identified by the id_file under the 'attachmentIds' key. | ||
|
||
The id_file is obtained in the action_upload_file as a result of | ||
a file uploaded to the NiceDay server. The id is stored in the | ||
uploaded_file_id slot. | ||
|
||
The uploaded file is a local one, and is uses the file indicated | ||
by the action_set_file_path | ||
""" | ||
id_file = tracker.get_slot("uploaded_file_id") | ||
dispatcher.utter_message( | ||
json_message={"text": "image", | ||
"attachmentIds": [id_file]}, | ||
) | ||
return[] | ||
|
||
|
||
class UploadFile(Action): | ||
def name(self): | ||
return "action_upload_file" | ||
|
||
async def run(self, dispatcher, tracker, domain): | ||
client = NicedayClient(NICEDAY_API_ENDPOINT) | ||
user_id = int(tracker.current_state()['sender_id']) | ||
|
||
filepath = tracker.get_slot('upload_file_path') | ||
with open(filepath, 'rb') as content: | ||
file = content.read() | ||
|
||
response = client.upload_file(user_id, filepath, file) | ||
file_id = response['id'] | ||
logging.info(response) | ||
logging.info(file_id) | ||
|
||
return[SlotSet("uploaded_file_id", file_id)] | ||
|
||
|
||
class SetFilePath(Action): | ||
def name(self): | ||
return "action_set_file_path" | ||
|
||
async def run(self, dispatcher, tracker, domain): | ||
|
||
# TODO: This is hardcoded for testing. Needs to be set according to the use case | ||
|
||
filepath = '/app/tst.PNG' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not a random choice XD |
||
|
||
return[SlotSet("upload_file_path", filepath)] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,16 @@ | |
""" | ||
import datetime | ||
import logging | ||
from typing import Text, Dict, Any | ||
|
||
from dateutil.relativedelta import relativedelta | ||
from dateutil.rrule import rrule, DAILY | ||
from niceday_client import NicedayClient, definitions | ||
from paalgorithms import weekly_kilometers | ||
from rasa_sdk import Action | ||
from rasa_sdk import Action, Tracker | ||
from rasa_sdk.events import SlotSet | ||
from rasa_sdk.executor import CollectingDispatcher | ||
from rasa_sdk.forms import FormValidationAction | ||
from virtual_coach_db.dbschema.models import Users | ||
from virtual_coach_db.helper.helper_functions import get_db_session | ||
|
||
|
@@ -175,3 +178,36 @@ async def run(self, dispatcher, tracker, domain): | |
"This is a tracker", | ||
recursive_rule) | ||
return[] | ||
|
||
|
||
class ValidateActivityGetFileForm(FormValidationAction): | ||
def name(self) -> Text: | ||
return 'validate_activity_get_file_form' | ||
|
||
def validate_received_file_text( | ||
self, slot_value: Text, dispatcher: CollectingDispatcher, | ||
tracker: Tracker, domain: Dict[Text, Any]) -> Dict[Text, Any]: | ||
# pylint: disable=unused-argument | ||
"""Validate the presence of an attached file""" | ||
|
||
# here the message metadata are retrieved | ||
events = tracker.current_state()['events'] | ||
|
||
user_events = [e for e in events if e['event'] == 'user'] | ||
# we defined the metadata key name 'attachmentIds' is defined in the broker | ||
received_file_ids_list = user_events[-1]['metadata']['attachmentIds'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the last event always contains the list of received files? Is there an async problem possible where two images are sent simultaneously and the user_events list contains a mess of events for the two sends? (apologies if I've misunderstood what's happening here) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a validation action that runs on user text input and the new message structure created in the custom channels always contains the metadata (even if the files list is empty). So, in theory we always get this. But theory and reality do not always match. It is a good idea to surround this with some verification. This action runs on a single message, so the is no possibility to mess with this action. |
||
|
||
if not self._is_valid_input(received_file_ids_list): | ||
dispatcher.utter_message(response="utter_no_attachment") | ||
return {"received_file_text": None} | ||
|
||
# At this point the file ID should be saved in the DB for | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we the ones sending these images, or will the user ever send images to us? Will the images ever be sensitive data that we need to be extra careful with in the db? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess what I'm wondering is, can anyone with the file ID fetch the file? Is it stored on Niceday servers? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, the files are stored in the NiceDay server, and for accessing them the id and the access token are needed. |
||
# re-accessing the shared file | ||
|
||
return {"received_file_text": slot_value} | ||
|
||
@staticmethod | ||
def _is_valid_input(value): | ||
if len(value) == 0: | ||
return False | ||
return True |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,51 @@ | ||
import inspect | ||
from typing import Text, Callable, Awaitable, Any | ||
import os | ||
import typing | ||
from typing import Text, Callable, Awaitable, Any, Dict, List | ||
|
||
from rasa.core.channels.channel import InputChannel, OutputChannel, UserMessage | ||
from rasa.core.channels.channel import InputChannel, UserMessage, CollectingOutputChannel | ||
from sanic import Blueprint, response | ||
from sanic.request import Request | ||
from sanic.response import HTTPResponse | ||
|
||
from niceday_client import NicedayClient | ||
|
||
NICEDAY_API_URL = 'http://niceday_api:8080/' | ||
NICEDAY_API_URL = os.getenv('NICEDAY_API_ENDPOINT') | ||
|
||
|
||
class NicedayOutputChannel(OutputChannel): | ||
class NicedayOutputChannel(CollectingOutputChannel): | ||
""" | ||
Output channel that sends messages to Niceday server | ||
""" | ||
def __init__(self): | ||
self.niceday_client = NicedayClient(niceday_api_uri=NICEDAY_API_URL) | ||
|
||
@classmethod | ||
def name(cls) -> Text: | ||
return "niceday_output_channel" | ||
|
||
async def send_text_message( | ||
self, recipient_id: Text, text: Text, **kwargs: Any | ||
) -> None: | ||
"""Send a message through this channel.""" | ||
for message_part in text.strip().split("\n\n"): | ||
self.niceday_client.post_message(int(recipient_id), message_part) | ||
def _message(self, # pylint: disable=too-many-arguments, arguments-renamed | ||
recipient_id: typing.Optional[str], | ||
text: typing.Optional[str] = None, | ||
image: typing.Optional[str] = None, | ||
buttons: typing.Optional[List[Dict[str, Any]]] = None, | ||
attachment: typing.Optional[str] = None, | ||
custom: typing.Optional[Dict[str, Any]] = None | ||
) -> Dict: | ||
msg_metadata = None | ||
if custom is not None: | ||
text = custom["text"] | ||
msg_metadata = custom["attachmentIds"] | ||
custom = None | ||
obj = { | ||
"recipient_id": recipient_id, | ||
"text": text, | ||
"image": image, | ||
"buttons": buttons, | ||
"attachment": attachment, | ||
"custom": custom, | ||
"metadata": msg_metadata | ||
} | ||
|
||
# filter out any values that are `None` | ||
return {k: v for k, v in obj.items() if v is not None} | ||
|
||
|
||
class NicedayInputChannel(InputChannel): | ||
|
@@ -57,18 +75,22 @@ async def health(request: Request) -> HTTPResponse: # pylint: disable=unused-ar | |
async def receive(request: Request) -> HTTPResponse: | ||
sender_id = request.json.get("sender") # method to get sender_id | ||
text = request.json.get("message") # method to fetch text | ||
|
||
metadata = request.json.get("metadata") | ||
collector = self.get_output_channel() | ||
await on_new_message( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this ever timeout (or fail in some way)? Does something cause the app to retry if that happens? (this is just out of curiosity) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This contacts the Rasa server, that has a timeout. But if it fails for whatever reason, at the moment we do nothing, you are right There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This contacts the Rasa server, that has a timeout. But if it fails for whatever reason, at the moment we do nothing, you are right |
||
UserMessage(text, collector, sender_id, input_channel=self.name()) | ||
UserMessage(text, | ||
collector, | ||
sender_id, | ||
input_channel=self.name(), | ||
metadata=metadata) | ||
) | ||
return response.text("success") | ||
return response.json(collector.messages) | ||
|
||
return custom_webhook | ||
|
||
def get_output_channel(self) -> OutputChannel: | ||
def get_output_channel(self) -> CollectingOutputChannel: | ||
""" | ||
Register output channel. This is the output channel that is used when calling the | ||
'trigger_intent' endpoint. | ||
""" | ||
return self.output_channel | ||
return NicedayOutputChannel() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,35 @@ | ||
intents: | ||
- urgent | ||
- request_metadata | ||
slots: | ||
### Id obtained from NiceDay when uploading a file | ||
uploaded_file_id: | ||
type: float | ||
initial_value: 0 | ||
influence_conversation: false | ||
mappings: | ||
- type: custom | ||
|
||
### Id obtained from NiceDay when uploading a file | ||
upload_file_path: | ||
type: text | ||
influence_conversation: false | ||
mappings: | ||
- type: custom | ||
actions: | ||
- action_end_dialog | ||
- mark_dialog_as_completed | ||
- action_handle_urgent_intent | ||
- action_upload_file | ||
- action_send_metadata | ||
- action_set_file_path | ||
|
||
|
||
responses: | ||
# To refer a user to a human | ||
utter_refer: | ||
- text: "Volgens mij heb je een probleem waar een mens je beter mee kan helpen. Ik raad je aan om contact op te nemen met iemand die je vertrouwt, zoals je huisarts." | ||
- text: "Volgens mij heb je een probleem waar een mens je beter mee kan helpen. Ik raad je aan om contact op te nemen met iemand die je vertrouwt, zoals je huisarts." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this for when the user shares a really horrible image? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could absolutely use it in this way! |
||
|
||
## send a file | ||
utter_file_sent: | ||
- text: "Here is your file" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
intents: | ||
- user_shares_image | ||
|
||
slots: | ||
received_file_text: | ||
type: float | ||
influence_conversation: true | ||
mappings: | ||
- type: from_text | ||
conditions: | ||
- active_loop: activity_get_file_form | ||
|
||
|
||
responses: | ||
utter_send_me_a_file: | ||
- text: "Now I ask for a file" | ||
utter_ask_activity_get_file_form_received_file_text: | ||
- text: "Send me a file" | ||
utter_file_received: | ||
- text: "I received a file" | ||
utter_no_attachment: | ||
- text: "I see no attached files" | ||
|
||
actions: | ||
- validate_activity_get_file_form | ||
|
||
forms: | ||
activity_get_file_form: | ||
ignored_intents: | ||
- user_shares_image | ||
required_slots: | ||
- received_file_text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be overkill, but a few unrelated actions could all end up together in
actions_common
, so I would add a short doctring here making clear that theSendMetadata
relates to theUploadFile
andSetFilePath
actions (at least, I assume they do)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely a good suggestion. It will help in understanding how to use such actions in the future.