Ashhar commited on
Commit
21999ba
·
1 Parent(s): 7822987

auth final

Browse files
app.py CHANGED
@@ -13,8 +13,9 @@ from groq import Groq
13
 
14
  import constants as C
15
  import utils as U
16
- from auth import authenticateFunc
17
- from sidebar import showSidebar
 
18
 
19
  from dotenv import load_dotenv
20
  load_dotenv()
@@ -273,7 +274,10 @@ def __predict():
273
 
274
 
275
  def __generateImage(prompt: str):
276
- fluxClient = Client("black-forest-labs/FLUX.1-schnell")
 
 
 
277
  result = fluxClient.predict(
278
  prompt=prompt,
279
  seed=0,
@@ -315,10 +319,28 @@ def __paintImageIfApplicable(
315
  return imagePath
316
 
317
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  def __resetButtonState():
319
  st.session_state.buttonValue = ""
320
 
321
 
 
 
 
 
322
  def __resetSelectedStory():
323
  st.session_state.selectedStory = {}
324
 
@@ -348,6 +370,16 @@ if "selectedStoryTitle" not in st.session_state:
348
  if "isStoryChosen" not in st.session_state:
349
  st.session_state.isStoryChosen = False
350
 
 
 
 
 
 
 
 
 
 
 
351
  U.pprint("\n")
352
  U.pprint("\n")
353
 
@@ -360,15 +392,19 @@ def mainApp():
360
  __setStartMsg("")
361
  st.button(C.START_MSG, on_click=lambda: __setStartMsg(C.START_MSG))
362
 
363
- for chat in st.session_state.chatHistory:
364
  role = chat["role"]
365
  content = chat["content"]
366
  imagePath = chat.get("image")
 
367
  avatar = C.AI_ICON if role == "assistant" else C.USER_ICON
368
  with st.chat_message(role, avatar=avatar):
369
  st.markdown(content)
370
  if imagePath:
371
  st.image(imagePath)
 
 
 
372
 
373
  # U.pprint(f"{st.session_state.buttonValue=}")
374
  # U.pprint(f"{st.session_state.selectedStoryTitle=}")
@@ -381,6 +417,7 @@ def mainApp():
381
  or st.session_state["startMsg"]
382
  ):
383
  __resetButtonState()
 
384
  __setStartMsg("")
385
  if st.session_state["selectedStoryTitle"] != prompt:
386
  __resetSelectedStory()
@@ -419,10 +456,6 @@ def mainApp():
419
 
420
  U.pprint(f"{response=}")
421
 
422
- def selectButton(optionLabel):
423
- st.session_state["buttonValue"] = optionLabel
424
- U.pprint(f"Selected: {optionLabel}")
425
-
426
  rawResponse = response
427
  responseParts = response.split(C.JSON_SEPARATOR)
428
 
@@ -451,12 +484,8 @@ def mainApp():
451
  action = jsonObj.get("action")
452
 
453
  if options:
454
- for option in options:
455
- st.button(
456
- option["label"],
457
- key=option["id"],
458
- on_click=lambda label=option["label"]: selectButton(label)
459
- )
460
  elif action:
461
  U.pprint(f"{action=}")
462
  if action == "SHOW_STORY_DATABASE":
@@ -466,6 +495,8 @@ def mainApp():
466
  except Exception as e:
467
  U.pprint(e)
468
 
 
 
469
 
470
- authenticateFunc(mainApp)
471
  showSidebar()
 
13
 
14
  import constants as C
15
  import utils as U
16
+ from helpers.auth import runWithAuth
17
+ from helpers.sidebar import showSidebar
18
+ from helpers.activities import saveLatestActivity
19
 
20
  from dotenv import load_dotenv
21
  load_dotenv()
 
274
 
275
 
276
  def __generateImage(prompt: str):
277
+ fluxClient = Client(
278
+ "black-forest-labs/FLUX.1-schnell",
279
+ os.environ.get("HF_FLUX_CLIENT_TOKEN")
280
+ )
281
  result = fluxClient.predict(
282
  prompt=prompt,
283
  seed=0,
 
319
  return imagePath
320
 
321
 
322
+ def __selectButton(optionLabel: str):
323
+ st.session_state["buttonValue"] = optionLabel
324
+ U.pprint(f"Selected: {optionLabel}")
325
+
326
+
327
+ def __showButtons(options: list):
328
+ for option in options:
329
+ st.button(
330
+ option["label"],
331
+ key=option["id"],
332
+ on_click=lambda label=option["label"]: __selectButton(label)
333
+ )
334
+
335
+
336
  def __resetButtonState():
337
  st.session_state.buttonValue = ""
338
 
339
 
340
+ def __resetButtons():
341
+ st.session_state.buttons = []
342
+
343
+
344
  def __resetSelectedStory():
345
  st.session_state.selectedStory = {}
346
 
 
370
  if "isStoryChosen" not in st.session_state:
371
  st.session_state.isStoryChosen = False
372
 
373
+ if "buttons" not in st.session_state:
374
+ st.session_state.buttons = []
375
+
376
+ if "activityId" not in st.session_state:
377
+ st.session_state.activityId = None
378
+
379
+ if "userActivitiesLog" not in st.session_state:
380
+ st.session_state.userActivitiesLog = []
381
+
382
+
383
  U.pprint("\n")
384
  U.pprint("\n")
385
 
 
392
  __setStartMsg("")
393
  st.button(C.START_MSG, on_click=lambda: __setStartMsg(C.START_MSG))
394
 
395
+ for (i, chat) in enumerate(st.session_state.chatHistory):
396
  role = chat["role"]
397
  content = chat["content"]
398
  imagePath = chat.get("image")
399
+ buttons = chat.get("buttons")
400
  avatar = C.AI_ICON if role == "assistant" else C.USER_ICON
401
  with st.chat_message(role, avatar=avatar):
402
  st.markdown(content)
403
  if imagePath:
404
  st.image(imagePath)
405
+ if buttons:
406
+ __showButtons(buttons)
407
+ chat["buttons"] = []
408
 
409
  # U.pprint(f"{st.session_state.buttonValue=}")
410
  # U.pprint(f"{st.session_state.selectedStoryTitle=}")
 
417
  or st.session_state["startMsg"]
418
  ):
419
  __resetButtonState()
420
+ __resetButtons()
421
  __setStartMsg("")
422
  if st.session_state["selectedStoryTitle"] != prompt:
423
  __resetSelectedStory()
 
456
 
457
  U.pprint(f"{response=}")
458
 
 
 
 
 
459
  rawResponse = response
460
  responseParts = response.split(C.JSON_SEPARATOR)
461
 
 
484
  action = jsonObj.get("action")
485
 
486
  if options:
487
+ __showButtons(options)
488
+ st.session_state.buttons = options
 
 
 
 
489
  elif action:
490
  U.pprint(f"{action=}")
491
  if action == "SHOW_STORY_DATABASE":
 
495
  except Exception as e:
496
  U.pprint(e)
497
 
498
+ saveLatestActivity()
499
+
500
 
501
+ runWithAuth(mainApp)
502
  showSidebar()
constants.py CHANGED
@@ -156,7 +156,10 @@ Note that the final story should include twist, turns and events that make it re
156
  USER_ICON = "icons/man.png"
157
  AI_ICON = "icons/Kommuneity.png"
158
  LOGIN_ICON = "icons/authenticity.png"
 
 
159
  IMAGE_LOADER = "icons/Wedges.svg"
160
  TEXT_LOADER = "icons/balls.svg"
161
  DB_LOADER = "icons/db_loader.svg"
 
162
  START_MSG = "I want to create a story 😊"
 
156
  USER_ICON = "icons/man.png"
157
  AI_ICON = "icons/Kommuneity.png"
158
  LOGIN_ICON = "icons/authenticity.png"
159
+ AVATAR_ICON = "icons/avatar.png"
160
+
161
  IMAGE_LOADER = "icons/Wedges.svg"
162
  TEXT_LOADER = "icons/balls.svg"
163
  DB_LOADER = "icons/db_loader.svg"
164
+
165
  START_MSG = "I want to create a story 😊"
data/userActivities.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import datetime as DT
3
+ from typing import TypedDict, List
4
+ from supabase import create_client, Client
5
+ from zoneinfo import ZoneInfo
6
+
7
+ from dotenv import load_dotenv
8
+ load_dotenv()
9
+
10
+ url: str = os.environ.get("SUPABASE_URL")
11
+ key: str = os.environ.get("SUPABASE_KEY")
12
+ supabase: Client = create_client(url, key)
13
+
14
+ Activity = TypedDict("Activity", {
15
+ "id": str,
16
+ "email": str,
17
+ "chat_history": list,
18
+ "messages": list,
19
+ "buttons": list,
20
+ "created_at": str,
21
+ "updated_at": str,
22
+ })
23
+
24
+
25
+ def getUserActivities(emailId: str, id=None) -> List[Activity]:
26
+ filterQuery = supabase.table("user_activities") \
27
+ .select("*") \
28
+ .eq("email", emailId)
29
+
30
+ if id:
31
+ filterQuery = filterQuery.eq("id", id)
32
+
33
+ response = filterQuery \
34
+ .order("updated_at", desc=True) \
35
+ .execute()
36
+
37
+ activities = response.data
38
+ return activities or []
39
+
40
+
41
+ def createUserActivity(emailId: str, chatHistory: list, messages: list, buttons: list) -> str:
42
+ data, count = supabase.table('user_activities').insert({
43
+ "email": emailId,
44
+ "chat_history": chatHistory,
45
+ "messages": messages,
46
+ "buttons": buttons,
47
+ }).execute()
48
+
49
+ if data:
50
+ print(f"{data=}")
51
+ activityId = data[1][0]["id"]
52
+ return activityId
53
+
54
+
55
+ def updateUserActivityById(id: str, chatHistory: list, messages: list, buttons: list):
56
+ data, count = supabase.table('user_activities').update({
57
+ "chat_history": chatHistory,
58
+ "messages": messages,
59
+ "buttons": buttons,
60
+ "updated_at": DT.datetime.now(ZoneInfo("UTC")).isoformat()
61
+ }).eq("id", id).execute()
helpers/activities.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from typing import List, TypedDict
3
+ import utils as U
4
+ from data import userActivities
5
+
6
+
7
+ class ActivityLog(TypedDict):
8
+ id: str
9
+ created_at: str
10
+ updated_at: str
11
+
12
+
13
+ ActivityLogs = List[ActivityLog]
14
+
15
+
16
+ def saveLatestActivity():
17
+ emailId = st.session_state.get("user", {}).get("email")
18
+ if not emailId:
19
+ U.pprint("No user email found")
20
+ return
21
+
22
+ activityId = st.session_state.activityId
23
+
24
+ if activityId:
25
+ userActivities.updateUserActivityById(
26
+ id=activityId,
27
+ chatHistory=st.session_state.get("chatHistory", []),
28
+ messages=st.session_state.get("messages", []),
29
+ buttons=st.session_state.get("buttons", [])
30
+ )
31
+ U.pprint("Current activity updated")
32
+ else:
33
+ activityId = userActivities.createUserActivity(
34
+ emailId=emailId,
35
+ chatHistory=st.session_state.get("chatHistory", []),
36
+ messages=st.session_state.get("messages", []),
37
+ buttons=st.session_state.get("buttons", [])
38
+ )
39
+ U.pprint("New activity created")
40
+ st.session_state.activityId = activityId
41
+
42
+
43
+ def __convertActivitiesToLog(activities) -> ActivityLogs:
44
+ if not activities:
45
+ return []
46
+
47
+ logs = []
48
+ for activity in activities:
49
+ logs.append({
50
+ "id": activity.get("id"),
51
+ "created_at": activity.get("created_at"),
52
+ "updated_at": activity.get("updated_at"),
53
+ })
54
+ return logs
55
+
56
+
57
+ def __retrieveUserActivities():
58
+ emailId = st.session_state.get("user", {}).get("email")
59
+ if not emailId:
60
+ U.pprint("No user email found")
61
+ return
62
+
63
+ activities = userActivities.getUserActivities(emailId)
64
+ st.session_state.userActivitiesLog = __convertActivitiesToLog(activities)
65
+ U.pprint(f"{len(activities)=}")
66
+ return activities
67
+
68
+
69
+ def restoreUserActivity(activityId: str = None):
70
+ activities = __retrieveUserActivities()
71
+ if activityId:
72
+ activities = [activity for activity in activities if activity.get("id") == activityId]
73
+
74
+ if activities:
75
+ latestActivity = activities[0]
76
+ U.pprint(f"{latestActivity=}")
77
+ activityId = latestActivity.get("id")
78
+ messages = latestActivity.get("messages", [])
79
+ chatHistory = latestActivity.get("chat_history", [])
80
+ buttons = latestActivity.get("buttons", [])
81
+
82
+ st.session_state.activityId = activityId
83
+ st.session_state.messages = messages
84
+ st.session_state.chatHistory = chatHistory
85
+
86
+ if chatHistory and buttons:
87
+ st.session_state.chatHistory[-1]["buttons"] = buttons
88
+
89
+ st.rerun()
90
+
91
+
92
+ def resetActivity():
93
+ keysToDelete = [
94
+ "activityId",
95
+ "messages",
96
+ "chatHistory",
97
+ "userActivitiesLog",
98
+ "buttons",
99
+ "buttonValue",
100
+ "isStoryChosen",
101
+ "selectedStory",
102
+ "selectedStoryTitle",
103
+ "startMsg",
104
+ "isStartMsgChosen"
105
+ ]
106
+ for key in keysToDelete:
107
+ if key in st.session_state:
108
+ del st.session_state[key]
109
+
110
+ __retrieveUserActivities()
111
+ st.rerun()
auth.py → helpers/auth.py RENAMED
@@ -3,7 +3,8 @@ from typing import Callable, Any
3
  import streamlit as st
4
  from descope.descope_client import DescopeClient
5
  from descope.exceptions import AuthException
6
- import constants as C
 
7
 
8
  from dotenv import load_dotenv
9
  load_dotenv()
@@ -12,7 +13,7 @@ DESCOPE_PROJECT_ID = os.environ.get("DESCOPE_PROJECT_ID")
12
  descopeClient = DescopeClient(project_id=DESCOPE_PROJECT_ID)
13
 
14
 
15
- def authenticateFunc(func: Callable[[], Any]):
16
  if "token" not in st.session_state:
17
  if "code" in st.query_params:
18
  code = st.query_params["code"]
@@ -25,11 +26,13 @@ def authenticateFunc(func: Callable[[], Any]):
25
  "jwt"
26
  )
