Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Demo #192

Draft
wants to merge 30 commits into
base: main
Choose a base branch
from
Draft

Demo #192

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7a51b09
wip allow starting a recording from the frontend
lebaudantoine Oct 15, 2024
d200eeb
wip grant recording permission
lebaudantoine Oct 15, 2024
8477296
wip add recording indicator
lebaudantoine Oct 15, 2024
40d1f01
wip deploy to staging
lebaudantoine Oct 16, 2024
e271c87
wip draft a mini webhook
lebaudantoine Oct 16, 2024
5e57647
wip connect minio
lebaudantoine Oct 16, 2024
3614b1e
wip create openai client
lebaudantoine Oct 16, 2024
3dcc93b
wip feedbacks
lebaudantoine Oct 16, 2024
ac183c9
wip bump secrets
rouja Oct 16, 2024
e861809
wip add some logs and allow toggling openai request
lebaudantoine Oct 16, 2024
83cfcac
wip add prompt and summary
lebaudantoine Oct 16, 2024
41c9693
wip handle if file doesn't exist
lebaudantoine Oct 16, 2024
f0b2507
wip get room from filename
lebaudantoine Oct 16, 2024
c9c5d3b
wip debug bucket
lebaudantoine Oct 16, 2024
0cf5ab1
wip
lebaudantoine Oct 16, 2024
fdaf567
wip revert minio
lebaudantoine Oct 16, 2024
817cf25
wip bump secret
rouja Oct 16, 2024
2a56cda
wip enable openai
lebaudantoine Oct 16, 2024
a4820ae
wip email user
lebaudantoine Oct 16, 2024
376ff89
wip room_slug
lebaudantoine Oct 16, 2024
7105c7c
remove tl
lebaudantoine Oct 16, 2024
2f675c2
wip clean and get blocknote content
lebaudantoine Oct 17, 2024
82f0251
fix code
lebaudantoine Oct 17, 2024
d49dc72
fix endpoint
lebaudantoine Oct 17, 2024
b43f992
wip enable openai
lebaudantoine Oct 17, 2024
98950f4
fix logger
lebaudantoine Oct 17, 2024
b1b28fe
iterate on prompt
lebaudantoine Oct 17, 2024
db071f0
wip iterate on the prompt
lebaudantoine Oct 17, 2024
c8b4592
wip iterate on the prompt
lebaudantoine Oct 17, 2024
808fc7a
wip update prompt
lebaudantoine Oct 18, 2024
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
2 changes: 1 addition & 1 deletion secrets
316 changes: 316 additions & 0 deletions src/backend/core/api/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@

from rest_framework.decorators import api_view
from rest_framework.response import Response
from minio import Minio
from django.conf import settings
import openai
import logging
from ..models import Room, RoleChoices

import tempfile
import os
import smtplib
import requests


logger = logging.getLogger(__name__)


def get_prompt(transcript, date):
return f"""

Audience: Coworkers.

**Do:**
- Detect the language of the transcript and provide your entire response in the same language.
- If any part of the transcript is unclear or lacks detail, politely inform the user, specifying which areas need further clarification.
- Ensure the accuracy of all information and refrain from adding unverified details.
- Format the response using proper markdown and structured sections.
- Be concise and avoid repeating yourself between the sections.
- Be super precise on nickname
- Be a nit-picker
- Auto-evaluate your response

**Don't:**
- Write something your are not sure.
- Write something that is not mention in the transcript.
- Don't make mistake while mentioning someone

**Task:**
Summarize the provided meeting transcript into clear and well-organized meeting minutes. The summary should be structured into the following sections, excluding irrelevant or inapplicable details:

1. **Summary**: Write a TL;DR of the meeting.
2. **Subjects Discussed**: List the key points or issues in bullet points.
4. **Next Steps**: Provide action items as bullet points, assigning each task to a responsible individual and including deadlines (if mentioned). Format action items as tickable checkboxes. Ensure every action is assigned and, if a deadline is provided, that it is clearly stated.

**Transcript**:
{transcript}

**Response:**

### Summary [Translate this title based on the transcript’s language]
[Provide a brief overview of the key points discussed]

### Subjects Discussed [Translate this title based on the transcript’s language]
- [Summarize each topic concisely]

### Next Steps [Translate this title based on the transcript’s language]
- [ ] Action item [Assign to the responsible individual(s) and include a deadline if applicable, follow this strict format: Action - List of owner(s), deadline.]

"""

