Thomas (Tom) Gardos commited on
Commit
eb53786
·
2 Parent(s): f8a5090 e3a8249

Merge pull request #63 from DL4DS/fastapi

Browse files
Dockerfile CHANGED
@@ -35,4 +35,4 @@ RUN --mount=type=secret,id=LITERAL_API_KEY_LOGGING,mode=0444,required=true
35
  RUN --mount=type=secret,id=CHAINLIT_AUTH_SECRET,mode=0444,required=true
36
 
37
  # Default command to run the application
38
- CMD ["sh", "-c", "python -m modules.vectorstore.store_manager && chainlit run main.py --host 0.0.0.0 --port 7860"]
 
35
  RUN --mount=type=secret,id=CHAINLIT_AUTH_SECRET,mode=0444,required=true
36
 
37
  # Default command to run the application
38
+ CMD ["sh", "-c", "python -m modules.vectorstore.store_manager && uvicorn app:app --host 0.0.0.0 --port 7860"]
code/app.py ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, Response
2
+ from fastapi.responses import HTMLResponse, RedirectResponse
3
+ from fastapi.templating import Jinja2Templates
4
+ from google.oauth2 import id_token
5
+ from google.auth.transport import requests as google_requests
6
+ from google_auth_oauthlib.flow import Flow
7
+ from chainlit.utils import mount_chainlit
8
+ import secrets
9
+ import json
10
+ import base64
11
+ from modules.config.constants import (
12
+ OAUTH_GOOGLE_CLIENT_ID,
13
+ OAUTH_GOOGLE_CLIENT_SECRET,
14
+ CHAINLIT_URL,
15
+ )
16
+ from fastapi.middleware.cors import CORSMiddleware
17
+ from fastapi.staticfiles import StaticFiles
18
+
19
+ GOOGLE_CLIENT_ID = OAUTH_GOOGLE_CLIENT_ID
20
+ GOOGLE_CLIENT_SECRET = OAUTH_GOOGLE_CLIENT_SECRET
21
+ GOOGLE_REDIRECT_URI = f"{CHAINLIT_URL}/auth/oauth/google/callback"
22
+
23
+ app = FastAPI()
24
+ app.mount("/public", StaticFiles(directory="public"), name="public")
25
+ app.add_middleware(
26
+ CORSMiddleware,
27
+ allow_origins=["*"], # Update with appropriate origins
28
+ allow_methods=["*"],
29
+ allow_headers=["*"], # or specify the headers you want to allow
30
+ expose_headers=["X-User-Info"], # Expose the custom header
31
+ )
32
+
33
+ templates = Jinja2Templates(directory="templates")
34
+ session_store = {}
35
+ CHAINLIT_PATH = "/chainlit_tutor"
36
+
37
+ USER_ROLES = {
38
+ "[email protected]": ["instructor", "bu"],
39
+ "[email protected]": ["instructor", "bu"],
40
+ "[email protected]": ["instructor", "bu"],
41
+ "[email protected]": ["guest"],
42
+ # Add more users and roles as needed
43
+ }
44
+
45
+ # Create a Google OAuth flow
46
+ flow = Flow.from_client_config(
47
+ {
48
+ "web": {
49
+ "client_id": GOOGLE_CLIENT_ID,
50
+ "client_secret": GOOGLE_CLIENT_SECRET,
51
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
52
+ "token_uri": "https://oauth2.googleapis.com/token",
53
+ "redirect_uris": [GOOGLE_REDIRECT_URI],
54
+ "scopes": [
55
+ "openid",
56
+ # "https://www.googleapis.com/auth/userinfo.email",
57
+ # "https://www.googleapis.com/auth/userinfo.profile",
58
+ ],
59
+ }
60
+ },
61
+ scopes=[
62
+ "openid",
63
+ "https://www.googleapis.com/auth/userinfo.email",
64
+ "https://www.googleapis.com/auth/userinfo.profile",
65
+ ],
66
+ redirect_uri=GOOGLE_REDIRECT_URI,
67
+ )
68
+
69
+
70
+ def get_user_role(username: str):
71
+ return USER_ROLES.get(username, ["student"]) # Default to "student" role
72
+
73
+
74
+ def get_user_info_from_cookie(request: Request):
75
+ user_info_encoded = request.cookies.get("X-User-Info")
76
+ if user_info_encoded:
77
+ try:
78
+ user_info_json = base64.b64decode(user_info_encoded).decode()
79
+ return json.loads(user_info_json)
80
+ except Exception as e:
81
+ print(f"Error decoding user info: {e}")
82
+ return None
83
+ return None
84
+
85
+
86
+ def get_user_info(request: Request):
87
+ session_token = request.cookies.get("session_token")
88
+ if session_token and session_token in session_store:
89
+ return session_store[session_token]
90
+ return None
91
+
92
+
93
+ @app.get("/", response_class=HTMLResponse)
94
+ async def login_page(request: Request):
95
+ user_info = get_user_info_from_cookie(request)
96
+ if user_info and user_info.get("google_signed_in"):
97
+ return RedirectResponse("/post-signin")
98
+ return templates.TemplateResponse("login.html", {"request": request})
99
+
100
+
101
+ @app.get("/login/guest")
102
+ @app.post("/login/guest")
103
+ async def login_guest():
104
+ username = "guest"
105
+ session_token = secrets.token_hex(16)
106
+ unique_session_id = secrets.token_hex(8)
107
+ username = f"{username}_{unique_session_id}"
108
+ session_store[session_token] = {
109
+ "email": username,
110
+ "name": "Guest",
111
+ "profile_image": "",
112
+ "google_signed_in": False, # Ensure guest users do not have this flag
113
+ }
114
+ user_info_json = json.dumps(session_store[session_token])
115
+ user_info_encoded = base64.b64encode(user_info_json.encode()).decode()
116
+
117
+ # Set cookies
118
+ response = RedirectResponse(url="/post-signin", status_code=303)
119
+ response.set_cookie(key="session_token", value=session_token)
120
+ response.set_cookie(key="X-User-Info", value=user_info_encoded, httponly=True)
121
+ return response
122
+
123
+
124
+ @app.get("/login/google")
125
+ async def login_google(request: Request):
126
+ # Clear any existing session cookies to avoid conflicts with guest sessions
127
+ response = RedirectResponse(url="/post-signin")
128
+ response.delete_cookie(key="session_token")
129
+ response.delete_cookie(key="X-User-Info")
130
+
131
+ user_info = get_user_info_from_cookie(request)
132
+ print(f"User info: {user_info}")
133
+ # Check if user is already signed in using Google
134
+ if user_info and user_info.get("google_signed_in"):
135
+ return RedirectResponse("/post-signin")
136
+ else:
137
+ authorization_url, _ = flow.authorization_url(prompt="consent")
138
+ return RedirectResponse(authorization_url, headers=response.headers)
139
+
140
+
141
+ @app.get("/auth/oauth/google/callback")
142
+ async def auth_google(request: Request):
143
+ try:
144
+ flow.fetch_token(code=request.query_params.get("code"))
145
+ credentials = flow.credentials
146
+ user_info = id_token.verify_oauth2_token(
147
+ credentials.id_token, google_requests.Request(), GOOGLE_CLIENT_ID
148
+ )
149
+
150
+ email = user_info["email"]
151
+ name = user_info.get("name", "")
152
+ profile_image = user_info.get("picture", "")
153
+
154
+ session_token = secrets.token_hex(16)
155
+ session_store[session_token] = {
156
+ "email": email,
157
+ "name": name,
158
+ "profile_image": profile_image,
159
+ "google_signed_in": True, # Set this flag to True for Google-signed users
160
+ }
161
+
162
+ user_info_json = json.dumps(session_store[session_token])
163
+ user_info_encoded = base64.b64encode(user_info_json.encode()).decode()
164
+
165
+ # Set cookies
166
+ response = RedirectResponse(url="/post-signin", status_code=303)
167
+ response.set_cookie(key="session_token", value=session_token)
168
+ response.set_cookie(key="X-User-Info", value=user_info_encoded, httponly=True)
169
+ return response
170
+ except Exception as e:
171
+ print(f"Error during Google OAuth callback: {e}")
172
+ return RedirectResponse(url="/", status_code=302)
173
+
174
+
175
+ @app.get("/post-signin", response_class=HTMLResponse)
176
+ async def post_signin(request: Request):
177
+ user_info = get_user_info_from_cookie(request)
178
+ if not user_info:
179
+ user_info = get_user_info(request)
180
+ # if user_info and user_info.get("google_signed_in"):
181
+ if user_info:
182
+ username = user_info["email"]
183
+ role = get_user_role(username)
184
+ jwt_token = request.cookies.get("X-User-Info")
185
+ return templates.TemplateResponse(
186
+ "dashboard.html",
187
+ {
188
+ "request": request,
189
+ "username": username,
190
+ "role": role,
191
+ "jwt_token": jwt_token,
192
+ },
193
+ )
194
+ return RedirectResponse("/")
195
+
196
+
197
+ @app.post("/start-tutor")
198
+ async def start_tutor(request: Request):
199
+ user_info = get_user_info_from_cookie(request)
200
+ if user_info:
201
+ user_info_json = json.dumps(user_info)
202
+ user_info_encoded = base64.b64encode(user_info_json.encode()).decode()
203
+
204
+ response = RedirectResponse(CHAINLIT_PATH, status_code=303)
205
+ response.set_cookie(key="X-User-Info", value=user_info_encoded, httponly=True)
206
+ return response
207
+
208
+ return RedirectResponse(url="/")
209
+
210
+
211
+ @app.exception_handler(Exception)
212
+ async def exception_handler(request: Request, exc: Exception):
213
+ return templates.TemplateResponse(
214
+ "error.html", {"request": request, "error": str(exc)}, status_code=500
215
+ )
216
+
217
+
218
+ @app.get("/chainlit_tutor/logout", response_class=HTMLResponse)
219
+ @app.post("/chainlit_tutor/logout", response_class=HTMLResponse)
220
+ async def app_logout(request: Request, response: Response):
221
+ # Clear session cookies
222
+ response.delete_cookie("session_token")
223
+ response.delete_cookie("X-User-Info")
224
+
225
+ print("logout_page called")
226
+
227
+ # Redirect to the logout page with embedded JavaScript
228
+ return RedirectResponse(url="/", status_code=302)
229
+
230
+
231
+ mount_chainlit(app=app, target="main.py", path=CHAINLIT_PATH)
232
+
233
+ if __name__ == "__main__":
234
+ import uvicorn
235
+
236
+ uvicorn.run(app, host="127.0.0.1", port=7860)
code/main.py CHANGED
@@ -5,7 +5,6 @@ from modules.config.constants import (
5
  LITERAL_API_URL,
6
  )