27
  st.session_state["user"] = jwtResponse["user"]
 
 
28
  st.rerun()
29
  except AuthException:
30
  st.error("Login failed!")
31
 
32
- st.warning("Create magic. Login to begin!", icon=":material/login:")
33
  with st.container(border=False):
34
  if st.button(
35
  "Sign in with Google",
@@ -47,11 +50,12 @@ def authenticateFunc(func: Callable[[], Any]):
47
  )
48
  else:
49
  try:
50
- with st.spinner("Authenticating ..."):
51
  jwtResponse = descopeClient.validate_and_refresh_session(
52
  st.session_state.token, st.session_state.refreshToken
53
  )
54
  st.session_state["token"] = jwtResponse["sessionToken"].get("jwt")
 
55
 
56
  func()
57
  except AuthException:
 
3
  import streamlit as st
4
  from descope.descope_client import DescopeClient
5
  from descope.exceptions import AuthException
6
+ from helpers.activities import restoreUserActivity
7
+ import utils as U
8
 
9
  from dotenv import load_dotenv
10
  load_dotenv()
 
13
  descopeClient = DescopeClient(project_id=DESCOPE_PROJECT_ID)
14
 
15
 
16
+ def runWithAuth(func: Callable[[], Any]):
17
  if "token" not in st.session_state:
18
  if "code" in st.query_params:
19
  code = st.query_params["code"]
 
26
  "jwt"
27
  )
28
  st.session_state["user"] = jwtResponse["user"]
29
+ with st.spinner("Restoring your chats ..."):
30
+ restoreUserActivity()
31
  st.rerun()
32
  except AuthException:
33
  st.error("Login failed!")
34
 
35
+ st.warning("Login to Unlock the Magic ✨", icon=":material/login:")
36
  with st.container(border=False):
37
  if st.button(
38
  "Sign in with Google",
 
50
  )
51
  else:
52
  try:
53
+ with st.spinner("Verifying your identity ..."):
54
  jwtResponse = descopeClient.validate_and_refresh_session(
55
  st.session_state.token, st.session_state.refreshToken
56
  )
57
  st.session_state["token"] = jwtResponse["sessionToken"].get("jwt")
58
+ U.pprint("User successfully authenticated!")
59
 
60
  func()
61
  except AuthException:
helpers/sidebar.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ import datetime as DT
4
+ from helpers.activities import resetActivity, restoreUserActivity, ActivityLogs
5
+ import constants as C
6
+ import utils as U
7
+
8
+
9
+ def isValidImageUrl(url):
10
+ if not url:
11
+ return False
12
+ try:
13
+ imageResponse = requests.head(url, timeout=5)
14
+ contentType = imageResponse.headers.get('Content-Type', '')
15
+ return imageResponse.status_code == 200 and contentType.startswith('image/')
16
+ except Exception:
17
+ return False
18
+
19
+
20
+ def showSidebar():
21
+ with st.sidebar:
22
+ if not ("user" in st.session_state and "token" in st.session_state):
23
+ return
24
+
25
+ st.markdown("""
26
+ <style>
27
+ .user-info {
28
+ display: flex;
29
+ align-items: center;
30
+ padding: 10px;
31
+ background-color: rgba(255, 255, 255, 0.1);
32
+ border-radius: 5px;
33
+ margin-bottom: 10px;
34
+ margin-top: -2rem;
35
+ }
36
+ .user-avatar {
37
+ width: 40px;
38
+ height: 40px;
39
+ border-radius: 50%;
40
+ margin-right: 10px;
41
+ }
42
+ .user-details {
43
+ flex-grow: 1;
44
+ }
45
+ .user-name {
46
+ font-weight: bold;
47
+ margin: 0;
48
+ }
49
+ .user-email {
50
+ font-size: 0.8em;
51
+ color: #888;
52
+ margin: 0;
53
+ }
54
+ </style>
55
+ """, unsafe_allow_html=True)
56
+
57
+ userAvatar = st.session_state["user"].get("picture", "https://example.com/default-avatar.png")
58
+ if not isValidImageUrl(userAvatar):
59
+ userAvatar = C.AVATAR_ICON
60
+ userName = st.session_state["user"].get("name", "User")
61
+ userEmail = st.session_state["user"].get("email", "")
62
+
63
+ st.markdown(f"""
64
+ <div class="user-info">
65
+ <img src="{userAvatar}" class="user-avatar">
66
+ <div class="user-details">
67
+ <p class="user-name">{userName}</p>
68
+ <p class="user-email">{userEmail}</p>
69
+ </div>
70
+ </div>
71
+ """, unsafe_allow_html=True)
72
+
73
+ if st.button("Logout", key="logout_button", type="secondary", use_container_width=True):
74
+ for key in ["token", "user"]:
75
+ if key in st.session_state:
76
+ del st.session_state[key]
77
+ st.rerun()
78
+
79
+ st.markdown("---")
80
+
81
+ if st.button("\\+ New Story", help="Your current story will be auto-saved 😊", key="save_activities_button", type="primary", use_container_width=True):
82
+ resetActivity()
83
+
84
+ userActivityLogs: ActivityLogs = st.session_state.get("userActivitiesLog", [])
85
+ U.pprint(f"{userActivityLogs=}")
86
+
87
+ if not userActivityLogs:
88
+ return
89
+
90
+ st.markdown("""
91
+ ---
92
+ ### Your Past Stories
93
+ """)
94
+ with st.container(height=300, border=False):
95
+ for log in userActivityLogs:
96
+ updatedAt = DT.datetime.fromisoformat(log["updated_at"].replace("Z", "+00:00"))
97
+ localTime = updatedAt.astimezone().strftime("%b %d, %I:%M %p")
98
+ activityId = log["id"]
99
+ if activityId == st.session_state.get("activityId"):
100
+ continue
101
+ if st.button(f"Saved ⌛ {localTime}", key=f"activity_{log['id']}", use_container_width=True):
102
+ restoreUserActivity(log["id"])
icons/avatar.png ADDED
sidebar.py DELETED
@@ -1,57 +0,0 @@
1
- import streamlit as st
2
-
3
-
4
- def showSidebar():
5
- with st.sidebar:
6
- if "user" in st.session_state and "token" in st.session_state:
7
- st.markdown("""
8
- <style>
9
- .user-info {
10
- display: flex;
11
- align-items: center;
12
- padding: 10px;
13
- background-color: rgba(255, 255, 255, 0.1);
14
- border-radius: 5px;
15
- margin-bottom: 10px;
16
- margin-top: -2rem;
17
- }
18
- .user-avatar {
19
- width: 40px;
20
- height: 40px;
21
- border-radius: 50%;
22
- margin-right: 10px;
23
- }
24
- .user-details {
25
- flex-grow: 1;
26
- }
27
- .user-name {
28
- font-weight: bold;
29
- margin: 0;
30
- }
31
- .user-email {
32
- font-size: 0.8em;
33
- color: #888;
34
- margin: 0;
35
- }
36
- </style>
37
- """, unsafe_allow_html=True)
38
-
39
- user_avatar = st.session_state["user"].get("picture", "https://example.com/default-avatar.png")
40
- user_name = st.session_state["user"].get("name", "User")
41
- user_email = st.session_state["user"].get("email", "")
42
-
43
- st.markdown(f"""
44
- <div class="user-info">
45
- <img src="{user_avatar}" class="user-avatar">
46
- <div class="user-details">
47
- <p class="user-name">{user_name}</p>
48
- <p class="user-email">{user_email}</p>
49
- </div>
50
- </div>
51
- """, unsafe_allow_html=True)
52
-
53
- if st.button("Logout", key="logout_button", type="secondary", use_container_width=True):
54
- for key in ["token", "user"]:
55
- if key in st.session_state:
56
- del st.session_state[key]
57
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils.py CHANGED
@@ -115,6 +115,15 @@ def applyCommonStyles():
115
  background-color: #1a1c23 !important;
116
  }
117
 
 
 
 
 
 
 
 
 
 
118
  </style>
119
  """,
120
  unsafe_allow_html=True
 
115
  background-color: #1a1c23 !important;
116
  }
117
 
118
+ div[data-testid="stSidebarUserContent"] {
119
+ padding-bottom: 1rem !important;
120
+ }
121
+
122
+ div[data-testid="stMarkdownContainer"] > hr {
123
+ margin-top: 0.5rem;
124
+ margin-bottom: 1rem;
125
+ }
126
+
127
  </style>
128
  """,
129
  unsafe_allow_html=True