louiecerv commited on
Commit
c078a25
·
1 Parent(s): 9759e20

full deploy to remote rep

Browse files
.dockerignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fly.toml
2
+ .git/
3
+ __pycache__/
4
+ .envrc
5
+ .venv/
Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11.9 AS builder
2
+
3
+ ENV PYTHONUNBUFFERED=1 \
4
+ PYTHONDONTWRITEBYTECODE=1
5
+ WORKDIR /app
6
+
7
+
8
+ RUN python -m venv .venv
9
+ COPY requirements.txt ./
10
+ RUN .venv/bin/pip install -r requirements.txt
11
+ FROM python:3.11.9-slim
12
+ WORKDIR /app
13
+ COPY --from=builder /app/.venv .venv/
14
+ COPY . .
15
+ CMD ["/app/.venv/bin/streamlit", "run", "pages\4_Settings.py"]
README.md CHANGED
@@ -1,14 +1,55 @@
1
  ---
2
- title: Exam Maker V1
3
  emoji: 😻
4
- colorFrom: yellow
5
- colorTo: gray
6
  sdk: streamlit
7
  sdk_version: 1.41.1
8
- app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
- short_description: AI-Enabled Examination Item Generator
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: WVSU Exam Maker
3
  emoji: 😻
4
+ colorFrom: purple
5
+ colorTo: purple
6
  sdk: streamlit
7
  sdk_version: 1.41.1
8
+ app_file: Exam_Maker.py
9
  pinned: false
10
  license: apache-2.0