7
  from modules.chat_processor.literal_ai import CustomLiteralDataLayer
8
-
9
  import json
10
  import yaml
11
  from typing import Any, Dict, no_type_check
@@ -21,6 +20,7 @@ import copy
21
  from typing import Optional
22
  from chainlit.types import ThreadDict
23
  import time
 
24
 
25
  USER_TIMEOUT = 60_000
26
  SYSTEM = "System"
@@ -445,14 +445,41 @@ class Chatbot:
445
  cl.user_session.set("memory", conversation_list)
446
  await self.start(config=thread_config)
447
 
448
- @cl.oauth_callback
449
- def auth_callback(
450
- provider_id: str,
451
- token: str,
452
- raw_user_data: Dict[str, str],
453
- default_user: cl.User,
454
- ) -> Optional[cl.User]:
455
- return default_user
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
 
457
  async def on_follow_up(self, action: cl.Action):
458
  message = await cl.Message(
@@ -482,4 +509,8 @@ async def start_app():
482
  cl.action_callback("follow up question")(chatbot.on_follow_up)
483
 
484
 
485
- asyncio.run(start_app())
 
 
 
 
 
5
  LITERAL_API_URL,
6
  )
7
  from modules.chat_processor.literal_ai import CustomLiteralDataLayer
 
