feat: initial commit

This commit is contained in:
2025-05-09 19:15:53 +05:30
commit 80c61dc127
54 changed files with 2195 additions and 0 deletions
View File
+32
View File
@@ -0,0 +1,32 @@
from database import get_db
from services.jwtService import create_jwt_token
from services.userServices import UserServices
from utils.password_utils import verify_password
from schemas.CreateSchemas import UserCreate
from exceptions.unauthorized_exception import UnauthorizedException
class AuthService:
def __init__(self):
self.user_service = UserServices()
async def login(self, email, password) -> str:
# get user
user = await self.user_service.get_user_by_email(email)
# verify password
if not verify_password(password, user.password):
raise UnauthorizedException("Invalid credentials")
# remove password from user dict
user_dict = user.__dict__.copy()
user_dict.pop("password", None)
# create token
token = create_jwt_token(user_dict)
return token
async def register(self, user_data: UserCreate) -> str:
user = await self.user_service.create_user(user_data)
token = create_jwt_token(user)
return token
+227
View File
@@ -0,0 +1,227 @@
#
# Copyright (c) 2025, Daily
#
# SPDX-License-Identifier: BSD 2-Clause License
#
import datetime
import io
import os
import sys
import wave
import aiofiles
from dotenv import load_dotenv
from fastapi import WebSocket
from loguru import logger
from pipecat.audio.vad.silero import SileroVADAnalyzer
from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineParams, PipelineTask
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
from pipecat.processors.audio.audio_buffer_processor import AudioBufferProcessor
from pipecat.serializers.twilio import TwilioFrameSerializer
from pipecat.services.elevenlabs import ElevenLabsTTSService
from pipecat.services.playht import PlayHTTTSService, Language
from pipecat.services.deepgram import DeepgramSTTService
from pipecat.services.fish import FishAudioTTSService
from pipecat.services.rime import RimeTTSService
from pipecat.services.cartesia import CartesiaTTSService
from pipecat.services.openai_realtime_beta import (
OpenAIRealtimeBetaLLMService,
SessionProperties,
TurnDetection,
)
from pipecat.services.anthropic import AnthropicLLMService
from pipecat.services.openai import OpenAILLMService
from pipecat.services.google import GoogleLLMService, GoogleLLMContext
from pipecat.transports.network.fastapi_websocket import (
FastAPIWebsocketParams,
FastAPIWebsocketTransport,
)
load_dotenv(override=True)
logger.remove(0)
logger.add(sys.stderr, level="DEBUG")
async def save_audio(
server_name: str, audio: bytes, sample_rate: int, num_channels: int
):
if len(audio) > 0:
filename = f"{server_name}_recording_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.wav"
with io.BytesIO() as buffer:
with wave.open(buffer, "wb") as wf:
wf.setsampwidth(2)
wf.setnchannels(num_channels)
wf.setframerate(sample_rate)
wf.writeframes(audio)
async with aiofiles.open(filename, "wb") as file:
await file.write(buffer.getvalue())
logger.info(f"Merged audio saved to {filename}")
else:
logger.info("No audio data to save")
async def run_bot(
websocket_client: WebSocket, stream_sid: str, testing: bool, option: int = 1
):
transport = FastAPIWebsocketTransport(
websocket=websocket_client,
params=FastAPIWebsocketParams(
audio_in_enabled=True,
audio_out_enabled=True,
add_wav_header=False,
vad_enabled=True,
vad_analyzer=SileroVADAnalyzer(),
vad_audio_passthrough=True,
serializer=TwilioFrameSerializer(stream_sid),
),
)
# llm = OpenAIRealtimeBetaLLMService(
# api_key=os.getenv("OPENAI_API_KEY"),
# session_properties=SessionProperties(
# modalities=["text"],
# turn_detection=TurnDetection(threshold=0.5, silence_duration_ms=800),
# voice=None,
# ),
# )
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
# llm = AnthropicLLMService(api_key=os.getenv("ANTRHOPIC_API_KEY"))
# llm = GoogleLLMService(api_key=os.getenv("GOOGLE_API_KEY"), model="phone_call")
stt = DeepgramSTTService(
api_key=os.getenv("DEEPGRAM_API_KEY"), audio_passthrough=True
)
# tts = PlayHTTTSService(
# api_key=os.getenv("PLAYHT_SECRE_KEY"),
# user_id=os.getenv("PLAYHT_USERID"),
# voice_url="s3://voice-cloning-zero-shot/80ba8839-a6e6-470c-8f68-7c1e5d3ee2ff/abigailsaad/manifest.json",
# params=PlayHTTTSService.InputParams(
# language=Language.EN,
# speed=1.0,
# ),
# ) # not working
# tts = FishAudioTTSService(
# api_key=os.getenv("FISH_AUDIO_API_KEY"),
# model="b545c585f631496c914815291da4e893", # Get this from Fish Audio playground
# output_format="pcm", # Choose output format
# sample_rate=24000, # Set sample rate
# params=FishAudioTTSService.InputParams(latency="normal", prosody_speed=1.0),
# ) # not working
if option == 1:
tts = CartesiaTTSService(
api_key=os.getenv("CARTESIA_API_KEY"),
voice_id="156fb8d2-335b-4950-9cb3-a2d33befec77", # British Lady
push_silence_after_stop=testing,
)
elif option == 2:
tts = RimeTTSService(
api_key=os.getenv("RIME_API_KEY"),
voice_id="stream",
model="mistv2",
)
elif option == 3:
tts = ElevenLabsTTSService(
api_key=os.getenv("ELEVEN_LABS_API_KEY"),
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22",
push_silence_after_stop=testing,
)
elif option == 4:
tts = RimeTTSService(
api_key=os.getenv("RIME_API_KEY"),
voice_id="breeze",
model="mistv2",
)
elif option == 5:
tts = CartesiaTTSService(
api_key=os.getenv("CARTESIA_API_KEY"),
voice_id="1d3ba41a-96e6-44ad-aabb-9817c56caa68", # British Lady
push_silence_after_stop=testing,
)
else:
tts = RimeTTSService(
api_key=os.getenv("RIME_API_KEY"),
voice_id="peak",
model="mistv2",
)
messages = [
{
"role": "system",
"content": f"""
Welcome to 365 Days Medical Centre Para Hills - we care about you.
If this is an emergency, please call triple zero.
We are open from 8 AM to 8 PM every day of the year.
All calls are recorded for training and quality purposes - please let us know if you do not wish to be recorded.
I am Nishka, your 24/7 healthcare receptionist. Which language would you like to speak?
""",
}
]
context = OpenAILLMContext(messages)
context_aggregator = llm.create_context_aggregator(context)
# NOTE: Watch out! This will save all the conversation in memory. You can
# pass `buffer_size` to get periodic callbacks.
audiobuffer = AudioBufferProcessor(user_continuous_stream=not testing)
pipeline = Pipeline(
[
transport.input(), # Websocket input from client
stt, # Speech-To-Text
context_aggregator.user(), # User context
llm, # LLM
tts, # Text-To-Speech
transport.output(), # Websocket output to client
audiobuffer, # Used to buffer the audio in the pipeline
context_aggregator.assistant(), # Assistant context
]
)
task = PipelineTask(
pipeline,
params=PipelineParams(
audio_in_sample_rate=8000,
audio_out_sample_rate=8000,
allow_interruptions=True,
),
)
@transport.event_handler("on_client_connected")
async def on_client_connected(transport, client):
# Start recording.
await audiobuffer.start_recording()
# Kick off the conversation.
messages.append(
{"role": "system", "content": "Please introduce yourself to the user."}
)
await task.queue_frames([context_aggregator.user().get_context_frame()])
@transport.event_handler("on_client_disconnected")
async def on_client_disconnected(transport, client):
await task.cancel()
# @audiobuffer.event_handler("on_audio_data")
# async def on_audio_data(buffer, audio, sample_rate, num_channels):
# server_name = f"server_{websocket_client.client.port}"
# await save_audio(server_name, audio, sample_rate, num_channels)
# We use `handle_sigint=False` because `uvicorn` is controlling keyboard
# interruptions. We use `force_gc=True` to force garbage collection after
# the runner finishes running a task which could be useful for long running
# applications with multiple clients connecting.
runner = PipelineRunner(handle_sigint=False, force_gc=True)
await runner.run(task)
+33
View File
@@ -0,0 +1,33 @@
from datetime import datetime, timedelta, timezone
import jwt
from enum import Enum
from utils.constants import JWT_SECRET, JWT_ALGORITHM, JWT_EXPIRE_MINUTES
def create_jwt_token(data: dict):
# Create a copy of the data and handle Enum and datetime serialization
to_encode = {}
for key, value in data.items():
if isinstance(value, Enum):
to_encode[key] = value.value # Convert Enum to its string value
elif isinstance(value, datetime):
to_encode[key] = value.isoformat() # Convert datetime to ISO format string
else:
to_encode[key] = value
# Safely evaluate the JWT_EXPIRE_MINUTES expression
minutes = eval(JWT_EXPIRE_MINUTES) if isinstance(JWT_EXPIRE_MINUTES, str) else JWT_EXPIRE_MINUTES
expire = datetime.now(timezone.utc) + timedelta(minutes=minutes)
to_encode.update({"exp": expire.timestamp()}) # Use timestamp for expiration
encoded_jwt = jwt.encode(to_encode, JWT_SECRET, algorithm=JWT_ALGORITHM)
return encoded_jwt
def verify_jwt_token(token: str):
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
+93
View File
@@ -0,0 +1,93 @@
from loguru import logger
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from sqlalchemy import or_
from database import get_db
from models.Users import Users
from exceptions.validation_exception import ValidationException
from schemas.ResponseSchemas import UserResponse
from utils.password_utils import hash_password
from schemas.CreateSchemas import UserCreate
from exceptions.resource_not_found_exception import ResourceNotFoundException
class UserServices:
def __init__(self):
self.db: Session = next(get_db())
async def create_user(self, user_data: UserCreate):
try:
# Check if user with same username or email exists
existing_user = (
self.db.query(Users)
.filter(Users.email == user_data.email.lower())
.first()
)
if existing_user:
raise ValidationException(
"User with same email already exists"
)
# Create a new user instance
new_user = Users(
username=user_data.username,
email=user_data.email.lower(),
password=hash_password(user_data.password),
clinicRole=user_data.clinicRole,
userType=user_data.userType,
)
# Add to database and commit
self.db.add(new_user)
self.db.commit()
self.db.refresh(new_user)
user_dict = new_user.__dict__.copy()
user_response = UserResponse(**user_dict).model_dump()
return user_response
except Exception as e:
logger.error("Error creating user", e)
self.db.rollback()
if e.__class__ == ValidationException:
raise ValidationException(e.message)
if e.__class__ == ResourceNotFoundException:
raise ResourceNotFoundException(e.message)
raise e
def get_user(self, user_id) -> UserResponse:
# Query the user by ID
user = self.db.query(Users).filter(Users.id == user_id).first()
if not user:
logger.error("User not found")
raise ResourceNotFoundException("User not found")
user_dict = user.__dict__.copy()
user_response = UserResponse(**user_dict).model_dump()
return user_response
async def get_user_by_email(self, email: str) -> UserResponse:
user = self.db.query(Users).filter(Users.email == email.lower()).first()
if not user:
logger.error("User not found")
raise ResourceNotFoundException("User not found")
user_dict = user.__dict__.copy()
user_response = UserResponse(**user_dict)
return user_response
def update_user(self, user_id, user):
return user
def delete_user(self, user_id):
return user