Skip to content
Draft
19 changes: 19 additions & 0 deletions bots/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@
"slack_create_personal_channels",
]
web_fields = ["web_allowed_origins", "web_config_extras"]
twilio_fields = [
"twilio_account_sid",
"twilio_auth_token",
"twilio_phone_number",
"twilio_phone_number_sid",
"twilio_default_to_gooey_asr",
"twilio_default_to_gooey_tts",
"twilio_voice",
"twilio_asr_language",
"twilio_initial_text",
"twilio_initial_audio_url",
"twilio_use_missed_call",
"twilio_waiting_audio_url",
"twilio_waiting_text",
]


class BotIntegrationAdminForm(forms.ModelForm):
Expand Down Expand Up @@ -133,12 +148,14 @@ class BotIntegrationAdmin(admin.ModelAdmin):
"slack_channel_name",
"slack_channel_hook_url",
"slack_access_token",
"twilio_phone_number",
]
list_display = [
"name",
"get_display_name",
"platform",
"wa_phone_number",
"twilio_phone_number",
"created_at",
"updated_at",
"billing_account_uid",
Expand Down Expand Up @@ -193,6 +210,7 @@ class BotIntegrationAdmin(admin.ModelAdmin):
*wa_fields,
*slack_fields,
*web_fields,
*twilio_fields,
]
},
),
Expand Down Expand Up @@ -489,6 +507,7 @@ class ConversationAdmin(admin.ModelAdmin):
"slack_user_name",
"slack_channel_id",
"slack_channel_name",
"twilio_phone_number",
] + [f"bot_integration__{field}" for field in BotIntegrationAdmin.search_fields]
actions = [export_to_csv, export_to_excel]

Expand Down
89 changes: 89 additions & 0 deletions bots/migrations/0077_botintegration_twilio_account_sid_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Generated by Django 4.2.7 on 2024-07-11 01:30

from django.db import migrations, models
import phonenumber_field.modelfields


class Migration(migrations.Migration):

dependencies = [
('bots', '0076_alter_workflowmetadata_default_image_and_more'),
]

operations = [
migrations.AddField(
model_name='botintegration',
name='twilio_account_sid',
field=models.TextField(blank=True, default='', help_text='Twilio account sid as found on twilio.com/console (mandatory)'),
),
migrations.AddField(
model_name='botintegration',
name='twilio_asr_language',
field=models.TextField(default='en-US', help_text='The language to use for Twilio ASR (https://www.twilio.com/docs/voice/twiml/gather#languagetags)'),
),
migrations.AddField(
model_name='botintegration',
name='twilio_auth_token',
field=models.TextField(blank=True, default='', help_text='Twilio auth token as found on twilio.com/console (mandatory)'),
),
migrations.AddField(
model_name='botintegration',
name='twilio_default_to_gooey_asr',
field=models.BooleanField(default=False, help_text="If true, the bot will use Gooey ASR for speech recognition instead of Twilio's when available on the attached run"),
),
migrations.AddField(
model_name='botintegration',
name='twilio_default_to_gooey_tts',
field=models.BooleanField(default=False, help_text="If true, the bot will use Gooey TTS for text to speech instead of Twilio's when available on the attached run"),
),
migrations.AddField(
model_name='botintegration',
name='twilio_initial_audio_url',
field=models.TextField(blank=True, default='', help_text='The initial audio url to play to the user when a call is started'),
),
migrations.AddField(
model_name='botintegration',
name='twilio_initial_text',
field=models.TextField(blank=True, default='', help_text='The initial text to send to the user when a call is started'),
),
migrations.AddField(
model_name='botintegration',
name='twilio_phone_number',
field=phonenumber_field.modelfields.PhoneNumberField(blank=True, default='', help_text='Twilio unformatted phone number as found on twilio.com/console/phone-numbers/incoming (mandatory)', max_length=128, region=None),
),
migrations.AddField(
model_name='botintegration',
name='twilio_phone_number_sid',
field=models.TextField(blank=True, default='', help_text='Twilio phone number sid as found on twilio.com/console/phone-numbers/incoming (mandatory)'),
),
migrations.AddField(
model_name='botintegration',
name='twilio_use_missed_call',
field=models.BooleanField(default=False, help_text="If true, the bot will reject incoming calls and call back the user instead so they don't get charged for the call"),
),
migrations.AddField(
model_name='botintegration',
name='twilio_voice',
field=models.TextField(default='woman', help_text="The voice to use for Twilio TTS ('man', 'woman', or Amazon Polly/Google Voices: https://www.twilio.com/docs/voice/twiml/say/text-speech#available-voices-and-languages)"),
),
migrations.AddField(
model_name='botintegration',
name='twilio_waiting_audio_url',
field=models.TextField(blank=True, default='', help_text='The audio url to play to the user while waiting for a response if using voice'),
),
migrations.AddField(
model_name='botintegration',
name='twilio_waiting_text',
field=models.TextField(blank=True, default='', help_text='The text to send to the user while waiting for a response if using sms'),
),
migrations.AddField(
model_name='conversation',
name='twilio_phone_number',
field=phonenumber_field.modelfields.PhoneNumberField(blank=True, default='', help_text="User's Twilio phone number (mandatory)", max_length=128, region=None),
),
migrations.AlterField(
model_name='botintegration',
name='platform',
field=models.IntegerField(choices=[(1, 'Facebook Messenger'), (2, 'Instagram'), (3, 'WhatsApp'), (4, 'Slack'), (5, 'Web'), (6, 'Twilio')], help_text='The platform that the bot is integrated with'),
),
]
94 changes: 93 additions & 1 deletion bots/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,14 @@ class Platform(models.IntegerChoices):
WHATSAPP = (3, "WhatsApp")
SLACK = (4, "Slack")
WEB = (5, "Web")
TWILIO = (6, "Twilio")

