CoolFace
Apppublic

ArunK-2003/KapNotes

sourceHugging Facemitupdated 2y agoView on Hugging Face
0likes
app.py535 linesDownload Raw Back to root
1import os2import json3import re4import html5import streamlit as st6import plotly.graph_objects as go7from google.cloud import storage8from google.oauth2 import service_account9from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer10from scipy.ndimage import gaussian_filter1d11from datetime import timedelta, datetime12 13gcp_credentials = os.getenv('GCP_CREDENTIALS')14credentials_dict = json.loads(gcp_credentials)15creds = service_account.Credentials.from_service_account_info(credentials_dict)16client = storage.Client(credentials=creds)17bucket_name = "kapnotes"18bucket = client.bucket(bucket_name)19st.set_page_config(page_title="Kap Notes", layout="wide")20 21def get_client_names():22    blobs = list(bucket.list_blobs(prefix=""))23    client_names = set()24    for blob in blobs:25        client_name = blob.name.split("/")[0]26        client_names.add(client_name)27    return sorted(client_names)28 29def validate_data(client_name, date, meeting):30    summary_blob_name = f"{client_name}/{date}/{meeting}/summary.txt"31    transcription_blob_name = f"{client_name}/{date}/{meeting}/transcription.txt"32    audio_blob_name = f"{client_name}/{date}/{meeting}/audio.wav"33    summary_blob = bucket.blob(summary_blob_name)34    transcription_blob = bucket.blob(transcription_blob_name)35    audio_blob = bucket.blob(audio_blob_name)36    return summary_blob.exists() and transcription_blob.exists() and audio_blob.exists()37 38def get_meetings_for_date(client_name, date):39    prefix = f"{client_name}/{date}/"40    blobs = list(bucket.list_blobs(prefix=prefix))41    meetings = set()42    for blob in blobs:43        parts = blob.name.split("/")44        if len(parts) > 2 and parts[2]:45            meetings.add(parts[2])46    return sorted(meetings)47 48def get_dates_for_client(client_name):49    prefix = f"{client_name}/"50    blobs = list(bucket.list_blobs(prefix=prefix))51    dates = set()52    for blob in blobs:53        parts = blob.name.split("/")54        if len(parts) > 1 and parts[1]:55            dates.add(parts[1])56    return sorted(dates)57 58def login():59    st.markdown("""60        <style>61        .stApp {62            background: linear-gradient(125deg,rgb(253, 250, 220) 0%,rgb(214, 245, 255) 50%, #F8F8FF 100%);63            background-size: 200% 200%;64            animation: gradientMove 10s ease infinite;65        }66        @keyframes gradientMove {67            0% { background-position: 0% 50%; }68            50% { background-position: 100% 50%; }69            100% { background-position: 0% 50%; }70        }71        h1 {72            color: #4A4A4A;73            font-size: 3.5rem;74            font-weight: 900;75            text-align: center;76            margin-bottom: 3rem;77            letter-spacing: 2px;78        }79        .stButton > button {80            width: 100%;81            background: linear-gradient(45deg, #2563eb 0%, #3b82f6 100%);82            color: white;83            border: none;84            border-radius: 16px;85            padding: 1rem 1.5rem;86            font-size: 1.3rem;87            font-weight: bold;88            cursor: pointer;89            margin-top: 2rem;90            box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4),91                        inset 0 -4px 8px rgba(0, 0, 0, 0.2),92                        inset 0 4px 8px rgba(255, 255, 255, 0.2);93            transition: all 0.3s ease;94        }95        .stButton > button:hover {96            transform: translateY(-3px) scale(1.03);97            box-shadow: 0 12px 24px rgba(0, 0, 0, 0.5),98                        inset 0 -4px 8px rgba(0, 0, 0, 0.2),99                        inset 0 4px 8px rgba(255, 255, 255, 0.2);100            background: linear-gradient(45deg, #1d4ed8 0%, #2563eb 100%);101        }102        </style>103    """, unsafe_allow_html=True)104 105    if 'password' not in st.session_state:106        st.session_state.password = ""107    108    st.markdown("<h1>KAP NOTES</h1>", unsafe_allow_html=True)109    110    client_names = get_client_names()111    client_name = st.selectbox("Select Client", client_names)112    113    if client_name:114        available_dates = get_dates_for_client(client_name)115        selected_date = st.selectbox(f"Available Dates for {client_name}", available_dates)116        117        if selected_date:118            available_meetings = get_meetings_for_date(client_name, selected_date)119            selected_meeting = st.selectbox(f"Available Meetings for {selected_date}", available_meetings)120            password = st.text_input("Enter Password", type="password", value=st.session_state.password)121            sign_in_button = st.button("Sign In", key="sign_in")122            if sign_in_button:123                if password == "kapnotes12345":124                    if validate_data(client_name, selected_date, selected_meeting):125                        st.session_state.client_name = client_name126                        st.session_state.date = selected_date127                        st.session_state.meeting = selected_meeting128                        st.session_state.logged_in = True129                        st.session_state.password = password130                        st.rerun()131                    else:132                        st.error(f"No records available for {client_name} on {selected_date}. Please select another option.")133                elif not password:134                    st.error("Please enter password.")135                else: 136                    st.error("Incorrect Password. Please try again.")137                    st.session_state.password = password138    139 140if 'logged_in' not in st.session_state:141    st.session_state.logged_in = False142 143if not st.session_state.logged_in:144    login()145else:146    client_name = st.session_state.client_name147    date = st.session_state.date148    meeting = st.session_state.meeting149    password= st.session_state.password150 151    if st.sidebar.button("Back"):152        st.session_state.logged_in = False153        st.rerun() 154 155    st.sidebar.markdown(f'''156        <div class="client-name-container">157            <div class="client-name">{client_name}</div>158        </div>159    ''', unsafe_allow_html=True)160 161    css = '''162        <style>163        [data-testid="stExpander"] div:has(>.streamlit-expanderContent) {164            max-height: 400px;165            overflow-y: scroll;166        }167        [data-testid="stSidebar"] {168            min-width: 400px; 169        }170        [data-testid="stSidebar"] > div:first-child {171            padding-top: 10px;172        }173        .main {174            margin-top: 0 !important;175        }176        .summary-box, .keypoints-box, .action-items-box {177            padding: 20px;178            border-radius: 15px;179            box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2), 0px 6px 15px rgba(0, 0, 0, 0.15);180            margin-top: 20px;181            margin-bottom: 30px;182            line-height: 1.8;183            font-size: 16px;184            color: #333;185            transition: transform 0.3s ease, box-shadow 0.3s ease;186        }187        .summary-box:hover, .keypoints-box:hover, .action-items-box:hover {188            transform: translateY(-0.1px) scale(1.05);189            box-shadow: 0px 12px 25px rgba(0, 0, 0, 0.25), 0px 18px 35px rgba(0, 0, 0, 0.2);190        }191        .summary-box {192            background: linear-gradient(145deg, #F1EAFF, #E5D4FF);193        }194        .keypoints-box {195            background: linear-gradient(145deg, #F1EAFF, #E5D4FF);196        }197        .action-items-box {198            background: linear-gradient(145deg, #F1EAFF, #E5D4FF);199        }200        .summary-box, .keypoints-box, .action-items-box {201            border: 1px solid rgba(0, 0, 0, 0.1);202            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2), 0 6px 18px rgba(0, 0, 0, 0.15);203        }204        .summary-box {205            background-color: #FFF8DC;206        }207        button:hover {208            background-color: white;209            color: black;210            border-color: black;211        }212        br {213            margin-top: 8px;214        }215        .audio-player-container {216            background: linear-gradient(135deg, #FF91A4, #FF4E00);217            padding: 25px;218            border-radius: 20px;219            box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.2), 0px 6px 25px rgba(0, 0, 0, 0.15);220            margin-top: 30px;221            margin-bottom: 40px;222            text-align: center;223            font-size: 18px;224            transition: all 0.7s ease-in-out, transform 0.3s ease;225        }226        .audio-player-container:hover {227            transform: translateY(-10px) scale(1.03);228            box-shadow: 0px 8px 25px rgba(0, 0, 0, 0.3), 0px 12px 35px rgba(0, 0, 0, 0.25);229        }230        .audio-player {231            width: 80%;232            height: 50px;233            border-radius: 15px;234            background-color: #FFF8DC;235            border: 1px solid rgba(0, 0, 0, 0.15);236            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2), 0 6px 18px rgba(0, 0, 0, 0.15);237            transition: all 0.3s ease, transform 0.3s ease;238        }239        .audio-player:hover {240            background-color: #FF7F50;241            transform: scale(1.1);242        }243        .audio-player-container h4 {244            color: #333;245            font-weight: 700;246            margin-bottom: 15px;247            font-size: 22px;248        }249        .audio-player-container .play-button {250            background-color: #FF4E00;251            border: none;252            padding: 10px 20px;253            color: white;254            font-weight: 600;255            border-radius: 30px;256            cursor: pointer;257            transition: all 0.5s ease;258        }259        .audio-player-container .play-button:hover {260            background-color: #FF91A4;261            box-shadow: 0px 4px 20px rgba(255, 145, 164, 0.5);262        }263        .audio-player-container .play-button:focus {264            outline: none;265        }266        .comment-box {267            background: linear-gradient(145deg, #f4f9fb, #dce5f5);268            border: 1px solid #cfd9e6;269            border-radius: 10px;270            padding: 15px;271            margin-bottom: 20px;272            box-shadow: 2px 2px 12px rgba(0, 0, 0, 0.1), -2px -2px 12px rgba(255, 255, 255, 0.8);273            font-family: 'Arial', sans-serif;274            transition: transform 0.3s ease, box-shadow 0.3s ease;275        }276        .comment-box:hover {277            transform: scale(1.02) translateY(-2px);278            box-shadow: 4px 4px 20px rgba(0, 0, 0, 0.2), -4px -4px 20px rgba(255, 255, 255, 0.4);279        }280        .comment-header {281            display: flex;282            justify-content: space-between;283            margin-bottom: 10px;284            font-weight: bold;285            color: #3e3e3e;286        }287        .comment-text {288            color: #555;289            font-size: 14px;290            line-height: 1.6;291        }292        .comment-header span {293            color: #007bff;294            font-size: 12px;295        }296        .form-container {297            margin-bottom: 30px;298        }299        .stTextInput, .stTextArea {300            border-radius: 5px;301            border: 1px solid #ccc;302            padding: 10px;303            font-size: 14px;304            margin-bottom: 15px;305            width: 100%;306        }307        .stFormSubmitButton {308            color: black;309            padding: 10px 20px;310            border-radius: 5px;311            font-size: 14px;312            cursor: pointer;313        }314 315        .client-name-container {316            padding: 20px;317            border-radius: 10px;318            background: linear-gradient(145deg, #ff7e5f, #feb47b);319            box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.1), -5px -5px 15px rgba(255, 255, 255, 0.3);320            transition: transform 0.3s ease, box-shadow 0.3s ease;321        }322 323        .client-name-container:hover {324            transform: translateY(-5px) scale(1.05);325            box-shadow: 8px 8px 20px rgba(0, 0, 0, 0.15), -8px -8px 20px rgba(255, 255, 255, 0.4);326        }327 328        .client-name {329            font-size: 2.5rem;330            font-weight: 700;331            color: #fff;332            text-align: center;333            margin: 0;334        }335 336        .form-container {337            display: flex;338            flex-direction: column;339            align-items: center;340        }341 342        .stButton>button {343            background-color: black;344            color: white;345            width: 150px;346            height: 40px;347            border: none;348            cursor: pointer;349            font-weight: bold;350            transition: all 0.3s;351        }352        .stButton>button:hover {353            background-color: white;354            color: black;355        }356        </style>357    '''358    st.markdown(css, unsafe_allow_html=True)359 360    summary_blob_name = f"{client_name}/{date}/{meeting}/summary.txt"361    transcription_blob_name = f"{client_name}/{date}/{meeting}/transcription.txt" 362    audio_blob_name = f"{client_name}/{date}/{meeting}/audio.wav"363 364    bucket = client.bucket(bucket_name)365 366    summary_blob = bucket.blob(summary_blob_name)367    summary_content = summary_blob.download_as_text()368 369    audio_blob = bucket.blob(audio_blob_name)370    audio_url = audio_blob.generate_signed_url(expiration=timedelta(hours=1), method='GET')371 372    summary_match = re.search(r"Summary:\s*(.*?)(?=\nKey Points:)", summary_content, re.DOTALL)373    summary = summary_match.group(1).strip() if summary_match else "Summary not found."374 375    key_points_match = re.search(r"Key Points:\s*(.*?)(?=\nAction Items:)", summary_content, re.DOTALL)376    key_points = re.findall(r"- (.*?)\n", key_points_match.group(1)) if key_points_match else ["Key points not found."]377 378    action_items_match = re.search(r"Action Items:\s*(.*)", summary_content, re.DOTALL)379    if action_items_match:380        action_items = re.findall(r"- (.*?)(?=\n- |$)", action_items_match.group(1), re.DOTALL)381    else:382        action_items = ["Action items not found."]383 384    transcription_blob = bucket.blob(transcription_blob_name)385    with transcription_blob.open("r") as file:386        meeting_data = json.load(file)387 388    speaker_data = {}389    total_talktime = 0390 391    for entry in meeting_data:392        speaker = entry["speaker"]393        duration = entry["end"] - entry["start"]394        text = entry["text"]395        total_talktime += duration396 397        if speaker not in speaker_data:398            speaker_data[speaker] = {"talktime": 0, "text": "", "words": 0}399 400        speaker_data[speaker]["talktime"] += duration401        speaker_data[speaker]["text"] += " " + text402        speaker_data[speaker]["words"] += len(text.split())403 404    for speaker, data in speaker_data.items():405        data["word_per_minute"] = round((data["words"] / data["talktime"] * 60), 2)406        data["talktime_percentage"] = round((data["talktime"] / total_talktime * 100), 2)407 408    combined_text = " ".join(data["text"] for data in speaker_data.values())409 410    analyzer = SentimentIntensityAnalyzer()411    sentences = combined_text.split('.')412    sentiment_polarity = [analyzer.polarity_scores(sentence)["compound"] for sentence in sentences if sentence.strip()]413    smoothed_polarity = gaussian_filter1d(sentiment_polarity, sigma=2)414 415    st.title("Kap Notes - Unveiling the story behind your meeting")416 417    st.markdown(f"### Summary\n<div class='summary-box'>{summary}</div>", unsafe_allow_html=True)418 419    st.markdown("### Meeting Highlights")420    st.markdown(421        f"<div class='keypoints-box'>" + "<br>".join(f"•&nbsp;{point}" for point in key_points) + "</div>",422        unsafe_allow_html=True423    )424 425    st.markdown("### Actionable Items")426    st.markdown(427        f"<div class='action-items-box'>" + "<br>".join(f"•&nbsp;{item}" for item in action_items) + "</div>",428        unsafe_allow_html=True429    )430 431    st.markdown("### Comments")432 433    if 'comments' not in st.session_state:434        st.session_state.comments = []435 436    def add_comment(comment):437        st.session_state.comments.append({"name": "", "comment": comment, "date": datetime.now().strftime("%d, %b %Y")})438 439    if 'name' not in st.session_state:440        st.session_state.name = ""441    if 'comment' not in st.session_state:442        st.session_state.comment = ""443 444    for comment in st.session_state.comments:445        st.markdown(446            f"""447            <div class="comment-box">448                <div class="comment-header">449                    <span>Admin</span>450                    <span>{comment['date']}</span>451                </div>452                <div class="comment-text">453                    {comment['comment']}454                </div>455            </div>456            """, unsafe_allow_html=True)457 458    with st.form(key="comment_form"):459        st.markdown('<div class="form-container">', unsafe_allow_html=True)460        comment_input = st.text_area("Your Comment", height=100, value=st.session_state.comment)461        submit_button = st.form_submit_button("Submit")462        st.markdown('</div>', unsafe_allow_html=True)463 464        if submit_button:465            if not comment_input:466                st.error("Enter your comment")  467            else:468                add_comment(comment_input)469                st.session_state.comment = "" 470                st.rerun()  471 472    with st.sidebar:473 474        speaker_names = list(speaker_data.keys())475        talk_time_percentages = [data["talktime_percentage"] for data in speaker_data.values()]476 477        color_palette = ["#A3BFF1", "#F4A7B9", "#C4F1D2", "#D6A7F2", "#FFD5A6", "#9BE1E6", "#F4A3C0", "#C1E7B4", "#F1D0FF", "#F9E9A6"]478        speaker_colors = {speaker: color_palette[i % len(color_palette)] for i, speaker in enumerate(speaker_names)} 479 480        st.markdown(f"""481        <div class="audio-player-container">482            <h4>Listen to the Meeting Audio</h4>483            <audio class="audio-player" controls>484                <source src="{audio_url}" type="audio/wav">485                Your browser does not support the audio element.486            </audio>487        </div>488        """, unsafe_allow_html=True)489            490        st.title("Chat Conversation")491        492        with st.expander("Click to view the chat conversation", expanded=False):493            chat_conversation = ""494            for index, entry in enumerate(meeting_data):495                speaker = entry["speaker"]496                text = entry["text"]497                talk_time = entry["end"] - entry["start"]498                speaker_color = speaker_colors[speaker]499                chat_conversation += f"""500                <div style="margin-bottom: 20px; background-color: {speaker_color}; padding: 15px; 501                            border-radius: 10px; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);">502                    <div style="display: flex; justify-content: space-between; align-items: center;">503                        <b style="color: black;">{speaker}</b>504                        <span style="color: black;">{talk_time:.2f} mins</span>505                    </div>506                    <div style="margin-top: 10px; text-align: justify; line-height: 1.6; color: black;">507                        {text}508                    </div>509                </div>510                """511            st.markdown(chat_conversation, unsafe_allow_html=True)512 513        fig = go.Figure(data=[go.Pie(labels=speaker_names, values=talk_time_percentages, marker=dict(colors=list(speaker_colors.values())), hole=0.3)])514        fig.update_layout(515            title="Speaker Analytics",516            showlegend=True,517            legend=dict(518                orientation="h",519                yanchor="top",520                y=-0.2,521                xanchor="center",522                x=0.5523            )524        )525        st.plotly_chart(fig)526 527        st.markdown("### Sentiment Analysis of the Meeting")528 529        fig = go.Figure()530        fig.add_trace(go.Scatter(x=list(range(len(smoothed_polarity))), y=smoothed_polarity, mode='lines', name='Sentiment', line=dict(color='blue')))531        fig.update_layout(532            xaxis=dict(title="Time (in seconds)"),533            yaxis=dict(title="Sentiment Score", range=[-1, 1]),534        )535        st.plotly_chart(fig)