8
  import json
9
  import yaml
10
  from typing import Any, Dict, no_type_check
 
20
  from typing import Optional
21
  from chainlit.types import ThreadDict
22
  import time
23
+ import base64
24
 
25
  USER_TIMEOUT = 60_000
26
  SYSTEM = "System"
 
445
  cl.user_session.set("memory", conversation_list)
446
  await self.start(config=thread_config)
447
 
448
+ # @cl.oauth_callback
449
+ # def auth_callback(
450
+ # provider_id: str,
451
+ # token: str,
452
+ # raw_user_data: Dict[str, str],
453
+ # default_user: cl.User,
454
+ # ) -> Optional[cl.User]:
455
+ # return default_user
456
+
457
+ @cl.header_auth_callback
458
+ def header_auth_callback(headers: dict) -> Optional[cl.User]:
459
+
460
+ print("\n\n\nI am here\n\n\n")
461
+ # try: # TODO: Add try-except block after testing
462
+ # TODO: Implement to get the user information from the headers (not the cookie)
463
+ cookie = headers.get("cookie") # gets back a str
464
+ # Create a dictionary from the pairs
465
+ cookie_dict = {}
466
+ for pair in cookie.split("; "):
467
+ key, value = pair.split("=", 1)
468
+ # Strip surrounding quotes if present
469
+ cookie_dict[key] = value.strip('"')
470
+
471
+ decoded_user_info = base64.b64decode(
472
+ cookie_dict.get("X-User-Info", "")
473
+ ).decode()
474
+ decoded_user_info = json.loads(decoded_user_info)
475
+
476
+ return cl.User(
477
+ identifier=decoded_user_info["email"],
478
+ metadata={
479
+ "name": decoded_user_info["name"],
480
+ "avatar": decoded_user_info["profile_image"],
481
+ },
482
+ )
483
 
