Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions chatbot/admin/profile_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class ProfileAdmin(ImportMixin, ExportActionMixin, SimpleHistoryAdmin):
list_filter = (
CustomAdvanceDateFilter,
'email',
'userid',
'phone',
ProfileCompanyFilter,
'profile_type'
Expand Down
8 changes: 4 additions & 4 deletions chatbot/celery_tasks/flow_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@


@shared_task
def get_flow_response(channel_name, session_id, profile_id, route, bot_type, bot_route, access_token=None):
"""Common bot task"""
print(f"bot_type is {bot_type} and bot_route is {bot_route}")
def get_flow_response(channel_name, session_id, profile_id, route, bot_type, bot_route, access_token=None,
ums_profile=None):
logger.info('[get_flow_response] bot_type=%s bot_route=%s', bot_type, bot_route)
bot_strategy = BotServiceFactory.create_bot_service(
bot_type=bot_type, route=bot_route
)
orchestrator = ChatOrchestrator(bot_strategy=bot_strategy)
return orchestrator.process_chat_request(
channel_name=channel_name, session_id=session_id, profile_id=profile_id,
language=route, access_token=access_token
language=route, access_token=access_token, ums_profile=ums_profile
)
7 changes: 5 additions & 2 deletions chatbot/consumers/async_base_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ async def disconnect(self, code):
logger.error('Receive Error: %s', e, exc_info=True)

finally:
await self.close()
try:
await self.close()
except Exception:
pass

async def receive(self, text_data):
raise NotImplementedError("receive method must be implemented in subclass")
Expand All @@ -63,7 +66,7 @@ def determine_company_chat_status(self, session_id, profile_id, route, is_discon

profile = Profile.objects.filter(id=profile_id).first()
try:
if profile:
if profile and profile.company:
company_bot = CompanyBot.objects.get(company=profile.company, route=route)
else:
company_bot = CompanyBot.objects.get(route=route)
Expand Down
58 changes: 41 additions & 17 deletions chatbot/consumers/async_consumer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import json
import traceback
import os
from django.conf import settings
from chatbot.celery_tasks.common_chat_tasks import save_in_company_db
Expand All @@ -9,6 +8,8 @@
from chatbot.celery_tasks.flow_tasks import get_flow_response
from chatbot.models.company_models import CompanyStateMachine
from chatbot.utils.audio_provider_utils import text_translate_provider
from asgiref.sync import sync_to_async
from chatbot.utils.elevate.profile_utils import fetch_elevate_user, upsert_elevate_profile
import logging
from channels.db import database_sync_to_async
from chatbot.utils.transliterate_utils import transliterate_text
Expand All @@ -29,6 +30,7 @@ def __init__(self, *args, **kwargs):
self.flow_name = None
self.ip_address = None
self.access_token = None
self.ums_profile = None
self.background_tasks = set()

async def disconnect(self, code):
Expand All @@ -55,14 +57,36 @@ async def receive(self, text_data):
self.ip_address = text_data_json.get('address')
self.access_token = text_data_json.get('access_token')

user_id = await self.handle_access_token(self.access_token)

elevate_user_data = await sync_to_async(fetch_elevate_user, thread_sensitive=False)(self.access_token)
elevate_result = await self.upsert_elevate_profile(elevate_user_data)
if elevate_result.get('error') == 'unauthorized':
logger.error('[authenticate] Elevate auth failure — closing connection')
await self.send(text_data=json.dumps({"msg": "Authentication failed. Invalid or expired token.", "source": "system", "error": True}))
await self.close()
return
if elevate_result.get('error') == 'elevate_server_error':
logger.error('[authenticate] Elevate server error — closing connection')
await self.send(text_data=json.dumps({"msg": "Service unavailable. Please try again later.", "source": "system", "error": True}))
await self.close()
return
if not elevate_result.get('profileid'):
logger.error('[authenticate] Elevate returned no profileid — closing connection')
await self.send(text_data=json.dumps(
{"msg": "Authentication failed. Please try again.", "source": "system", "error": True}
))
await self.close()
return
self.profile_id = elevate_result['profileid']
self.ums_profile = elevate_result.get('ums_profile')

profile = await self.get_profile(self.profile_id)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
logger.info(
f"channel_name: %s, session_id: %s, profile_id: %s, route: %s",
'channel_name: %s, session_id: %s, profile_id: %s, route: %s',
self.channel_name, self.session_id, self.profile_id, self.route
)

user_id = await self.handle_access_token(self.access_token)

self.company_bot = await self.get_company_bot(profile, self.bot_route)

# Create chat session asynchronously
Expand Down Expand Up @@ -147,20 +171,19 @@ async def receive(self, text_data):
# Start the Celery task but don't wait for it
get_flow_response.delay(
self.channel_name, self.session_id, self.profile_id, self.route,
'common', self.bot_route, access_token=self.access_token
'common', self.bot_route, access_token=self.access_token,
ums_profile=self.ums_profile
)

except Exception as e:
logger.error('Receive Error: %s', e, exc_info=True)
traceback.print_exc()

async def connect(self):
try:
logger.info(f"Attempting to connect to websocket")
await super().connect()
except Exception as e:
logger.error('Connect Error: %s', e, exc_info=True)
traceback.print_exc()

@database_sync_to_async
def get_profile(self, profile_id):
Expand All @@ -172,28 +195,30 @@ def get_profile(self, profile_id):
def handle_access_token(self, access_token):
user_id = None

if access_token:
print("Access Token: ", access_token)

if access_token and PUBLIC_KEY:
try:
decoded = jwt.decode(
access_token,
PUBLIC_KEY,
algorithms=["HS256"]
)
print("Decoded JWT: ", decoded)
if decoded:
user_id = decoded.get("data", {}).get("id")
except Exception as e:
logger.error('JWT Decode Error: %s', e, exc_info=True)
print(f"JWT Decode Error: {e}")
logger.error('[handle_access_token] JWT decode error: %s', e, exc_info=True)
elif access_token and not PUBLIC_KEY:
logger.error('[handle_access_token] JWT_PUBLIC_KEY is not set — skipping decode')

logger.info("User_id: %s", user_id)
logger.info('[handle_access_token] user_id=%s', user_id)
return user_id
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@database_sync_to_async
def upsert_elevate_profile(self, user_data):
return upsert_elevate_profile(user_data)

@database_sync_to_async
def get_company_bot(self, profile, route):
if profile:
if profile and profile.company_id:
return CompanyBot.objects.get(company=profile.company, route=route)
else:
return CompanyBot.objects.get(route=route)
Expand Down Expand Up @@ -273,10 +298,9 @@ def translate_message(self, message):
voice_provider=transliterate_voice_provider, source_language=self.route, target_language='en',
message_body=message, is_sentence=True
)
print("Trans response: ", response)
logger.info('[translate_message] transliterate response=%s', response)
Comment thread
KUNALTEMPEST marked this conversation as resolved.
if response and response.get('content'):
content = response.get('content')
print("Trans content: ", content)
if content and isinstance(content, list) and len(content)>0:
content = content[0]
return content
Expand Down
57 changes: 57 additions & 0 deletions chatbot/migrations/0086_alter_profile_unique_together_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Generated by Django 5.2 on 2026-06-19 08:13

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chatbot', '0085_flow_title_bot_historicalflow_title_bot_and_more'),
]