11
+ short_description: App that uses user authentication
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
15
+
16
+ # WVSU Exam Maker
17
+ =====================
18
+
19
+ A cutting-edge tool designed to assist faculty members of West Visayas State University in creating comprehensive exams with ease.
20
+
21
+ ## Table of Contents
22
+ -----------------
23
+
24
+ * [Features](#features)
25
+ * [Getting Started](#getting-started)
26
+ * [Important Note](#important-note)
27
+ * [Development Team](#development-team)
28
+
29
+ ## Features
30
+ --------
31
+
32
+ * Generate high-quality exam questions in various formats, including:
33
+ + Multiple Choice
34
+ + True or False
35
+ + Short Response
36
+ + Essay
37
+ * Leverage the latest AI technology from Google Gemini 2 to create effective and engaging exams
38
+ * Streamline exam creation and simplify exam preparation
39
+
40
+ ## Getting Started
41
+ ---------------
42
+
43
+ 1. Define exam requirements using our intuitive interface
44
+ 2. Upload reference materials, such as lecture notes and tables of specifications
45
+ 3. Generate exam questions in various formats
46
+
47
+ ## Important Note
48
+ --------------
49
+
50
+ While the WVSU Exam Maker utilizes advanced AI technology, it is essential to review the output carefully, as AI can make mistakes. User supervision is necessary to ensure accuracy.
51
+
52
+ ## Development Team
53
+ ------------------
54
+
55
+ The WVSU Exam Maker was developed by the AI Research Team of the Management Information System Office.
app.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import sqlite3
3
+ from passlib.hash import bcrypt
4
+ import pandas as pd
5
+ import re
6
+ import warnings
7
+ warnings.filterwarnings("ignore", message="module 'bcrypt' has no attribute '__about__'")
8
+ if "is_starting" not in st.session_state:
9
+ st.session_state["is_starting"] = True
10
+
11
+ if "authenticated" not in st.session_state:
12
+ st.session_state["authenticated"] = False
13
+
14
+ #from pages.About import show_about
15
+ #from pages.Text_prompt import show_text_prompt
16
+ #from pages.Multimodal import show_multimodal
17
+ #from pages.Settings import show_settings
18
+
19
+ if "authenticated" not in st.session_state:
20
+ st.session_state["authenticated"] = False
21
+
22
+ def create_usertable():
23
+ conn = sqlite3.connect('users.db')
24
+ c = conn.cursor()
25
+ c.execute('CREATE TABLE IF NOT EXISTS userstable(username TEXT, password TEXT)')
26
+ c.execute('CREATE TABLE IF NOT EXISTS system_instructions(username TEXT PRIMARY KEY, instruction TEXT)')
27
+ c.execute('CREATE TABLE IF NOT EXISTS user_prompts(id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, prompt_time TEXT, prompt_type TEXT)')
28
+ conn.commit()
29
+ conn.close()
30
+
31
+ def add_userdata(username, password):
32
+ conn = sqlite3.connect('users.db')
33
+ c = conn.cursor()
34
+ c.execute('INSERT INTO userstable(username, password) VALUES (?,?)', (username, password))
35
+ conn.commit()
36
+ conn.close()
37
+
38
+ def login_user(username, password):
39
+ conn = sqlite3.connect('users.db')
40
+ c = conn.cursor()
41
+ c.execute('SELECT password FROM userstable WHERE username =?', (username,))
42
+ stored_hash = c.fetchone()
43
+ conn.close()
44
+
45
+ if stored_hash:
46
+ stored_hash = stored_hash[0]
47
+ return check_hashes(password, stored_hash)
48
+ else:
49
+ return False
50
+
51
+ def view_all_users():
52
+ conn = sqlite3.connect('users.db')
53
+ c = conn.cursor()
54
+ c.execute('SELECT * FROM userstable')
55
+ data = c.fetchall()
56
+ conn.close()
57
+ return data
58
+
59
+ # --- Hashing ---
60
+ def make_hashes(password):
61
+ return bcrypt.hash(password)
62
+
63
+ def check_hashes(password, hashed_text):
64
+ return bcrypt.verify(password, hashed_text)
65
+
66
+ # --- Authentication ---
67
+ def authenticate(username, password):
68
+ return login_user(username, password)
69
+
70
+ def logout():
71
+ del st.session_state["authenticated"]
72
+ del st.session_state["username"]
73
+ del st.session_state["page"]
74
+
75
+ # --- Initialize session state ---
76
+ if "authenticated" not in st.session_state:
77
+ st.session_state["authenticated"] = False
78
+ if "username" not in st.session_state:
79
+ st.session_state["username"] = None
80
+ if "page" not in st.session_state:
81
+ st.session_state["page"] = "login"
82
+
83
+ # --- Login page ---
84
+ def login_page():
85
+ st.title("WVSU Exam Maker")
86
+ st.subheader("User Login")
87
+ username = st.text_input("User Name")
88
+ password = st.text_input("Password", type='password')
89
+ if st.button("Login"):
90
+ result = authenticate(username.lower(), password)
91
+ if result:
92
+ st.session_state["authenticated"] = True
93
+ st.session_state["username"] = username
94
+ st.success("Logged In as {}".format(username))
95
+ st.session_state["page"] = "main"
96
+ st.session_state["is_starting"] = False
97
+ st.rerun()
98
+ else:
99
+ st.warning("Incorrect Username/Password")
100
+
101
+ st.write("Don't have an account? Click Signup.")
102
+ # --- Signup button ---
103
+ if st.button("Signup"):
104
+ st.session_state["page"] = "signup"
105
+ st.rerun()
106
+
107
+ # --- Signup page ---
108
+ def signup_page():
109
+ st.subheader("Create New Account")
110
+ new_user = st.text_input("Username")
111
+ new_password = st.text_input("Password", type='password')
112
+
113
+ # Display password requirements
114
+ st.write("Password Requirements:")
115
+ st.write("* Minimum length: 8 characters")
116
+ st.write("* Mix of uppercase and lowercase letters")
117
+ st.write("* At least one number")
118
+ st.write("* At least one special character")
119
+
120
+ # Validate password strength
121
+ col1, col2 = st.columns([1, 1])
122
+ if col1.button("Signup"):
123
+ password_strength = validate_password(new_password)
124
+ if password_strength:
125
+ # Check if username already exists
126
+ conn = sqlite3.connect('users.db')
127
+ c = conn.cursor()
128
+ c.execute('SELECT * FROM userstable WHERE username=?', (new_user,))
129
+ existing_user = c.fetchone()
130
+ conn.close()
131
+
132
+ if existing_user:
133
+ st.error("Username already exists. Please choose a different username.")
134
+ else:
135
+ hashed_new_password = make_hashes(new_password)
136
+ add_userdata(new_user, hashed_new_password)
137
+ st.success("You have successfully created a valid Account")
138
+ st.info("Go to Login Menu to login")
139
+ st.session_state["page"] = "login"
140
+ st.rerun()
141
+ else:
142
+ st.error("Password does not meet the requirements.")
143
+ if col2.button("Cancel"):
144
+ st.session_state["page"] = "login"
145
+ st.rerun()
146
+
147
+ # --- Validate password strength ---
148
+ def validate_password(password):
149
+ # Define password requirements
150
+ min_length = 8
151
+ has_uppercase = re.search(r"[A-Z]", password)
152
+ has_lowercase = re.search(r"[a-z]", password)
153
+ has_number = re.search(r"\d", password)
154
+ has_symbol = re.search(r"[!@#$%^&*()_+=-{};:'<>,./?]", password)
155
+
156
+ # Check if password meets all requirements
157
+ if (len(password) >= min_length and
158
+ has_uppercase and
159
+ has_lowercase and
160
+ has_number and
161
+ has_symbol):
162
+ return True
163
+ else:
164
+ return False
165
+
166
+ # --- Manage users page ---
167
+ def manage_users_page():
168
+ st.subheader("User Management")
169
+ user_result = view_all_users()
170
+ clean_db = pd.DataFrame(user_result, columns=["Username", "Password"])
171
+ st.dataframe(clean_db)
172
+
173
+ # --- Main app ---
174
+ def main():
175
+ create_usertable()
176
+
177
+ if st.session_state["page"] == "login":
178
+ login_page()
179
+ elif st.session_state["page"] == "signup":
180
+ signup_page()
181
+ else:
182
+
183
+ msg = """
184
+ # Welcome to the WVSU Exam Maker!
185
+
186
+ We are excited to introduce you to the WVSU Exam Maker, a cutting-edge tool designed to assist faculty members of West Visayas State University in creating comprehensive exams with ease.
187
+
188
+ ### Empowering Teachers, Enhancing Education
189
+
190
+ With the WVSU Exam Maker, you can generate high-quality exam questions in various formats, saving you time and effort. Our innovative app leverages the latest AI technology from Google Gemini 2 to help you create exams that are both effective and engaging.
191
+
192
+ ### Explore the Possibilities
193
+
194
+ • **Streamline exam creation**: Generate questions in multiple formats, including Multiple Choice, True or False, Short Response, and Essay.
195
+ • **Enhance exam accuracy**: Review and refine AI-generated questions to ensure accuracy and relevance.
196
+ • **Simplify exam preparation**: Use our intuitive interface to define exam requirements and upload reference materials.
197
+
198
+ ### Get Started Today!
199
+
200
+ We invite you to explore the WVSU Exam Maker and discover how it can support your teaching and assessment needs.
201
+
202
+ Thank you for using the WVSU Exam Maker!
203
+ """
204
+ st.markdown(msg)
205
+
206
+ # Display username and logout button on every page
207
+ st.sidebar.write(f"Welcome, {st.session_state['username']}")
208
+ if st.sidebar.button("Logout"):
209
+ logout()
210
+ st.rerun()
211
+
212
+
213
+ if __name__ == "__main__":
214
+ main()
fly.toml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # fly.toml app configuration file generated for wvsu-exam-maker-final-v2 on 2025-01-15T20:40:44+08:00
2
+ #
3
+ # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
4
+ #
5
+
6
+ app = 'wvsu-exam-maker-final-v2'
7
+ primary_region = 'sin'
8
+
9
+ [build]
10
+
11
+ [http_service]
12
+ internal_port = 8501
13
+ force_https = true
14
+ auto_stop_machines = 'stop'
15
+ auto_start_machines = true
16
+ min_machines_running = 0
17
+ processes = ['app']
18
+
19
+ [[vm]]
20
+ memory = '1gb'
21
+ cpu_kind = 'shared'
22
+ cpus = 1
monitor.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import sqlite3
3
+ import pandas as pd
4
+
5
+ # Connect to the database
6
+ conn = sqlite3.connect('users.db')
7
+ c = conn.cursor()
8
+
9
+ # Create a function to retrieve user prompts from the database
10
+ def get_user_prompts(username=None):
11
+ if username:
12
+ c.execute('SELECT * FROM user_prompts WHERE username=?', (username,))
13
+ else:
14
+ c.execute('SELECT * FROM user_prompts')
15
+ user_prompts = c.fetchall()
16
+ return user_prompts
17
+
18
+ # Create a function to convert user prompts to a Pandas DataFrame
19
+ def user_prompts_to_df(user_prompts):
20
+ df = pd.DataFrame(user_prompts, columns=['id', 'username', 'prompt_time', 'prompt_type'])
21
+ return df
22
+
23
+ # Create a Streamlit page
24
+ st.title("User Prompts Summary")
25
+
26
+ # Retrieve all usernames from the database
27
+ c.execute('SELECT username FROM userstable')
28
+ usernames = [row[0] for row in c.fetchall()]
29
+
30
+ # Add a selectbox to filter user prompts by username
31
+ username_filter = st.selectbox("Filter by username", ["All"] + usernames)
32
+
33
+ # Retrieve user prompts based on the selected username
34
+ if username_filter == "All":
35
+ user_prompts = get_user_prompts()
36
+ else:
37
+ user_prompts = get_user_prompts(username_filter)
38
+
39
+ # Convert user prompts to a Pandas DataFrame
40
+ df = user_prompts_to_df(user_prompts)
41
+
42
+ # Display the DataFrame
43
+ st.write(df)
pages/1_About.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ def show_about():
4
+ about = """
5
+ ### WVSU Exam Maker: A Faculty Guide
6
+
7
+ The WVSU Exam Maker is a cutting-edge tool designed to assist faculty members of West Visayas State University in creating comprehensive exams with ease. Leveraging the latest AI technology from Google Gemini 2, this innovative app helps teachers generate questions in various formats, including:
8
+
9
+
10
+ * **Multiple Choice**: Assess students' knowledge with objective, structured questions.
11
+ * **True or False**: Evaluate students' understanding with concise, binary questions.
12
+ * **Short Response**: Encourage students to provide brief, written answers.
13
+ * **Essay**: Foster critical thinking and in-depth writing with longer, more open-ended questions.
14
+
15
+
16
+ ## Key Features
17
+
18
+ ### Text Prompt Page
19
+ Define exam requirements with precision using various input options.
20
+
21
+
22
+ ### Multimodal Prompt Page
23
+ Upload reference documents (PDF or image) to generate questions, including:
24
+
25
+
26
+ * Lecture materials
27
+ * Tables of specifications
28
+ * Rubrics
29
+ * Other relevant inputs
30
+
31
+
32
+ ## Important Note
33
+ While the WVSU Exam Maker utilizes advanced AI technology, it is essential to review the output carefully, as AI can make mistakes. User supervision is necessary to ensure accuracy.
34
+
35
+ ## Development Team
36
+ The WVSU Exam Maker was developed by the AI Research Team of the Management Information System Office.
37
+ """
38
+ # Add your About page content here
39
+ st.markdown(about)
40
+
41
+ if st.session_state["authenticated"]:
42
+ show_about()
43
+ else:
44
+ if not st.session_state["is_starting"]:
45
+ st.write("You are not authenticated. Please log in to access this page.")
pages/2_Text_prompt.py ADDED
@@ -0,0 +1,640 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import sqlite3
3
+ import time
4
+ import datetime
5
+ from PIL import Image
6
+ import google.generativeai as genai
7
+ import os
8
+ from reportlab.pdfgen import canvas
9
+ from reportlab.lib.pagesizes import A4, letter
10
+ from io import BytesIO
11
+ import tempfile
12
+ import json
13
+ import re
14
+ from reportlab.platypus import Paragraph, Frame, Spacer
15
+ from reportlab.lib.styles import getSampleStyleSheet
16
+ import shutil
17
+
18
+ MODEL_ID = "gemini-2.0-flash-exp"
19
+ api_key = os.getenv("GEMINI_API_KEY")
20
+ model_id = MODEL_ID
21
+ genai.configure(api_key=api_key)
22
+ enable_stream = False
23
+
24
+ if "model" not in st.session_state:
25
+ st.session_state.model = genai.GenerativeModel(MODEL_ID)
26
+
27
+ if "chat" not in st.session_state:
28
+ st.session_state.chat = st.session_state.model.start_chat()
29
+
30
+ def get_system_instruction(username):
31
+ """ Retrieves the system instruction for the user from the database. """
32
+ conn = sqlite3.connect('users.db')
33
+ c = conn.cursor()
34
+ c.execute('SELECT instruction FROM system_instructions WHERE username=?', (username,))
35
+ instruction = c.fetchone()
36
+ conn.close()
37
+ if instruction:
38
+ return instruction[0]
39
+ else:
40
+ return "Default system instruction."
41
+
42
+ def save_user_prompt(username, prompt_time, prompt_type):
43
+ """ Saves the user prompt to the database for monitoring purposes. """
44
+
45
+ conn = sqlite3.connect('users.db')
46
+ c = conn.cursor()
47
+ c.execute('INSERT INTO user_prompts(username, prompt_time, prompt_type) VALUES (?,?,?)', (username, prompt_time, prompt_type))
48
+ conn.commit()
49
+ conn.close()
50
+
51
+ def merge_json_strings(json_str1, json_str2):
52
+ """
53
+ Merges two JSON strings into one, handling potential markdown tags.
54
+
55
+ Args:
56
+ json_str1: The first JSON string, potentially with markdown tags.
57
+ json_str2: The second JSON string, potentially with markdown tags.
58
+
59
+ Returns:
60
+ A cleaned JSON string representing the merged JSON objects.
61
+ """
62
+
63
+ # Clean the JSON strings by removing markdown tags
64
+ cleaned_json_str1 = _clean_markdown(json_str1)
65
+ cleaned_json_str2 = _clean_markdown(json_str2)
66
+
67
+ try:
68
+ # Parse the cleaned JSON strings into Python dictionaries
69
+ data1 = json.loads(cleaned_json_str1)
70
+ data2 = json.loads(cleaned_json_str2)
71
+
72
+ # Merge the dictionaries
73
+ merged_data = _merge_dicts(data1, data2)
74
+
75
+ # Convert the merged dictionary back into a JSON string
76
+ return json.dumps(merged_data, indent=2)
77
+ except json.JSONDecodeError as e:
78
+ return f"Error decoding JSON: {e}"
79
+
80
+
81
+ def _clean_markdown(text):
82
+ """
83
+ Removes markdown tags from a string if they exist.
84
+ Otherwise, returns the original string unchanged.
85
+
86
+ Args:
87
+ text: The input string.
88
+
89
+ Returns:
90
+ The string with markdown tags removed, or the original string
91
+ if no markdown tags were found.
92
+ """
93
+ try:
94
+ # Check if the string contains markdown
95
+ if re.match(r"^```json\s*", text) and re.search(r"\s*```$", text):
96
+ # Remove leading ```json
97
+ text = re.sub(r"^```json\s*", "", text)
98
+ # Remove trailing ```
99
+ text = re.sub(r"\s*```$", "", text)
100
+ return text
101
+ except Exception as e:
102
+ # Log the error
103
+ st.error(f"Error cleaning markdown: {e}")
104
+ return None
105
+
106
+ def _merge_dicts(data1, data2):
107
+ """
108
+ Recursively merges two data structures.
109
+
110
+ Handles merging of dictionaries and lists.
111
+ For dictionaries, if a key exists in both and both values are dictionaries
112
+ or lists, they are merged recursively. Otherwise, the value from data2 is used.
113
+ For lists, the lists are concatenated.
114
+
115
+ Args:
116
+ data1: The first data structure (dictionary or list).
117
+ data2: The second data structure (dictionary or list).
118
+
119
+ Returns:
120
+ The merged data structure.
121
+
122
+ Raises:
123
+ ValueError: If the data types are not supported for merging.
124
+ """
125
+ if isinstance(data1, dict) and isinstance(data2, dict):
126
+ for key, value in data2.items():
127
+ if key in data1 and isinstance(data1[key], (dict, list)) and isinstance(value, type(data1[key])):
128
+ _merge_dicts(data1[key], value)
129
+ else:
130
+ data1[key] = value
131
+ return data1
132
+ elif isinstance(data1, list) and isinstance(data2, list):
133
+ return data1 + data2
134
+ else:
135
+ raise ValueError("Unsupported data types for merging")
136
+
137
+ def create_json(metadata, content):
138
+ """
139
+ Creates a JSON string combining metadata and content.
140
+
141
+ Args:
142
+ metadata: A dictionary containing metadata information.
143
+ content: A dictionary containing the quiz content.
144
+
145
+ Returns:
146
+ A string representing the combined JSON data.
147
+ """
148
+
149
+ # Create metadata with timestamp
150
+ metadata = {
151
+ "subject": metadata.get("subject", ""),
152
+ "topic": metadata.get("topic", ""),
153
+ "num_questions": metadata.get("num_questions", 0),
154
+ "exam_type": metadata.get("exam_type", ""),
155
+ "timestamp": datetime.datetime.now().isoformat()
156
+ }
157
+
158
+ # Combine metadata and content
159
+ combined_data = {"metadata": metadata, "content": content}
160
+
161
+ # Convert to JSON string
162
+ json_string = json.dumps(combined_data, indent=4)
163
+
164
+ return json_string
165
+
166
+ def create_pdf(data):
167
+ """Creates a PDF file with text wrapping for quiz content."""
168
+ try:
169
+ # Load the JSON data
170
+ data = json.loads(data)
171
+
172
+ if 'metadata' not in data or 'content' not in data:
173
+ st.error("Error: Invalid data format. Missing 'metadata' or 'content' keys.")
174
+ return None
175
+
176
+ metadata = data['metadata']
177
+ content = data['content']
178
+
179
+ # Validate metadata
180
+ required_metadata_keys = ['subject', 'topic', 'exam_type', 'num_questions']
181
+ if not all(key in metadata for key in required_metadata_keys):
182
+ st.error("Error: Invalid metadata format. Missing required keys.")
183
+ return None
184
+
185
+ # Create a unique filename with timestamp
186
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
187
+ pdf_filename = f"quiz_output_{timestamp}.pdf"
188
+
189
+ # Get the temporary directory
190
+ temp_dir = tempfile.gettempdir()
191
+ pdf_path = os.path.join(temp_dir, pdf_filename)
192
+
193
+ c = canvas.Canvas(pdf_path, pagesize=A4)
194
+ c.setFont("Helvetica", 10)
195
+
196
+ exam_type = metadata['exam_type']
197
+
198
+ styles = getSampleStyleSheet()
199
+ style_normal = styles["Normal"]
200
+
201
+ y_position = 750
202
+ line_height = 15
203
+ frame_width = 500
204
+ first_page = True
205
+
206
+ for idx, q in enumerate(content):
207
+ if not isinstance(q, dict):
208
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
209
+ continue
210
+
211
+ if first_page:
212
+ # Print metadata once
213
+ for key, label in [("subject", "Subject"), ("topic", "Topic"),
214
+ ("exam_type", "Type"), ("num_questions", "Number of Questions")]:
215
+ c.drawString(50, y_position, f"{label}: {metadata[key]}")
216
+ y_position -= line_height
217
+
218
+ y_position -= line_height # Extra space before questions
219
+ first_page = False
220
+
221
+ # Print question number
222
+ question_text = f"{idx+1}. "
223
+ c.drawString(50, y_position, question_text)
224
+ x_position = 70 # Adjust starting position for question text
225
+
226
+ # --- Changes for better text flow ---
227
+ # Split the question into words
228
+ words = q.get('question', q.get('statement', '')).split()
229
+ current_line = ""
230
+ for word in words:
231
+ temp_line = current_line + " " + word
232
+ text_width = c.stringWidth(temp_line, "Helvetica", 10)
233
+ if text_width <= frame_width:
234
+ current_line = temp_line
235
+ else:
236
+ c.drawString(x_position, y_position, current_line)
237
+ y_position -= line_height
238
+ current_line = word
239
+ if y_position < 50:
240
+ c.showPage()
241
+ c.setFont("Helvetica", 10)
242
+ y_position = 750
243
+ # Print the last line of the question
244
+ c.drawString(x_position, y_position, current_line)
245
+ y_position -= line_height
246
+ # --- End of changes ---
247
+
248
+ if exam_type == "Multiple Choice":
249
+ # Validate question structure
250
+ required_question_keys = ['question', 'options', 'correct_answer']
251
+ if not all(key in q for key in required_question_keys):
252
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
253
+ continue
254
+
255
+ # Print options
256
+ for option_idx, option in enumerate(q['options'], ord('a')):
257
+ c.drawString(70, y_position, f"{chr(option_idx)}) {option}")
258
+ y_position -= line_height
259
+ if y_position < 50:
260
+ c.showPage()
261
+ c.setFont("Helvetica", 10)
262
+ y_position = 750
263
+
264
+ # Print correct answer
265
+ c.drawString(70, y_position, f"Correct Answer: {q['correct_answer']}")
266
+ y_position -= line_height * 2
267
+
268
+ elif exam_type == "True or False":
269
+ # Validate question structure
270
+ required_question_keys = ['statement', 'options', 'correct_answer']
271
+ if not all(key in q for key in required_question_keys):
272
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
273
+ continue
274
+
275
+ # Print options
276
+ for option in q['options']:
277
+ c.drawString(70, y_position, f"{option}")
278
+ y_position -= line_height
279
+ if y_position < 50:
280
+ c.showPage()
281
+ c.setFont("Helvetica", 10)
282
+ y_position = 750
283
+
284
+ # Print correct answer
285
+ c.drawString(70, y_position, f"Correct Answer: {q['correct_answer']}")
286
+ y_position -= line_height * 2
287
+
288
+ elif exam_type in ["Short Response", "Essay Type"]:
289
+ # Validate question structure
290
+ required_question_keys = ['question', 'correct_answer']
291
+ if not all(key in q for key in required_question_keys):
292
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
293
+ continue
294
+
295
+ # Print correct answer
296
+ answer_text = f"Correct Answer: {q['correct_answer']}"
297
+
298
+ # --- Changes for better text flow ---
299
+ # Split the answer into words
300
+ words = answer_text.split()
301
+ current_line = ""
302
+ for word in words:
303
+ temp_line = current_line + " " + word
304
+ text_width = c.stringWidth(temp_line, "Helvetica", 10)
305
+ if text_width <= frame_width:
306
+ current_line = temp_line
307
+ else:
308
+ c.drawString(x_position, y_position, current_line)
309
+ y_position -= line_height
310
+ current_line = word
311
+ if y_position < 50:
312
+ c.showPage()
313
+ c.setFont("Helvetica", 10)
314
+ y_position = 750
315
+ # Print the last line of the answer
316
+ c.drawString(x_position, y_position, current_line)
317
+ y_position -= line_height * 2
318
+ # --- End of changes ---
319
+
320
+ if y_position < 50:
321
+ c.showPage()
322
+ c.setFont("Helvetica", 10)
323
+ y_position = 750
324
+
325
+ # Add the notice at the end
326
+ notice = "This exam was generated by the WVSU Exam Maker (c) 2025 West Visayas State University"
327
+ c.drawString(50, y_position, notice)
328
+
329
+ c.save()
330
+ return pdf_path
331
+
332
+ except Exception as e:
333
+ st.error(f"Error creating PDF: {e}")
334
+ return None
335
+
336
+ def generate_quiz_content(data):
337
+ """
338
+ Separates the metadata and content from a JSON string containing exam data.
339
+ Creates a markdown formatted text that contains the exam metadata and
340
+ enumerates the questions, options and answers nicely formatted for readability.
341
+
342
+ Args:
343
+ data: A JSON string containing the exam data.
344
+
345
+ Returns:
346
+ A markdown formatted string.
347
+ """
348
+ data = json.loads(data)
349
+ metadata = data["metadata"]
350
+ content = data["content"]
351
+ exam_type = metadata["exam_type"]
352
+ if exam_type == "Multiple Choice":
353
+ md_text = f"""# {metadata['subject']} - {metadata['topic']}
354
+
355
+ **Exam Type:** {metadata['exam_type']}
356
+ **Number of Questions:** {metadata['num_questions']}
357
+ **Timestamp:** {metadata['timestamp']}
358
+
359
+ ---
360
+
361
+ """
362
+ for i, q in enumerate(content):
363
+ md_text += f"""Question {i+1}:
364
+ {q['question']}
365
+
366
+ """
367
+ for j, option in enumerate(q['options'], ord('a')):
368
+ md_text += f"""{chr(j)}. {option}
369
+
370
+ """
371
+ md_text += f"""**Correct Answer:** {q['correct_answer']}
372
+
373
+ ---
374
+
375
+ """
376
+ md_text += """This exam was generated by the WVSU Exam Maker
377
+ (c) 2025 West Visayas State University
378
+ """
379
+
380
+ elif exam_type == "True or False":
381
+ md_text = f"""# {metadata['subject']} - {metadata['topic']}
382
+
383
+ **Exam Type:** {metadata['exam_type']}
384
+ **Number of Questions:** {metadata['num_questions']}
385
+ **Timestamp:** {metadata['timestamp']}
386
+
387
+ ---
388
+
389
+ """
390
+
391
+ for i, q in enumerate(content):
392
+ md_text += f"""Statement {i+1}:
393
+
394
+ {q['statement']}
395
+
396
+ """
397
+ for j, option in enumerate(q['options'], ord('a')):
398
+ md_text += f"""{option}
399
+ """
400
+
401
+ md_text += f"""**Correct Answer:** {q['correct_answer']}
402
+
403
+ ---
404
+ """
405
+ md_text += """This exam was generated by the WVSU Exam Maker
406
+ (c) 2025 West Visayas State University"""
407
+
408
+ elif exam_type == "Short Response" or exam_type == "Essay Type":
409
+ md_text = f"""# {metadata['subject']} - {metadata['topic']}
410
+
411
+ **Exam Type:** {metadata['exam_type']}
412
+ **Number of Questions:** {metadata['num_questions']}
413
+ **Timestamp:** {metadata['timestamp']}
414
+
415
+ ---
416
+
417
+ """
418
+
419
+ for i, q in enumerate(content):
420
+ md_text += f"""Question {i+1}:
421
+
422
+ {q['question']}
423
+
424
+ """
425
+ md_text += f"""**Correct Answer:** {q['correct_answer']}
426
+
427
+ ---
428
+ """
429
+ md_text += """This exam was generated by the WVSU Exam Maker
430
+ (c) 2025 West Visayas State University"""
431
+
432
+ return md_text
433
+
434
+ def generate_metadata(subject, topic, num_questions, exam_type):
435
+ """Generates quiz metadata as a dictionary combining num_questions,
436
+ exam_type, and timestamp.
437
+
438
+ Args:
439
+ num_questions: The number of questions in the exam (int).
440
+ exam_type: The type of exam (str).
441
+
442
+ Returns:
443
+ A dictionary containing the quiz metadata.
444
+ """
445
+
446
+ # Format the timestamp
447
+ timestamp = datetime.datetime.now()
448
+ formatted_timestamp = timestamp.strftime("%Y-%m-%d %H:%M:%S")
449
+
450
+ metadata = {
451
+ "subject": subject,
452
+ "topic": topic,
453
+ "num_questions": num_questions,
454
+ "exam_type": exam_type,
455
+ "timestamp": formatted_timestamp
456
+ }
457
+
458
+ return metadata
459
+
460
+ def generate_text(prompt):
461
+ """Generates text based on the prompt."""
462
+ try:
463
+
464
+ # Send a text prompt to Gemini API
465
+ chat = st.session_state.chat
466
+ response = chat.send_message(
467
+ [
468
+ prompt
469
+ ],
470
+ stream=enable_stream
471
+ )
472
+
473
+ return response.text
474
+
475
+ except Exception as e:
476
+ st.error(f"An error occurred while generating text: {e}")
477
+ return None
478
+
479
+ def show_text_prompt():
480
+ st.subheader("Text Prompt")
481
+
482
+ username = st.session_state["username"]
483
+ st.write(f"Welcome, {username}! This page allows you to generate questions based on user inputs.")
484
+
485
+ # Display username and logout button on every page
486
+ st.sidebar.write(f"Current user: {st.session_state['username']}")
487
+
488
+ # User inputs
489
+ # Course selection
490
+ course = st.selectbox("Select Course",
491
+ ["Diploma in Teaching",
492
+ "Post Baccalaureate Diploma in Early Childhood Education",
493
+ "Master of Arts in Education - Language Teaching (English)",
494
+ "Master in Education major in Early Childhood Education"])
495
+
496
+
497
+ # Year level selection
498
+ year_level = st.selectbox("Select Year Level",
499
+ ["1st Year",
500
+ "2nd Year",
501
+ "3rd Year",
502
+ "4th Year"])
503
+
504
+ # Subject selection
505
+ subject = st.text_input("Enter Subject",
506
+ "e.g.,The Teaching Profession, Facilitating Learner-Centered Teaching")
507
+
508
+ # Topic selection
509
+ topic = st.text_input("Enter Topic",
510
+ "e.g., Teacher as a professional, Introduction to Learner-Centered Teaching")
511
+
512
+ # Question type selection
513
+ question_type = st.selectbox("Select Question Type",
514
+ ["Multiple Choice",
515
+ "True or False",
516
+ "Short Response",
517
+ "Essay Type"])
518
+
519
+ difficulty = st.selectbox("Select Difficulty",["easy","average","hard"])
520
+
521
+ #number of questions to generate
522
+ if question_type != "Essay Type":
523
+ num_questions = st.selectbox("Number of Questions to Generate",
524
+ [10, 20, 30, 40, 50])
525
+ else:
526
+ num_questions = st.selectbox("Number of Questions to Generate",
527
+ [1, 2, 3, 4, 5])
528
+
529
+ # Combine user inputs into a prompt
530
+ prompt = f"""Refer to the uploaded document. Generate a {question_type} question for a {year_level} {course} student
531
+ in {subject} on the topic of {topic} with a {difficulty} difficulty level.
532
+ The questions should require higher order thinking skills.
533
+ """
534
+
535
+ if question_type == "Multiple Choice":
536
+ prompt += """Provide 4 choices. Provide the correct answer in the format 'Answer: A'.
537
+ Use the following JSON format for each question:
538
+ [{
539
+ "question": "Your question here?",
540
+ "options": ["Option A", "Option B", "Option C", "Option D"],
541
+ "correct_answer": "full text of the correct answer"
542
+ }, ... more questions]
543
+ Ensure that the response only contains the JSON array of questions and nothing else.
544
+ """
545
+ elif question_type == "True or False":
546
+ prompt += """Indicate whether the statement is true or false. Keep the statement brief and concise.
547
+ Use the following JSON format for each question:
548
+ [{
549
+ "statement": "Your statement here",
550
+ "options": ["True", "False"],
551
+ "correct_answer": True"
552
+ }, ... more questions]
553
+ Ensure that the response only contains the JSON array of questions and nothing else.
554
+ """
555
+ elif question_type == "Short Response":
556
+ prompt += """Create question that require a word or short phrase as answer. Use the following JSON format for each question:
557
+ [{
558
+ "question": "Your question here?",
559
+ "correct_answer": A word or phrase"
560
+ }, ... more questions]
561
+ Ensure that the response only contains the JSON array of questions and nothing else.
562
+ """
563
+ elif question_type == "Essay Type":
564
+ prompt += """Create questions that require a short essay between 300 to 500 words.
565
+ Provide a detailed answer. Use the following JSON format for each question:
566
+ [{
567
+ "question": "Your question here?",
568
+ "correct_answer": The essay answer goes here."
569
+ }, ... more questions]
570
+ Ensure that the response only contains the JSON array of questions and nothing else.
571
+ """
572
+
573
+ if not question_type == "Essay Type":
574
+ prompt += f"Generate 10 questions. Do not repeat questions you have already given in previous prompts. Exclude markdown tags in the response."
575
+ else:
576
+ prompt += f" Generate {num_questions} questions. Do not repeat questions you have already given in previous prompts. Exclude markdown tags in the response"
577
+
578
+ full_quiz = ""
579
+
580
+ # Send button
581
+ if st.button("Generate Questions"):
582
+
583
+ if question_type == "Essay Type":
584
+ #prompt once
585
+ with st.spinner('Generating questions...'):
586
+ full_quiz = _clean_markdown(generate_text(prompt))
587
+
588
+ else:
589
+ if num_questions == 10:
590
+
591
+ #prompt once
592
+ with st.spinner('Generating questions...'):
593
+ full_quiz = _clean_markdown(generate_text(prompt))
594
+ else:
595
+ #prompt multiple times
596
+ times = num_questions//10
597
+ for i in range(times):
598
+ with st.spinner('Generating questions...'):
599
+ response = generate_text(prompt)
600
+
601
+ if i==0:
602
+ full_quiz = _clean_markdown(response)
603
+ else:
604
+ full_quiz = merge_json_strings(full_quiz, response)
605
+
606
+ metadata = generate_metadata(subject, topic, num_questions, question_type)
607
+
608
+ try:
609
+ # Attempt to load the string as JSON to validate it
610
+ content = json.loads(full_quiz)
611
+ except json.JSONDecodeError:
612
+ st.error("Error: Invalid JSON string for quiz content.")
613
+ st.stop()
614
+
615
+ json_string = create_json(metadata, content)
616
+
617
+ quiz_markdown = generate_quiz_content(json_string)
618
+ st.markdown(quiz_markdown)
619
+
620
+ pdf_path = create_pdf(json_string)
621
+
622
+ if pdf_path:
623
+ """Click the button to download the generated PDF."""
624
+ try:
625
+ with open(pdf_path, "rb") as f:
626
+ st.download_button("Download PDF", f, file_name=os.path.basename(pdf_path))
627
+ except Exception as e:
628
+ st.error(f"Error handling file download: {e}")
629
+ else:
630
+ st.error("Failed to generate the PDF. Please try again.")
631
+
632
+ #record the prompt for monitoring
633
+ save_user_prompt(username, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Multimodal")
634
+
635
+ if st.session_state["authenticated"]:
636
+ show_text_prompt()
637
+
638
+ else:
639
+ if not st.session_state["is_starting"]:
640
+ st.write("You are not authenticated. Please log in to access this page.")
pages/3_Multimodal.py ADDED
@@ -0,0 +1,685 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import sqlite3
3
+ import time
4
+ import datetime
5
+ from PIL import Image
6
+ import google.generativeai as genai
7
+ import os
8
+ from reportlab.pdfgen import canvas
9
+ from reportlab.lib.pagesizes import A4, letter
10
+ from io import BytesIO
11
+ import tempfile
12
+ import json
13
+ import re
14
+ from reportlab.platypus import Paragraph, Frame, Spacer
15
+ from reportlab.lib.styles import getSampleStyleSheet
16
+ import shutil
17
+
18
+ MODEL_ID = "gemini-2.0-flash-exp"
19
+ api_key = os.getenv("GEMINI_API_KEY")
20
+ model_id = MODEL_ID
21
+ genai.configure(api_key=api_key)
22
+ enable_stream = False
23
+
24
+ if "model" not in st.session_state:
25
+ st.session_state.model = genai.GenerativeModel(MODEL_ID)
26
+
27
+ if "chat" not in st.session_state:
28
+ st.session_state.chat = st.session_state.model.start_chat()
29
+
30
+ if "is_new_file" not in st.session_state:
31
+ st.session_state.is_new_file = True
32
+
33
+ def get_system_instruction(username):
34
+ """ Retrieves the system instruction for the user from the database. """
35
+ conn = sqlite3.connect('users.db')
36
+ c = conn.cursor()
37
+ c.execute('SELECT instruction FROM system_instructions WHERE username=?', (username,))
38
+ instruction = c.fetchone()
39
+ conn.close()
40
+ if instruction:
41
+ return instruction[0]
42
+ else:
43
+ return "Default system instruction."
44
+
45
+ def save_user_prompt(username, prompt_time, prompt_type):
46
+ """ Saves the user prompt to the database for monitoring purposes. """
47
+
48
+ conn = sqlite3.connect('users.db')
49
+ c = conn.cursor()
50
+ c.execute('INSERT INTO user_prompts(username, prompt_time, prompt_type) VALUES (?,?,?)', (username, prompt_time, prompt_type))
51
+ conn.commit()
52
+ conn.close()
53
+
54
+ def merge_json_strings(json_str1, json_str2):
55
+ """
56
+ Merges two JSON strings into one, handling potential markdown tags.
57
+
58
+ Args:
59
+ json_str1: The first JSON string, potentially with markdown tags.
60
+ json_str2: The second JSON string, potentially with markdown tags.
61
+
62
+ Returns:
63
+ A cleaned JSON string representing the merged JSON objects.
64
+ """
65
+
66
+ # Clean the JSON strings by removing markdown tags
67
+ cleaned_json_str1 = _clean_markdown(json_str1)
68
+ cleaned_json_str2 = _clean_markdown(json_str2)
69
+
70
+ try:
71
+ # Parse the cleaned JSON strings into Python dictionaries
72
+ data1 = json.loads(cleaned_json_str1)
73
+ data2 = json.loads(cleaned_json_str2)
74
+
75
+ # Merge the dictionaries
76
+ merged_data = _merge_dicts(data1, data2)
77
+
78
+ # Convert the merged dictionary back into a JSON string
79
+ return json.dumps(merged_data, indent=2)
80
+ except json.JSONDecodeError as e:
81
+ return f"Error decoding JSON: {e}"
82
+
83
+
84
+ def _clean_markdown(text):
85
+ """
86
+ Removes markdown tags from a string if they exist.
87
+ Otherwise, returns the original string unchanged.
88
+
89
+ Args:
90
+ text: The input string.
91
+
92
+ Returns:
93
+ The string with markdown tags removed, or the original string
94
+ if no markdown tags were found.
95
+ """
96
+ try:
97
+ # Check if the string contains markdown
98
+ if re.match(r"^```json\s*", text) and re.search(r"\s*```$", text):
99
+ # Remove leading ```json
100
+ text = re.sub(r"^```json\s*", "", text)
101
+ # Remove trailing ```
102
+ text = re.sub(r"\s*```$", "", text)
103
+ return text
104
+ except Exception as e:
105
+ # Log the error
106
+ st.error(f"Error cleaning markdown: {e}")
107
+ return None
108
+
109
+ def _merge_dicts(data1, data2):
110
+ """
111
+ Recursively merges two data structures.
112
+
113
+ Handles merging of dictionaries and lists.
114
+ For dictionaries, if a key exists in both and both values are dictionaries
115
+ or lists, they are merged recursively. Otherwise, the value from data2 is used.
116
+ For lists, the lists are concatenated.
117
+
118
+ Args:
119
+ data1: The first data structure (dictionary or list).
120
+ data2: The second data structure (dictionary or list).
121
+
122
+ Returns:
123
+ The merged data structure.
124
+
125
+ Raises:
126
+ ValueError: If the data types are not supported for merging.
127
+ """
128
+ if isinstance(data1, dict) and isinstance(data2, dict):
129
+ for key, value in data2.items():
130
+ if key in data1 and isinstance(data1[key], (dict, list)) and isinstance(value, type(data1[key])):
131
+ _merge_dicts(data1[key], value)
132
+ else:
133
+ data1[key] = value
134
+ return data1
135
+ elif isinstance(data1, list) and isinstance(data2, list):
136
+ return data1 + data2
137
+ else:
138
+ raise ValueError("Unsupported data types for merging")
139
+
140
+ def create_json(metadata, content):
141
+ """
142
+ Creates a JSON string combining metadata and content.
143
+
144
+ Args:
145
+ metadata: A dictionary containing metadata information.
146
+ content: A dictionary containing the quiz content.
147
+
148
+ Returns:
149
+ A string representing the combined JSON data.
150
+ """
151
+
152
+ # Create metadata with timestamp
153
+ metadata = {
154
+ "subject": metadata.get("subject", ""),
155
+ "topic": metadata.get("topic", ""),
156
+ "num_questions": metadata.get("num_questions", 0),
157
+ "exam_type": metadata.get("exam_type", ""),
158
+ "timestamp": datetime.datetime.now().isoformat()
159
+ }
160
+
161
+ # Combine metadata and content
162
+ combined_data = {"metadata": metadata, "content": content}
163
+
164
+ # Convert to JSON string
165
+ json_string = json.dumps(combined_data, indent=4)
166
+
167
+ return json_string
168
+
169
+ def create_pdf(data):
170
+ """Creates a PDF file with text wrapping for quiz content."""
171
+ try:
172
+ # Load the JSON data
173
+ data = json.loads(data)
174
+
175
+ if 'metadata' not in data or 'content' not in data:
176
+ st.error("Error: Invalid data format. Missing 'metadata' or 'content' keys.")
177
+ return None
178
+
179
+ metadata = data['metadata']
180
+ content = data['content']
181
+
182
+ # Validate metadata
183
+ required_metadata_keys = ['subject', 'topic', 'exam_type', 'num_questions']
184
+ if not all(key in metadata for key in required_metadata_keys):
185
+ st.error("Error: Invalid metadata format. Missing required keys.")
186
+ return None
187
+
188
+ # Create a unique filename with timestamp
189
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
190
+ pdf_filename = f"quiz_output_{timestamp}.pdf"
191
+
192
+ # Get the temporary directory
193
+ temp_dir = tempfile.gettempdir()
194
+ pdf_path = os.path.join(temp_dir, pdf_filename)
195
+
196
+ c = canvas.Canvas(pdf_path, pagesize=A4)
197
+ c.setFont("Helvetica", 10)
198
+
199
+ exam_type = metadata['exam_type']
200
+
201
+ styles = getSampleStyleSheet()
202
+ style_normal = styles["Normal"]
203
+
204
+ y_position = 750
205
+ line_height = 15
206
+ frame_width = 500
207
+ first_page = True
208
+
209
+ for idx, q in enumerate(content):
210
+ if not isinstance(q, dict):
211
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
212
+ continue
213
+
214
+ if first_page:
215
+ # Print metadata once
216
+ for key, label in [("subject", "Subject"), ("topic", "Topic"),
217
+ ("exam_type", "Type"), ("num_questions", "Number of Questions")]:
218
+ c.drawString(50, y_position, f"{label}: {metadata[key]}")
219
+ y_position -= line_height
220
+
221
+ y_position -= line_height # Extra space before questions
222
+ first_page = False
223
+
224
+ # Print question number
225
+ question_text = f"{idx+1}. "
226
+ c.drawString(50, y_position, question_text)
227
+ x_position = 70 # Adjust starting position for question text
228
+
229
+ # --- Changes for better text flow ---
230
+ # Split the question into words
231
+ words = q.get('question', q.get('statement', '')).split()
232
+ current_line = ""
233
+ for word in words:
234
+ temp_line = current_line + " " + word
235
+ text_width = c.stringWidth(temp_line, "Helvetica", 10)
236
+ if text_width <= frame_width:
237
+ current_line = temp_line
238
+ else:
239
+ c.drawString(x_position, y_position, current_line)
240
+ y_position -= line_height
241
+ current_line = word
242
+ if y_position < 50:
243
+ c.showPage()
244
+ c.setFont("Helvetica", 10)
245
+ y_position = 750
246
+ # Print the last line of the question
247
+ c.drawString(x_position, y_position, current_line)
248
+ y_position -= line_height
249
+ # --- End of changes ---
250
+
251
+ if exam_type == "Multiple Choice":
252
+ # Validate question structure
253
+ required_question_keys = ['question', 'options', 'correct_answer']
254
+ if not all(key in q for key in required_question_keys):
255
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
256
+ continue
257
+
258
+ # Print options
259
+ for option_idx, option in enumerate(q['options'], ord('a')):
260
+ c.drawString(70, y_position, f"{chr(option_idx)}) {option}")
261
+ y_position -= line_height
262
+ if y_position < 50:
263
+ c.showPage()
264
+ c.setFont("Helvetica", 10)
265
+ y_position = 750
266
+
267
+ # Print correct answer
268
+ c.drawString(70, y_position, f"Correct Answer: {q['correct_answer']}")
269
+ y_position -= line_height * 2
270
+
271
+ elif exam_type == "True or False":
272
+ # Validate question structure
273
+ required_question_keys = ['statement', 'options', 'correct_answer']
274
+ if not all(key in q for key in required_question_keys):
275
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
276
+ continue
277
+
278
+ # Print options
279
+ for option in q['options']:
280
+ c.drawString(70, y_position, f"{option}")
281
+ y_position -= line_height
282
+ if y_position < 50:
283
+ c.showPage()
284
+ c.setFont("Helvetica", 10)
285
+ y_position = 750
286
+
287
+ # Print correct answer
288
+ c.drawString(70, y_position, f"Correct Answer: {q['correct_answer']}")
289
+ y_position -= line_height * 2
290
+
291
+ elif exam_type in ["Short Response", "Essay Type"]:
292
+ # Validate question structure
293
+ required_question_keys = ['question', 'correct_answer']
294
+ if not all(key in q for key in required_question_keys):
295
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
296
+ continue
297
+
298
+ # Print correct answer
299
+ answer_text = f"Correct Answer: {q['correct_answer']}"
300
+
301
+ # --- Changes for better text flow ---
302
+ # Split the answer into words
303
+ words = answer_text.split()
304
+ current_line = ""
305
+ for word in words:
306
+ temp_line = current_line + " " + word
307
+ text_width = c.stringWidth(temp_line, "Helvetica", 10)
308
+ if text_width <= frame_width:
309
+ current_line = temp_line
310
+ else:
311
+ c.drawString(x_position, y_position, current_line)
312
+ y_position -= line_height
313
+ current_line = word
314
+ if y_position < 50:
315
+ c.showPage()
316
+ c.setFont("Helvetica", 10)
317
+ y_position = 750
318
+ # Print the last line of the answer
319
+ c.drawString(x_position, y_position, current_line)
320
+ y_position -= line_height * 2
321
+ # --- End of changes ---
322
+
323
+ if y_position < 50:
324
+ c.showPage()
325
+ c.setFont("Helvetica", 10)
326
+ y_position = 750
327
+
328
+ # Add the notice at the end
329
+ notice = "This exam was generated by the WVSU Exam Maker (c) 2025 West Visayas State University"
330
+ c.drawString(50, y_position, notice)
331
+
332
+ c.save()
333
+ return pdf_path
334
+
335
+ except Exception as e:
336
+ st.error(f"Error creating PDF: {e}")
337
+ return None
338
+
339
+ def generate_quiz_content(data):
340
+ """
341
+ Separates the metadata and content from a JSON string containing exam data.
342
+ Creates a markdown formatted text that contains the exam metadata and
343
+ enumerates the questions, options and answers nicely formatted for readability.
344
+
345
+ Args:
346
+ data: A JSON string containing the exam data.
347
+
348
+ Returns:
349
+ A markdown formatted string.
350
+ """
351
+ data = json.loads(data)
352
+ metadata = data["metadata"]
353
+ content = data["content"]
354
+ exam_type = metadata["exam_type"]
355
+ if exam_type == "Multiple Choice":
356
+ md_text = f"""# {metadata['subject']} - {metadata['topic']}
357
+
358
+ **Exam Type:** {metadata['exam_type']}
359
+ **Number of Questions:** {metadata['num_questions']}
360
+ **Timestamp:** {metadata['timestamp']}
361
+
362
+ ---
363
+
364
+ """
365
+ for i, q in enumerate(content):
366
+ md_text += f"""Question {i+1}:
367
+ {q['question']}
368
+
369
+ """
370
+ for j, option in enumerate(q['options'], ord('a')):
371
+ md_text += f"""{chr(j)}. {option}
372
+
373
+ """
374
+ md_text += f"""**Correct Answer:** {q['correct_answer']}
375
+
376
+ ---
377
+
378
+ """
379
+ md_text += """This exam was generated by the WVSU Exam Maker
380
+ (c) 2025 West Visayas State University
381
+ """
382
+
383
+ elif exam_type == "True or False":
384
+ md_text = f"""# {metadata['subject']} - {metadata['topic']}
385
+
386
+ **Exam Type:** {metadata['exam_type']}
387
+ **Number of Questions:** {metadata['num_questions']}
388
+ **Timestamp:** {metadata['timestamp']}
389
+
390
+ ---
391
+
392
+ """
393
+
394
+ for i, q in enumerate(content):
395
+ md_text += f"""Statement {i+1}:
396
+
397
+ {q['statement']}
398
+
399
+ """
400
+ for j, option in enumerate(q['options'], ord('a')):
401
+ md_text += f"""{option}
402
+ """
403
+
404
+ md_text += f"""**Correct Answer:** {q['correct_answer']}
405
+
406
+ ---
407
+ """
408
+ md_text += """This exam was generated by the WVSU Exam Maker
409
+ (c) 2025 West Visayas State University"""
410
+
411
+ elif exam_type == "Short Response" or exam_type == "Essay Type":
412
+ md_text = f"""# {metadata['subject']} - {metadata['topic']}
413
+
414
+ **Exam Type:** {metadata['exam_type']}
415
+ **Number of Questions:** {metadata['num_questions']}
416
+ **Timestamp:** {metadata['timestamp']}
417
+
418
+ ---
419
+
420
+ """
421
+
422
+ for i, q in enumerate(content):
423
+ md_text += f"""Question {i+1}:
424
+
425
+ {q['question']}
426
+
427
+ """
428
+ md_text += f"""**Correct Answer:** {q['correct_answer']}
429
+
430
+ ---
431
+ """
432
+ md_text += """This exam was generated by the WVSU Exam Maker
433
+ (c) 2025 West Visayas State University"""
434
+
435
+ return md_text
436
+
437
+ def generate_metadata(subject, topic, num_questions, exam_type):
438
+ """Generates quiz metadata as a dictionary combining num_questions,
439
+ exam_type, and timestamp.
440
+
441
+ Args:
442
+ num_questions: The number of questions in the exam (int).
443
+ exam_type: The type of exam (str).
444
+
445
+ Returns:
446
+ A dictionary containing the quiz metadata.
447
+ """
448
+
449
+ # Format the timestamp
450
+ timestamp = datetime.datetime.now()
451
+ formatted_timestamp = timestamp.strftime("%Y-%m-%d %H:%M:%S")
452
+
453
+ metadata = {
454
+ "subject": subject,
455
+ "topic": topic,
456
+ "num_questions": num_questions,
457
+ "exam_type": exam_type,
458
+ "timestamp": formatted_timestamp
459
+ }
460
+
461
+ return metadata
462
+
463
+ def generate_text(uploaded_file, mime_type, prompt):
464
+ """Generates text based on the uploaded file and prompt."""
465
+ try:
466
+ if st.session_state.is_new_file:
467
+ # Upload the file with the correct MIME type
468
+ file_data = genai.upload_file(uploaded_file, mime_type=mime_type)
469
+
470
+ # Send file and prompt to Gemini API
471
+ chat = st.session_state.chat
472
+ response = chat.send_message(
473
+ [
474
+ prompt,
475
+ file_data
476
+ ],
477
+ stream=enable_stream
478
+ )
479
+ st.session_state.is_new_file = False
480
+ else:
481
+ # continue chat without sending the file again
482
+ # Send a text prompt to Gemini API
483
+ chat = st.session_state.chat
484
+ response = chat.send_message(
485
+ [
486
+ prompt
487
+ ],
488
+ stream=enable_stream
489
+ )
490
+
491
+ return response.text
492
+
493
+ except Exception as e:
494
+ st.error(f"An error occurred while generating text: {e}")
495
+ return None
496
+
497
+ def show_multimodal():
498
+ st.subheader("Multimodal")
499
+ username = st.session_state["username"]
500
+ st.write(f"Welcome, {username}! This page allows you to generate questions based on an image or PDF file.")
501
+
502
+ # Display username and logout button on every page
503
+ st.sidebar.write(f"Current user: {st.session_state['username']}")
504
+
505
+ # we dont use the system instruction for now
506
+ #system_instruction = get_system_instruction(username)
507
+
508
+ # File uploader with allowed types
509
+ uploaded_file = st.file_uploader("Choose an image or PDF...", type=["jpg", "jpeg", "png", "pdf"])
510
+
511
+ if uploaded_file is not None:
512
+ # Determine file type
513
+ file_type = uploaded_file.type
514
+ if file_type.startswith('image'):
515
+ # Display the uploaded image
516
+ image = Image.open(uploaded_file)
517
+ st.image(image, caption="Uploaded Image.", use_container_width=True)
518
+ mime_type = "image/jpeg" # Use a consistent MIME type for images
519
+ # Display a message for PDF upload
520
+ st.write("Image file was uploaded. Questions will be generated based on its contents.")
521
+ elif file_type == 'application/pdf':
522
+ # Display a message for PDF upload
523
+ st.write("PDF file uploaded. Questions will be generated based on its contents.")
524
+ mime_type = "application/pdf"
525
+ else:
526
+ st.error("Unsupported file type. Please upload an image or PDF.")
527
+ st.stop()
528
+
529
+ # User inputs
530
+ # Course selection
531
+ course = st.selectbox("Select Course",
532
+ ["Diploma in Teaching",
533
+ "Post Baccalaureate Diploma in Early Childhood Education",
534
+ "Master of Arts in Education - Language Teaching (English)",
535
+ "Master in Education major in Early Childhood Education"])
536
+
537
+ # Year level selection
538
+ year_level = st.selectbox("Select Year Level",
539
+ ["1st Year",
540
+ "2nd Year",
541
+ "3rd Year",
542
+ "4th Year"])
543
+
544
+ # Subject selection
545
+ subject = st.text_input("Enter Subject",
546
+ "e.g.,The Teaching Profession, Facilitating Learner-Centered Teaching")
547
+
548
+ # Topic selection
549
+ topic = st.text_input("Enter Topic",
550
+ "e.g., Teacher as a professional, Introduction to Learner-Centered Teaching")
551
+
552
+ # Question type selection
553
+ question_type = st.selectbox("Select Question Type",
554
+ ["Multiple Choice",
555
+ "True or False",
556
+ "Short Response",
557
+ "Essay Type"])
558
+
559
+ difficulty = st.selectbox("Select Difficulty",["easy","average","hard"])
560
+
561
+ #number of questions to generate
562
+ if question_type != "Essay Type":
563
+ num_questions = st.selectbox("Number of Questions to Generate",
564
+ [10, 20, 30, 40, 50])
565
+ else:
566
+ num_questions = st.selectbox("Number of Questions to Generate",
567
+ [1, 2, 3, 4, 5])
568
+
569
+ # Combine user inputs into a prompt
570
+ prompt = f"""Refer to the uploaded document. Generate a {question_type} question for a {year_level} {course} student
571
+ in {subject} on the topic of {topic} with a {difficulty} difficulty level.
572
+ The questions should require higher order thinking skills.
573
+ """
574
+
575
+ if question_type == "Multiple Choice":
576
+ prompt += """Provide 4 choices. Provide the correct answer in the format 'Answer: A'.
577
+ Use the following JSON format for each question:
578
+ [{
579
+ "question": "Your question here?",
580
+ "options": ["Option A", "Option B", "Option C", "Option D"],
581
+ "correct_answer": "full text of the correct answer"
582
+ }, ... more questions]
583
+ Ensure that the response only contains the JSON array of questions and nothing else.
584
+ """
585
+ elif question_type == "True or False":
586
+ prompt += """Indicate whether the statement is true or false. Keep the statement brief and concise.
587
+ Use the following JSON format for each question:
588
+ [{
589
+ "statement": "Your statement here",
590
+ "options": ["True", "False"],
591
+ "correct_answer": True"
592
+ }, ... more questions]
593
+ Ensure that the response only contains the JSON array of questions and nothing else.
594
+ """
595
+ elif question_type == "Short Response":
596
+ prompt += """Create question that require a word or short phrase as answer. Use the following JSON format for each question:
597
+ [{
598
+ "question": "Your question here?",
599
+ "correct_answer": A word or phrase"
600
+ }, ... more questions]
601
+ Ensure that the response only contains the JSON array of questions and nothing else.
602
+ """
603
+ elif question_type == "Essay Type":
604
+ prompt += """Create questions that require a short essay between 300 to 500 words.
605
+ Provide a detailed answer. Use the following JSON format for each question:
606
+ [{
607
+ "question": "Your question here?",
608
+ "correct_answer": The essay answer goes here."
609
+ }, ... more questions]
610
+ Ensure that the response only contains the JSON array of questions and nothing else.
611
+ """
612
+
613
+ if not question_type == "Essay Type":
614
+ prompt += f"Generate 10 questions. Do not repeat questions you have already given in previous prompts. Exclude markdown tags in the response."
615
+ else:
616
+ prompt += f" Generate {num_questions} questions. Do not repeat questions you have already given in previous prompts. Exclude markdown tags in the response"
617
+
618
+ full_quiz = ""
619
+
620
+ # Send button
621
+ if st.button("Generate Questions"):
622
+
623
+
624
+ if not uploaded_file:
625
+
626
+ st.warning("Please upload an image or PDF and enter a prompt.")
627
+ st.stop()
628
+ else:
629
+ if question_type == "Essay Type":
630
+ #prompt once
631
+ with st.spinner('Generating questions...'):
632
+ full_quiz = _clean_markdown(generate_text(uploaded_file, mime_type, prompt))
633
+
634
+ else:
635
+ if num_questions == 10:
636
+
637
+ #prompt once
638
+ with st.spinner('Generating questions...'):
639
+ full_quiz = _clean_markdown(generate_text(uploaded_file, mime_type, prompt))
640
+ else:
641
+ #prompt multiple times
642
+ times = num_questions//10
643
+ for i in range(times):
644
+ with st.spinner('Generating questions...'):
645
+ response = generate_text(uploaded_file, mime_type, prompt)
646
+
647
+ if i==0:
648
+ full_quiz = _clean_markdown(response)
649
+ else:
650
+ full_quiz = merge_json_strings(full_quiz, response)
651
+
652
+ metadata = generate_metadata(subject, topic, num_questions, question_type)
653
+
654
+ try:
655
+ # Attempt to load the string as JSON to validate it
656
+ content = json.loads(full_quiz)
657
+ except json.JSONDecodeError:
658
+ st.error("Error: Invalid JSON string for quiz content.")
659
+ st.stop()
660
+
661
+ json_string = create_json(metadata, content)
662
+
663
+ quiz_markdown = generate_quiz_content(json_string)
664
+ st.markdown(quiz_markdown)
665
+
666
+ pdf_path = create_pdf(json_string)
667
+
668
+ if pdf_path:
669
+ """Click the button to download the generated PDF."""
670
+ try:
671
+ with open(pdf_path, "rb") as f:
672
+ st.download_button("Download PDF", f, file_name=os.path.basename(pdf_path))
673
+ except Exception as e:
674
+ st.error(f"Error handling file download: {e}")
675
+ else:
676
+ st.error("Failed to generate the PDF. Please try again.")
677
+
678
+ #record the prompt for monitoring
679
+ save_user_prompt(username, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Multimodal")
680
+
681
+ if st.session_state["authenticated"]:
682
+ show_multimodal()
683
+ else:
684
+ if not st.session_state["is_starting"]:
685
+ st.write("You are not authenticated. Please log in to access this page.")
pages/4_Settings.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import sqlite3
3
+
4
+ def get_system_instruction(username):
5
+ conn = sqlite3.connect('users.db')
6
+ c = conn.cursor()
7
+ c.execute('SELECT instruction FROM system_instructions WHERE username=?', (username,))
8
+ instruction = c.fetchone()
9
+ conn.close()
10
+ if instruction:
11
+ return instruction[0]
12
+ else:
13
+ return "Default system instruction."
14
+
15
+ def save_system_instruction(username, instruction):
16
+ conn = sqlite3.connect('users.db')
17
+ c = conn.cursor()
18
+ c.execute('SELECT * FROM system_instructions WHERE username=?', (username,))
19
+ existing_instruction = c.fetchone()
20
+ if existing_instruction:
21
+ c.execute('UPDATE system_instructions SET instruction=? WHERE username=?', (instruction, username))
22
+ else:
23
+ c.execute('INSERT INTO system_instructions(username, instruction) VALUES (?,?)', (username, instruction))
24
+ conn.commit()
25
+ conn.close()
26
+
27
+ def show_settings():
28
+ st.subheader("Settings")
29
+ username = st.session_state["username"]
30
+ system_instruction = get_system_instruction(username)
31
+ st.write("System Instruction:")
32
+ instruction = st.text_area("", value=system_instruction, height=200)
33
+ if st.button("Save Changes"):
34
+ save_system_instruction(username, instruction)
35
+ st.success("System instruction saved successfully.")
36
+
37
+ st.write("Note: System instruction is not used in this version of the app.")
38
+
39
+ if st.session_state["authenticated"]:
40
+ show_settings()
41
+ else:
42
+ if not st.session_state["is_starting"]:
43
+ st.write("You are not authenticated. Please log in to access this page.")
pages/__pycache__/About.cpython-313.pyc ADDED
Binary file (740 Bytes). View file
 
pages/__pycache__/Multimodal.cpython-313.pyc ADDED
Binary file (22.8 kB). View file
 
pages/__pycache__/Settings.cpython-313.pyc ADDED
Binary file (2.61 kB). View file
 
pages/__pycache__/Text_prompt.cpython-313.pyc ADDED
Binary file (2.56 kB). View file
 
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ streamlit
2
+ passlib
3
+ bcrypt==3.2.0
4
+ google-generativeai
5
+ reportlab
users.db ADDED
Binary file (24.6 kB). View file