def get_room_and_owners(slug):
"""Wip."""

try:
room = Room.objects.get(slug=slug)
owner_accesses = room.accesses.filter(role=RoleChoices.OWNER)
owners = [access.user for access in owner_accesses]

logger.info("Room %s has owners: %s", slug, owners)

except Room.DoesNotExist:
logger.error("Room with slug %s does not exist", slug)

owners = None
room = None

return room, owners


def remove_temporary_file(path):
"""Wip."""

if not path or not os.path.exists(path):
return

os.remove(path)
logger.info("Temporary file %s has been deleted.", path)

def get_blocknote_content(summary):
"""Wip."""

if not settings.BLOCKNOTE_CONVERTER_URL:
logger.error("BLOCKNOTE_CONVERTER_URL is not configured")
return None

headers = {
"Content-Type": "application/json"
}

data = {
"markdown": summary
}

logger.info("Converting summary in BlockNote.js…")
response = requests.post(settings.BLOCKNOTE_CONVERTER_URL, headers=headers, json=data)

if response.status_code != 200:
logger.error(f"Failed to convert summary. Status code: {response.status_code}")
return None

response_data = response.json()
if not 'content' in response_data:
logger.error(f"Content is missing: %s", response_data)
return None

content = response_data['content']
logger.info("Base64 content: %s", content)

return content



def get_document_link(content, email):
"""Wip."""

logger.info("Create a document for %s", email)

if not settings.DOCS_BASE_URL:
logger.error("DOCS_BASE_URL is not configured")
return None

headers = {
"Content-Type": "application/json"
}

data = {
"content": content,
"owner": email
}

logger.info("Querying docs…")
response = requests.post(f"{settings.DOCS_BASE_URL}/api/v1.0/summary/", headers=headers, json=data)

if response.status_code != 200:
logger.error(f"Failed to get document's id. Status code: {response.status_code}")
return None

response_data = response.json()
if not 'id' in response_data:
logger.error(f"ID is missing: %s", response_data)
return None

id = response_data['id']
logger.info("Document's id: %s", id)

return f"{settings.DOCS_BASE_URL}/docs/{id}/"


def email_owner_with_summary(room, link, owner):
"""Wip."""

logger.info("Emailing owner: %s", owner)

try:
room.email_summary(owners=[owner], link=link)
except smtplib.SMTPException:
logger.error("Error while emailing owner")

def strip_room_slug(filename):
"""Wip."""
return filename.split("_")[2].split(".")[0]

def strip_room_date(filename):
"""Wip."""
return filename.split("_")[1].split(".")[0]


def get_minio_client():
"""Wip."""

try:
return Minio(
settings.MINIO_URL,
access_key=settings.MINIO_ACCESS_KEY,
secret_key=settings.MINIO_SECRET_KEY,
)
except Exception as e:
logger.error("An error occurred while creating the Minio client %s: %s", settings.MINIO_URL, str(e))


def download_temporary_file(minio_client, filename):
"""Wip."""

temp_file_path = None

logger.info('downloading file %s', filename)

try:
audio_file_stream = minio_client.get_object(settings.MINIO_BUCKET, object_name=filename)

with tempfile.NamedTemporaryFile(delete=False, suffix='.ogg') as temp_audio_file:

for data in audio_file_stream.stream(32 * 1024):
temp_audio_file.write(data)

temp_file_path = temp_audio_file.name
logger.info('Temporary file created at %s', temp_file_path)

audio_file_stream.close()
audio_file_stream.release_conn()

except Exception as e:
logger.error("An error occurred while accessing the object: %s", str(e))

return temp_file_path