operations = [
migrations.AlterUniqueTogether(
name='profile',
unique_together=set(),
),
migrations.AlterField(
model_name='companybot',
name='provider',
field=models.CharField(choices=[('bedrock', 'BEDROCK'), ('openai', 'OPENAI'), ('anthropic', 'ANTHROPIC'), ('openrouter', 'Openrouter')], default='bedrock', help_text='Select the LLM provider (BEDROCK, ANTHROPIC, or OPENAI)', max_length=100),
),
migrations.AlterField(
model_name='historicalcompanybot',
name='provider',
field=models.CharField(choices=[('bedrock', 'BEDROCK'), ('openai', 'OPENAI'), ('anthropic', 'ANTHROPIC'), ('openrouter', 'Openrouter')], default='bedrock', help_text='Select the LLM provider (BEDROCK, ANTHROPIC, or OPENAI)', max_length=100),
),
migrations.AlterField(
model_name='historicalprofile',
name='email',
field=models.EmailField(blank=True, max_length=1000, null=True),
),
migrations.AlterField(
model_name='historicalprofile',
name='userid',
field=models.CharField(blank=True, max_length=500, null=True),
),
migrations.AlterField(
model_name='profile',
name='company',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='chatbot.company'),
),
migrations.AlterField(
model_name='profile',
name='email',
field=models.EmailField(blank=True, max_length=1000, null=True),
),
migrations.AlterField(
model_name='profile',
name='userid',
field=models.CharField(blank=True, max_length=500, null=True),
),
migrations.AddIndex(
model_name='profile',
index=models.Index(fields=['userid'], name='chatbot_pro_userid_3d404e_idx'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 5.2 on 2026-06-24 04:31

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chatbot', '0086_alter_profile_unique_together_and_more'),
]

operations = [
migrations.RemoveIndex(
model_name='profile',
name='chatbot_pro_userid_3d404e_idx',
),
migrations.AddConstraint(
model_name='profile',
constraint=models.UniqueConstraint(condition=models.Q(('userid__isnull', False)), fields=('userid',), name='uniq_profile_userid'),
),
migrations.AddConstraint(
model_name='profile',
constraint=models.UniqueConstraint(condition=models.Q(('email__isnull', False), ('userid__isnull', True)), fields=('email', 'company_id'), name='uniq_profile_email_company'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.2 on 2026-06-24 05:04

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chatbot', '0087_remove_profile_chatbot_pro_userid_3d404e_idx_and_more'),
]

operations = [
migrations.RemoveIndex(
model_name='botvernacular',
name='bot_vernacu_company_483975_idx',
),
migrations.AddConstraint(
model_name='botvernacular',
constraint=models.UniqueConstraint(fields=('company_bot', 'language'), name='uniq_bot_vernacular_bot_language'),
),
]
7 changes: 6 additions & 1 deletion chatbot/models/bot_vernacular_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,10 @@ class Meta:
indexes = [
models.Index(fields=['language']),
models.Index(fields=['created_at']),
models.Index(fields=['company_bot']),
]
constraints = [
models.UniqueConstraint(
fields=['company_bot', 'language'],
name='uniq_bot_vernacular_bot_language',
),
]
26 changes: 22 additions & 4 deletions chatbot/models/profile_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ def get_file_upload_path(self, filename):
return upload_path

first_name = models.CharField(max_length=100, null=True, blank=True)
userid = models.CharField(max_length=200, null=True, blank=True)
userid = models.CharField(max_length=500, null=True, blank=True)
last_name = models.CharField(max_length=100, null=True, blank=True)
email = models.EmailField(max_length=1000, null=False, blank=False)
email = models.EmailField(max_length=1000, null=True, blank=True)
phone = models.CharField(max_length=20, null=True, blank=True)
alternate_phone = models.CharField(max_length=20, null=True, blank=True)
country = models.CharField(max_length=100, null=True, blank=True)
status = models.CharField(max_length=20, choices=EntityStatus.choices, default=EntityStatus.ACTIVE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
company = models.ForeignKey(Company, on_delete=models.DO_NOTHING, null=False, blank=False)
company = models.ForeignKey(Company, on_delete=models.DO_NOTHING, null=True, blank=True)
password = models.CharField(max_length=1000, null=True, blank=True)
profile_type = models.CharField(max_length=20, choices=ProfileType.choices, default=ProfileType.USER)
profile_code = models.CharField(max_length=100, null=True, blank=True)
Expand Down Expand Up @@ -60,13 +60,31 @@ def clean(self):
})

def save(self, *args, **kwargs):
if self.userid == '':
self.userid = None
if not self.userid and (not self.company_id or not self.email):
raise ValueError("Profile must have either a userid (UMS flow) or both company and email (traditional flow).")
if not self.userid and self.email and self.company_id:
if Profile.objects.filter(email=self.email, company_id=self.company_id).exclude(pk=self.pk).exists():
raise ValueError("A profile with this email and company already exists.")
if self.password and 'pbkdf2_sha256' not in self.password:
self.password = make_password(self.password)
super().save(*args, **kwargs)

class Meta:
unique_together = ('email', 'company')
indexes = [
models.Index(fields=['email']),
models.Index(fields=['phone']),
]
constraints = [
models.UniqueConstraint(
fields=['userid'],
condition=models.Q(userid__isnull=False),
name='uniq_profile_userid',
),
models.UniqueConstraint(
fields=['email', 'company_id'],
condition=models.Q(userid__isnull=True, email__isnull=False),
name='uniq_profile_email_company',
),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
2 changes: 1 addition & 1 deletion chatbot/services/core/base_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def get_session_data(session_id, profile_id, bot_route):
chat_session = ChatSession.objects.filter(session=session_id).first()
profile = Profile.objects.filter(id=profile_id).first()

if profile:
if profile and profile.company_id:
company_bot = CompanyBot.objects.get(company=profile.company, route=bot_route)
else:
company_bot = CompanyBot.objects.get(route=bot_route)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down
Loading