Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…ckathon

* 'main' of https://github.com/lukas-holzner/codefusion-hackathon:
  Added tts endpoint
  Added  Endpoint for audio Converation
  show agenda also in chat
  Fixed
  Removed Test endpoint
  Fixed get Meeting
  Added Update agenda
  • Loading branch information
vorasudh committed Oct 1, 2024
2 parents 7acfa27 + e7700cf commit d303fc4
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 28 deletions.
60 changes: 60 additions & 0 deletions backend/app/ai_manager.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import os
from dotenv import load_dotenv
from fastapi import UploadFile, HTTPException
from fastapi.responses import FileResponse
from openai import OpenAI
from typing import List, Dict
import json
import io
import tempfile
from starlette.background import BackgroundTask

# Load environment variables from .env file
load_dotenv()
Expand Down Expand Up @@ -134,3 +139,58 @@ def process_user_message(system_message: str, messages: List[str]) -> Dict[str,
break

print("Conversation ended.")

async def convert_audio_to_text(audio_file: UploadFile):
"""
Convert an uploaded audio file to text using OpenAI's Whisper model.
"""
try:
# Read the file content
file_content = await audio_file.read()

# Create a file-like object from the content
audio_file_obj = io.BytesIO(file_content)
audio_file_obj.name = audio_file.filename # Set the filename

# Call the OpenAI API to transcribe the audio
transcript = client.audio.transcriptions.create(
model="whisper-1",
file=audio_file_obj,
response_format="text"
)

return transcript
except Exception as e:
print(f"Error in audio transcription: {str(e)}")
return None

async def convert_text_to_audio(text: str, voice: str = "alloy"):
"""
Convert text to audio using OpenAI's text-to-speech API and return a FastAPI FileResponse.
:param text: The text to convert to speech
:param voice: The voice to use (default is "alloy")
:return: A FastAPI FileResponse containing the audio file
"""
try:
response = client.audio.speech.create(
model="tts-1",
voice=voice,
input=text
)

# Create a temporary file to store the audio content
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_file:
temp_file.write(response.content)
temp_file_path = temp_file.name

# Return a FileResponse
return FileResponse(
path=temp_file_path,
media_type="audio/mpeg",
filename="speech.mp3",
background=BackgroundTask(lambda: os.unlink(temp_file_path))
)
except Exception as e:
print(f"Error in text-to-speech conversion: {str(e)}")
raise HTTPException(status_code=500, detail="Text-to-speech conversion failed")
8 changes: 4 additions & 4 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from fastapi.middleware.cors import CORSMiddleware
from .database import engine
from . import models
from .routers import meetings, users, test
from .routers import meetings, users, tts
import os

models.Base.metadata.create_all(bind=engine)
Expand All @@ -32,9 +32,9 @@
)

# Include the router with the /api prefix
app.include_router(meetings.router, tags=["meetings"])
app.include_router(users.router, tags=["users"])
app.include_router(test.router, tags=["test"])
app.include_router(meetings.router, tags=["Meetings"])
app.include_router(users.router, tags=["Users"])
app.include_router(tts.router, tags=["TTS"])

@app.get("/", include_in_schema=False)
async def serve_index():
Expand Down
83 changes: 72 additions & 11 deletions backend/app/routers/meetings.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from sqlalchemy.orm import Session
from typing import List
import os

from app.routers.users import get_user
from app.ai_manager import generate_initial_prompt, process_user_message
from app.ai_manager import generate_initial_prompt, process_user_message, convert_audio_to_text
from .. import schemas
from ..database import get_db
from ..models import Conversation, Meeting, ChatMessage, MeetingAgenda
Expand All @@ -17,6 +18,9 @@

router = APIRouter()

AUDIO_MIME_TYPES = ["audio/mpeg", "audio/mp3", "audio/wav", "audio/ogg"]
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB in bytes

# Meeting CRUD operations
def get_meeting(db: Session, meeting_id: int):
return db.query(Meeting).filter(Meeting.id == meeting_id).first()
Expand Down Expand Up @@ -131,6 +135,11 @@ def read_meetings(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)
return meetings

@router.get("/meetings/{meeting_id}", response_model=schemas.MeetingSchema)
def read_meeting_route(meeting_id: int, db: Session = Depends(get_db)):
db_meeting = get_meeting(db, meeting_id=meeting_id)
if db_meeting is None:
raise HTTPException(status_code=404, detail="Meeting not found")
return db_meeting

