ArunK-2003 commited on
Commit
c15f5d8
·
verified ·
1 Parent(s): 5fca03a

Uploaded UI Code

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