def get_icon(self):
match self:
case Platform.WEB:
return f'<i class="fa-regular fa-globe"></i>'
case Platform.TWILIO:
return f'<img src="https://storage.googleapis.com/dara-c1b52.appspot.com/daras_ai/media/73d11836-3988-11ef-9e06-02420a00011a/favicon-32x32.png" style="height: 1.2em; vertical-align: middle;">'
case _:
return f'<i class="fa-brands fa-{self.name.lower()}"></i>'

Expand Down Expand Up @@ -622,6 +625,67 @@ class BotIntegration(models.Model):
help_text="Extra configuration for the bot's web integration",
)

twilio_account_sid = models.TextField(
blank=True,
default="",
help_text="Twilio account sid as found on twilio.com/console (mandatory)",
)
twilio_auth_token = models.TextField(
blank=True,
default="",
help_text="Twilio auth token as found on twilio.com/console (mandatory)",
)
twilio_phone_number = PhoneNumberField(
blank=True,
default="",
help_text="Twilio unformatted phone number as found on twilio.com/console/phone-numbers/incoming (mandatory)",
)
twilio_phone_number_sid = models.TextField(
blank=True,
default="",
help_text="Twilio phone number sid as found on twilio.com/console/phone-numbers/incoming (mandatory)",
)
twilio_default_to_gooey_asr = models.BooleanField(
default=False,
help_text="If true, the bot will use Gooey ASR for speech recognition instead of Twilio's when available on the attached run",
)
twilio_default_to_gooey_tts = models.BooleanField(
default=False,
help_text="If true, the bot will use Gooey TTS for text to speech instead of Twilio's when available on the attached run",
)
twilio_voice = models.TextField(
default="woman",
help_text="The voice to use for Twilio TTS ('man', 'woman', or Amazon Polly/Google Voices: https://www.twilio.com/docs/voice/twiml/say/text-speech#available-voices-and-languages)",
)
twilio_initial_text = models.TextField(
default="",
blank=True,
help_text="The initial text to send to the user when a call is started",
)
twilio_initial_audio_url = models.TextField(
default="",
blank=True,
help_text="The initial audio url to play to the user when a call is started",
)
twilio_use_missed_call = models.BooleanField(
default=False,
help_text="If true, the bot will reject incoming calls and call back the user instead so they don't get charged for the call",
)
twilio_waiting_audio_url = models.TextField(
default="",
blank=True,
help_text="The audio url to play to the user while waiting for a response if using voice",
)
twilio_waiting_text = models.TextField(
default="",
blank=True,
help_text="The text to send to the user while waiting for a response if using sms",
)
twilio_asr_language = models.TextField(
default="en-US",
help_text="The language to use for Twilio ASR (https://www.twilio.com/docs/voice/twiml/gather#languagetags)",
)

