Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
50 changes: 33 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,7 @@
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 chatbot.utils.elevate.profile_utils import handle_elevate_profile
import logging
from channels.db import database_sync_to_async
from chatbot.utils.transliterate_utils import transliterate_text
Expand All @@ -29,6 +29,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 +56,29 @@ 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_result = await self.sync_elevate_profile(self.access_token)
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 elevate_result.get('profileid'):
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.
Resolved
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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 +163,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 +187,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.
Resolved

@database_sync_to_async
def sync_elevate_profile(self, access_token):
return handle_elevate_profile(access_token)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

@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 +290,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.
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',
),
]
24 changes: 20 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,29 @@ def clean(self):
})

def save(self, *args, **kwargs):
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.
Resolved
]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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.
Resolved
Expand Down
7 changes: 3 additions & 4 deletions chatbot/services/core/orchestrator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import traceback
import logging
from .base_service import BaseChatService
from .prompt_builder import PromptBuilder
Expand All @@ -17,7 +16,7 @@ def __init__(self, bot_strategy):
self.prompt_builder = PromptBuilder()
self.message_handler = MessageHandler()

def process_chat_request(self, channel_name, session_id, profile_id, language, access_token=None):
def process_chat_request(self, channel_name, session_id, profile_id, language, access_token=None, ums_profile=None):
"""Main processing method"""
try:
# Get session data
Expand Down Expand Up @@ -66,7 +65,7 @@ def process_chat_request(self, channel_name, session_id, profile_id, language, a
# Build prompt
prompt_to_use = self.prompt_builder.build_system_prompt(
company_bot=session_data['company_bot'], state_machine=state_machine,
profile=session_data['profile']
profile=session_data['profile'], ums_profile=ums_profile
)

# Append running finalized sources so the LLM knows what to update on each turn
Expand All @@ -91,6 +90,7 @@ def process_chat_request(self, channel_name, session_id, profile_id, language, a
'temp_messages': temp_messages,
'intro_mssg': intro_mssg,
'access_token': access_token,
'ums_profile': ums_profile,
}

# Add strategy-specific parameters
Expand All @@ -104,7 +104,6 @@ def process_chat_request(self, channel_name, session_id, profile_id, language, a

except Exception as e:
logger.error('Error in chat processing: %s', e, exc_info=True)
traceback.print_exc()
return None

def _handle_error_response(self, error_msg, channel_name, language, chat_session, company_bot):
Expand Down
Loading