feat: initial commit
This commit is contained in:
@@ -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
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user