484
  async def on_follow_up(self, action: cl.Action):
485
  message = await cl.Message(
 
509
  cl.action_callback("follow up question")(chatbot.on_follow_up)
510
 
511
 
512
+ loop = asyncio.get_event_loop()
513
+ if loop.is_running():
514
+ asyncio.ensure_future(start_app())
515
+ else:
516
+ asyncio.run(start_app())
code/modules/config/constants.py CHANGED
@@ -12,6 +12,7 @@ LLAMA_CLOUD_API_KEY = os.getenv("LLAMA_CLOUD_API_KEY")
12
  HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_TOKEN")
13
  LITERAL_API_KEY_LOGGING = os.getenv("LITERAL_API_KEY_LOGGING")
14
  LITERAL_API_URL = os.getenv("LITERAL_API_URL")
 
15
 
16
  OAUTH_GOOGLE_CLIENT_ID = os.getenv("OAUTH_GOOGLE_CLIENT_ID")
17
  OAUTH_GOOGLE_CLIENT_SECRET = os.getenv("OAUTH_GOOGLE_CLIENT_SECRET")
 
12
  HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_TOKEN")
13
  LITERAL_API_KEY_LOGGING = os.getenv("LITERAL_API_KEY_LOGGING")
14
  LITERAL_API_URL = os.getenv("LITERAL_API_URL")
15
+ CHAINLIT_URL = os.getenv("CHAINLIT_URL")
16
 
17
  OAUTH_GOOGLE_CLIENT_ID = os.getenv("OAUTH_GOOGLE_CLIENT_ID")
18
  OAUTH_GOOGLE_CLIENT_SECRET = os.getenv("OAUTH_GOOGLE_CLIENT_SECRET")
code/templates/dashboard.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Dashboard</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
9
+
10
+ body, html {
11
+ margin: 0;
12
+ padding: 0;
13
+ font-family: 'Inter', sans-serif;
14
+ background: url('./public/space.jpg') no-repeat center center fixed;
15
+ background-size: cover;
16
+ background-color: #1a1a1a;
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ height: 100vh;
21
+ color: #f5f5f5;
22
+ }
23
+
24
+ .container {
25
+ background: linear-gradient(145deg, rgba(255, 255, 255, 0.85), rgba(240, 240, 240, 0.85));
26
+ border: 1px solid rgba(255, 255, 255, 0.3);
27
+ border-radius: 12px;
28
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.37);
29
+ backdrop-filter: blur(10px);
30
+ -webkit-backdrop-filter: blur(10px);
31
+ width: 100%;
32
+ max-width: 400px;
33
+ padding: 40px;
34
+ box-sizing: border-box;
35
+ text-align: center;
36
+ position: relative;
37
+ overflow: hidden;
38
+ }
39
+
40
+ .container:before {
41
+ content: '';
42
+ position: absolute;
43
+ top: 0;
44
+ left: 0;
45
+ width: 100%;
46
+ height: 100%;
47
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0) 70%);
48
+ z-index: 1;
49
+ pointer-events: none;
50
+ }
51
+
52
+ .container > * {
53
+ position: relative;
54
+ z-index: 2;
55
+ }
56
+
57
+ .avatar {
58
+ width: 80px;
59
+ height: 80px;
60
+ border-radius: 50%;
61
+ margin-bottom: 20px;
62
+ border: 2px solid #fff;
63
+ }
64
+
65
+ .container h1 {
66
+ margin-bottom: 20px;
67
+ font-size: 26px;
68
+ font-weight: 600;
69
+ color: #333;
70
+ }
71
+
72
+ .container p {
73
+ font-size: 15px;
74
+ color: #666;
75
+ margin-bottom: 30px;
76
+ }
77
+
78
+ .button {
79
+ padding: 12px 0;
80
+ margin: 10px 0;
81
+ font-size: 16px;
82
+ border: none;
83
+ border-radius: 5px;
84
+ cursor: pointer;
85
+ width: 100%;
86
+ transition: background-color 0.3s ease, color 0.3s ease;
87
+ background-color: #FAFAFA;
88
+ color: #333;
89
+ }
90
+
91
+ .button:hover {
92
+ background-color: #f0f0f0;
93
+ }
94
+
95
+ .start-button {
96
+ background-color: #4CAF50;
97
+ color: #fff;
98
+ }
99
+
100
+ .start-button:hover {
101
+ background-color: #45a049;
102
+ }
103
+ </style>
104
+ </head>
105
+ <body>
106
+ <div class="container">
107
+ <img src="/public/avatars/ai_tutor.png" alt="AI Tutor Avatar" class="avatar">
108
+ <h1>Welcome, {{ username }}</h1>
109
+ <p>Ready to start your AI tutoring session?</p>
110
+ <form action="/start-tutor" method="post">
111
+ <button type="submit" class="button start-button">Start AI Tutor</button>
112
+ </form>
113
+ </div>
114
+ <script>
115
+ let token = "{{ jwt_token }}";
116
+ console.log("Token: ", token);
117
+ localStorage.setItem('token', token);
118
+ </script>
119
+ </body>
120
+ </html>
code/templates/login.html ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Login</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
9
+
10
+ body, html {
11
+ margin: 0;
12
+ padding: 0;
13
+ font-family: 'Inter', sans-serif;
14
+ background: url('/public/space.jpg') no-repeat center center fixed;
15
+ background-size: cover;
16
+ background-color: #1a1a1a;
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ height: 100vh;
21
+ color: #f5f5f5;
22
+ }
23
+
24
+ .container {
25
+ background: linear-gradient(145deg, rgba(255, 255, 255, 0.85), rgba(240, 240, 240, 0.85));
26
+ border: 1px solid rgba(255, 255, 255, 0.3);
27
+ border-radius: 12px;
28
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.37);
29
+ backdrop-filter: blur(10px);
30
+ -webkit-backdrop-filter: blur(10px);
31
+ width: 100%;
32
+ max-width: 400px;
33
+ padding: 40px;
34
+ box-sizing: border-box;
35
+ text-align: center;
36
+ position: relative;
37
+ overflow: hidden;
38
+ }
39
+
40
+ .container:before {
41
+ content: '';
42
+ position: absolute;
43
+ top: 0;
44
+ left: 0;
45
+ width: 100%;
46
+ height: 100%;
47
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0) 70%);
48
+ z-index: 1;
49
+ pointer-events: none;
50
+ }
51
+
52
+ .container > * {
53
+ position: relative;
54
+ z-index: 2;
55
+ }
56
+
57
+ .avatar {
58
+ width: 80px;
59
+ height: 80px;
60
+ border-radius: 50%;
61
+ margin-bottom: 20px;
62
+ border: 2px solid #fff;
63
+ }
64
+
65
+ .container h1 {
66
+ margin-bottom: 20px;
67
+ font-size: 26px;
68
+ font-weight: 600;
69
+ color: #333;
70
+ }
71
+
72
+ .container p {
73
+ font-size: 15px;
74
+ color: #666;
75
+ margin-bottom: 30px;
76
+ }
77
+
78
+ .button {
79
+ padding: 12px 0;
80
+ margin: 10px 0;
81
+ font-size: 16px;
82
+ border: none;
83
+ border-radius: 5px;
84
+ cursor: pointer;
85
+ width: 100%;
86
+ transition: background-color 0.3s ease, color 0.3s ease;
87
+ background-color: #FAFAFA;
88
+ color: #333;
89
+ }
90
+
91
+ .button:hover {
92
+ background-color: #f0f0f0;
93
+ }
94
+
95
+ .google-button {
96
+ background-color: #4285F4;
97
+ color: #fff;
98
+ border: none;
99
+ }
100
+
101
+ .google-button:hover {
102
+ background-color: #357ae8;
103
+ }
104
+ </style>
105
+ </head>
106
+ <body>
107
+ <div class="container">
108
+ <img src="/public/avatars/ai_tutor.png" alt="AI Tutor Avatar" class="avatar">
109
+ <h1>Terrier Tutor</h1>
110
+ <p>Hey! Welcome to the DS598 AI tutor</p>
111
+ <form action="/login/guest" method="post">
112
+ <button type="submit" class="button">Sign in as Guest</button>
113
+ </form>
114
+ <form action="/login/google" method="get">
115
+ <button type="submit" class="button google-button">Sign in with Google</button>
116
+ </form>
117
+ </div>
118
+ </body>
119
+ </html>
code/templates/logout.html ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Logout</title>
5
+ <script>
6
+ window.onload = function() {
7
+ fetch('/chainlit_tutor/logout', {
8
+ method: 'POST',
9
+ credentials: 'include' // Ensure cookies are sent
10
+ }).then(() => {
11
+ window.location.href = '/';
12
+ }).catch(error => {
13
+ console.error('Logout failed:', error);
14
+ });
15
+ };
16
+ </script>
17
+ </head>
18
+ <body>
19
+ <p>Logging out... If you are not redirected, <a href="/">click here</a>.</p>
20
+ </body>
21
+ </html>