@router.put("/meetings/{meeting_id}", response_model=schemas.Meeting)
def update_meeting_route(meeting_id: int, meeting: schemas.MeetingCreate, db: Session = Depends(get_db)):
Expand Down Expand Up @@ -177,29 +186,81 @@ def create_conversation(meeting_id: int, user_id: int, db: Session = Depends(get
else:
db_conversation = get_conversation(db, meeting_id=meeting_id, user_id=user_id)

# Process messages to remove <agenda> tags
for message in db_conversation.chat_messages:
message.message = re.sub(r'<agenda>.*?</agenda>', '', message.message, flags=re.DOTALL)

db_conversation.chat_messages = extract_and_format_agenda(db_conversation.chat_messages)
return db_conversation

@router.post("/meetings/{meeting_id}/{user_id}/conversation/message", response_model=schemas.Conversation)
def router_add_message(meeting_id: int, user_id: int, message: str, db: Session = Depends(get_db)):
ret = add_message(db, meeting_id=meeting_id, user_id=user_id, message=ChatMessage(message=message, author="user", timestamp=datetime.now()))

for message in ret.chat_messages:
message.message = re.sub(r'<agenda>.*?</agenda>', '', message.message, flags=re.DOTALL)

ret.chat_messages = extract_and_format_agenda(ret.chat_messages)
return ret

@router.post("/meetings/{meeting_id}/{user_id}/conversation/message_audio", response_model=schemas.Conversation)
async def router_add_message_audio(
meeting_id: int,
user_id: int,
audio_file: UploadFile = File(...),
db: Session = Depends(get_db)
):
# Check file type
if audio_file.content_type not in AUDIO_MIME_TYPES:
raise HTTPException(status_code=400, detail="Invalid file type. Only audio files are allowed.")

# Check file size
file_size = await audio_file.read()
await audio_file.seek(0) # Reset file pointer to the beginning
if len(file_size) > MAX_FILE_SIZE:
raise HTTPException(status_code=400, detail="File size exceeds the 5 MB limit.")

audio_text = await convert_audio_to_text(audio_file)
if audio_text is None:
raise HTTPException(status_code=500, detail="Failed to transcribe audio file.")

return router_add_message(meeting_id, user_id, audio_text, db)

@router.delete("/meetings/{meeting_id}/{user_id}/conversation", response_model=schemas.Conversation)
def delete_conversation(meeting_id: int, user_id: int, db: Session = Depends(get_db)):
db_conversation = get_conversation(db, meeting_id=meeting_id, user_id=user_id)
if db_conversation is None:
raise HTTPException(status_code=404, detail="Conversation not found")
db.delete(db_conversation)
db.commit()
return db_conversation

return db_conversation

@router.post("/meetings/{meeting_id}/{user_id}/conversation/update_agenda", response_model=schemas.Conversation)
def update_agenda(
meeting_id: int,
user_id: int,
agenda: List[schemas.MeetingAgenda],
db: Session = Depends(get_db)
):
db_conversation = get_conversation(db, meeting_id=meeting_id, user_id=user_id)
if db_conversation is None:
raise HTTPException(status_code=404, detail="Conversation not found")

# Clear existing agenda items
db_conversation.meeting_agenda.clear()

# Create new MeetingAgenda objects and add them to the conversation
for item in agenda:
new_agenda_item = MeetingAgenda(
agenda_item=item.agenda_item,
completed=item.completed,
conversation_id=db_conversation.id
)
db_conversation.meeting_agenda.append(new_agenda_item)

db.commit()
db.refresh(db_conversation)
return db_conversation

def extract_and_format_agenda(messages):
for message in messages:
agenda_match = re.search(r'<agenda>(.*?)</agenda>', message.message, re.DOTALL)
if agenda_match:
agenda_content = agenda_match.group(1)
agenda_items = [item.strip() for item in agenda_content.split(',')]
formatted_agenda = '\n'.join(f"{i+1}. {item}" for i, item in enumerate(agenda_items))
message.message = re.sub(r'<agenda>.*?</agenda>', formatted_agenda, message.message, flags=re.DOTALL)
return messages
12 changes: 0 additions & 12 deletions backend/app/routers/test.py

This file was deleted.

10 changes: 10 additions & 0 deletions backend/app/routers/tts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from fastapi import APIRouter, Body
from fastapi.responses import FileResponse
from app.ai_manager import convert_text_to_audio


router = APIRouter()

@router.post("/tts/")
async def tts(text: str = Body(...), voice: str = Body("alloy")):
return await convert_text_to_audio(text, voice)
8 changes: 8 additions & 0 deletions backend/app/routers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,11 @@ def delete_user_route(user_id: int, db: Session = Depends(get_db)):
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user

@router.get("/users/{user_id}/meetings", response_model=List[schemas.Meeting])
def read_user_meetings(user_id: int, db: Session = Depends(get_db)):
db_user = get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user.meetings

2 changes: 1 addition & 1 deletion backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ sqlalchemy
pydantic
python-dotenv
openai

python-multipart

0 comments on commit d303fc4

Please sign in to comment.