# todo - discuss retry policy if the webhook fail
@api_view(["POST"])
def minio_webhook(request):

data = request.data

logger.info('Minio webhook sent %s', data)

record = data["Records"][0]
s3 = record['s3']
bucket = s3['bucket']
bucket_name = bucket['name']
object = s3['object']
filename = object['key']

if bucket_name != settings.MINIO_BUCKET:
logger.info('Not interested in this bucket: %s', bucket_name)
return Response("Not interested in this bucket")

if object['contentType'] != 'audio/ogg':
logger.info('Not interested in this file type: %s', object['contentType'])
return Response("Not interested in this file type")

room_slug = strip_room_slug(filename)
room_date = strip_room_date(filename)
logger.info('file received %s for room %s', filename, room_slug)

minio_client = get_minio_client()

temp_file_path = None
summary = None

try:
temp_file_path = download_temporary_file(minio_client, filename)

if settings.OPENAI_ENABLE and temp_file_path:
logger.info('Initiating OpenAI client …')
openai_client = openai.OpenAI(
api_key=settings.OPENAI_API_KEY,
)

with open(temp_file_path, "rb") as audio_file:
logger.info('Querying transcription …')
transcript = openai_client.audio.transcriptions.create(
model="whisper-1",
file=audio_file
)

logger.info('Transcript: \n %s', transcript)
prompt = get_prompt(transcript.text, room_date)

logger.info('Prompt: \n %s', prompt)

summary_response = openai_client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a concise and structured assistant, that summarizes meeting transcripts."},
{"role": "user", "content": prompt}
],
)

summary = summary_response.choices[0].message.content
logger.info('Summary: \n %s', summary)

except Exception as e:
logger.error("An error occurred: %s", str(e))
raise

finally:
remove_temporary_file(temp_file_path)

if not summary:
logger.error("Empty summary.")
return Response("")

room, owners = get_room_and_owners(room_slug)

if not owners or not room:
logger.error("No owners in room %s", room_slug)
return Response("")

content = get_blocknote_content(summary)

if not content:
logger.error("Empty content.")
return Response("")

owner = owners[0]

link = get_document_link(content, owner.email)

if not link:
logger.error("Empty link.")
return Response("")

email_owner_with_summary(room, link, owner)

return Response("")
23 changes: 23 additions & 0 deletions src/backend/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from django.utils.text import capfirst, slugify
from django.utils.translation import gettext_lazy as _

from django.template.loader import render_to_string

from timezone_field import TimeZoneField

logger = getLogger(__name__)
Expand Down Expand Up @@ -325,3 +327,24 @@ def clean_fields(self, exclude=None):
else:
raise ValidationError({"name": f'Room name "{self.name:s}" is reserved.'})
super().clean_fields(exclude=exclude)


def email_summary(self, owners, link):
"""Wip"""

template_vars = {
"title": "Votre résumé est prêt",
"link": link,
"room": self.slug,
}
msg_html = render_to_string("mail/html/summary.html", template_vars)
msg_plain = render_to_string("mail/text/invitation.txt", template_vars)

for owner in owners:
owner.email_user(
subject="Votre résumé est prêt",
from_email=settings.EMAIL_FROM,
message=msg_plain,
html_message=msg_html,
fail_silently=False,
)
3 changes: 2 additions & 1 deletion src/backend/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from rest_framework.routers import DefaultRouter

from core.api import get_frontend_configuration, viewsets
from core.api import get_frontend_configuration, viewsets, demo
from core.authentication.urls import urlpatterns as oidc_urls

# - Main endpoints
Expand All @@ -24,6 +24,7 @@
*router.urls,
*oidc_urls,
path("config/", get_frontend_configuration, name="config"),
path("minio-webhook/", demo.minio_webhook, name="demo"),
]
),
),
Expand Down
1 change: 1 addition & 0 deletions src/backend/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def generate_token(room: str, user, username: Optional[str] = None) -> str:
room=room,
room_join=True,
room_admin=True,
room_record=True,
can_update_own_metadata=True,
can_publish_sources=[
"camera",
Expand Down
Loading
Loading