streaming_enabled = models.BooleanField(
default=False,
help_text="If set, the bot will stream messages to the frontend (Slack & Web only)",
Expand Down Expand Up @@ -667,6 +731,7 @@ def get_display_name(self):
or " | #".join(
filter(None, [self.slack_team_name, self.slack_channel_name])
)
or (self.twilio_phone_number and self.twilio_phone_number.as_international)
or self.name
or (
self.platform == Platform.WEB
Expand Down Expand Up @@ -699,6 +764,21 @@ def get_web_widget_config(self, target="#gooey-embed") -> dict:
)
return config

def translate(self, text: str) -> str:
from daras_ai_v2.asr import run_google_translate, should_translate_lang

if text and should_translate_lang(self.user_language):
active_run = self.get_active_saved_run()
return run_google_translate(
[text],
self.user_language,
glossary_url=(
active_run.state.get("output_glossary") if active_run else None
),
)[0]
else:
return text


class BotIntegrationAnalysisRun(models.Model):
bot_integration = models.ForeignKey(
Expand Down Expand Up @@ -769,7 +849,12 @@ class ConversationQuerySet(models.QuerySet):
def get_unique_users(self) -> "ConversationQuerySet":
"""Get unique conversations"""
return self.distinct(
"fb_page_id", "ig_account_id", "wa_phone_number", "slack_user_id"
"fb_page_id",
"ig_account_id",
"wa_phone_number",
"slack_user_id",
"twilio_phone_number",
"web_user_id",
)

def to_df(self, tz=pytz.timezone(settings.TIME_ZONE)) -> "pd.DataFrame":
Expand Down Expand Up @@ -962,6 +1047,12 @@ class Conversation(models.Model):
help_text="Whether this is a personal slack channel between the bot and the user",
)

twilio_phone_number = PhoneNumberField(
blank=True,
default="",
help_text="User's Twilio phone number (mandatory)",
)

web_user_id = models.CharField(
max_length=512,
blank=True,
Expand Down Expand Up @@ -1007,6 +1098,7 @@ def get_display_name(self):
or self.fb_page_id
or self.slack_user_id
or self.web_user_id
or (self.twilio_phone_number and self.twilio_phone_number.as_international)
)

get_display_name.short_description = "User"
Expand Down
9 changes: 9 additions & 0 deletions bots/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
create_personal_channel,
SlackBot,
)
from daras_ai_v2.twilio_bot import create_voice_call, send_sms_message
from daras_ai_v2.vector_search import references_as_prompt
from gooeysite.bg_db_conn import get_celery_result_db_safe
from recipes.VideoBots import ReplyButton, messages_as_prompt
Expand Down Expand Up @@ -153,6 +154,7 @@ def send_broadcast_msgs_chunked(
buttons: list[ReplyButton] = None,
convo_qs: QuerySet[Conversation],
bi: BotIntegration,
medium: str,
):
convo_ids = list(convo_qs.values_list("id", flat=True))
for i in range(0, len(convo_ids), 100):
Expand All @@ -164,6 +166,7 @@ def send_broadcast_msgs_chunked(
documents=documents,
bi_id=bi.id,
convo_ids=convo_ids[i : i + 100],
medium=medium,
)


Expand All @@ -177,6 +180,7 @@ def send_broadcast_msg(
documents: list[str] = None,
bi_id: int,
convo_ids: list[int],
medium: str,
):
bi = BotIntegration.objects.get(id=bi_id)
convos = Conversation.objects.filter(id__in=convo_ids)
Expand Down Expand Up @@ -205,6 +209,11 @@ def send_broadcast_msg(
username=bi.name,
token=bi.slack_access_token,
)[0]
case Platform.TWILIO:
if medium == "Voice Call":
create_voice_call(convo, text, audio)
else:
send_sms_message(convo, text, media_url=audio)
case _:
raise NotImplementedError(
f"Platform {bi.platform} doesn't support broadcasts yet"
Expand Down
Loading