Spaces:
Running
Running
Abhinav
commited on
Commit
·
484bee1
1
Parent(s):
f2f372e
Refactor AI integration and enhance quiz functionality with validation
Browse files- .gitignore +3 -1
- ai.py +85 -0
- app.py +80 -76
- requirements.txt +0 -0
- stylesheet.py +176 -3
.gitignore
CHANGED
@@ -2,4 +2,6 @@ env/
|
|
2 |
.env
|
3 |
.env.*
|
4 |
.env.local
|
5 |
-
__pycache__/
|
|
|
|
|
|
2 |
.env
|
3 |
.env.*
|
4 |
.env.local
|
5 |
+
__pycache__/
|
6 |
+
.vscode/
|
7 |
+
test.py
|
ai.py
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from openai import OpenAI
|
3 |
+
import modal
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
|
6 |
+
load_dotenv()
|
7 |
+
|
8 |
+
class Colors:
|
9 |
+
"""ANSI color codes for terminal output formatting."""
|
10 |
+
GREEN = "\033[0;32m"
|
11 |
+
RED = "\033[0;31m"
|
12 |
+
BLUE = "\033[0;34m"
|
13 |
+
GRAY = "\033[0;90m"
|
14 |
+
BOLD = "\033[1m"
|
15 |
+
END = "\033[0m"
|
16 |
+
|
17 |
+
|
18 |
+
def ask_ai(
|
19 |
+
prompt,
|
20 |
+
system_prompt,
|
21 |
+
temperature=0.7,
|
22 |
+
max_tokens=None,
|
23 |
+
stream=True,
|
24 |
+
verbose=False
|
25 |
+
):
|
26 |
+
"""
|
27 |
+
Send a prompt to the AI model and get a response.
|
28 |
+
|
29 |
+
Args:
|
30 |
+
prompt (str): The user prompt to send to the AI
|
31 |
+
system_prompt (str): The system instructions for the AI
|
32 |
+
model (str): The model name to use
|
33 |
+
temperature (float): Controls randomness (0.0-1.0)
|
34 |
+
max_tokens (int): Maximum tokens in the response
|
35 |
+
stream (bool): Whether to stream the response
|
36 |
+
verbose (bool): Whether to print status messages
|
37 |
+
|
38 |
+
Returns:
|
39 |
+
str: The AI's response text
|
40 |
+
"""
|
41 |
+
|
42 |
+
# Create OpenAI client and set up the connection to Modal
|
43 |
+
API_KEY = os.getenv("Modal_API_KEY")
|
44 |
+
client = OpenAI(api_key=API_KEY)
|
45 |
+
|
46 |
+
# Set base URL to point to our Modal-deployed endpoint
|
47 |
+
client.base_url = f"https://abhinav77642--llama-3-1-8b-instruct-serve.modal.run/v1"
|
48 |
+
|
49 |
+
# Set up the messages for the conversation
|
50 |
+
messages = [
|
51 |
+
{"role": "system", "content": system_prompt},
|
52 |
+
{"role": "user", "content": prompt}
|
53 |
+
]
|
54 |
+
|
55 |
+
# Set up the completion parameters
|
56 |
+
completion_args = {
|
57 |
+
"model": "neuralmagic/Meta-Llama-3.1-8B-Instruct-quantized.w4a16",
|
58 |
+
"messages": messages,
|
59 |
+
"temperature": temperature,
|
60 |
+
"max_tokens": max_tokens,
|
61 |
+
"stream": stream
|
62 |
+
}
|
63 |
+
|
64 |
+
# Remove None values
|
65 |
+
completion_args = {k: v for k, v in completion_args.items() if v is not None}
|
66 |
+
|
67 |
+
try:
|
68 |
+
response = client.chat.completions.create(**completion_args)
|
69 |
+
|
70 |
+
# Handle the response based on streaming or non-streaming mode
|
71 |
+
if stream:
|
72 |
+
result = ""
|
73 |
+
for chunk in response:
|
74 |
+
if chunk.choices and chunk.choices[0].delta.content:
|
75 |
+
content = chunk.choices[0].delta.content
|
76 |
+
result += content
|
77 |
+
|
78 |
+
return result
|
79 |
+
else:
|
80 |
+
result = response.choices[0].message.content
|
81 |
+
return result
|
82 |
+
|
83 |
+
except Exception as e:
|
84 |
+
error_msg = f"Error during API call: {e}"
|
85 |
+
return f"Error: {error_msg}"
|
app.py
CHANGED
@@ -1,42 +1,16 @@
|
|
1 |
-
import os
|
2 |
import json
|
3 |
import gradio as gr
|
4 |
import PyPDF2 as pypdf
|
5 |
-
from prompts import prompts
|
6 |
from stylesheet import style
|
7 |
from dotenv import load_dotenv
|
8 |
-
from
|
9 |
-
from
|
10 |
-
from azure.core.credentials import AzureKeyCredential
|
11 |
|
12 |
load_dotenv()
|
13 |
|
14 |
css, js = style()
|
15 |
|
16 |
|
17 |
-
# AI Response Generation Function
|
18 |
-
def ai(system_prompt, user_prompt):
|
19 |
-
endpoint = "https://abhin-marwbf2g-eastus2.cognitiveservices.azure.com/openai/deployments/gpt-4o"
|
20 |
-
model_name = "gpt-4o"
|
21 |
-
api_key = os.getenv("API_KEY")
|
22 |
-
client = ChatCompletionsClient(
|
23 |
-
endpoint=endpoint,
|
24 |
-
credential=AzureKeyCredential(api_key),
|
25 |
-
)
|
26 |
-
response = client.complete(
|
27 |
-
messages=[
|
28 |
-
SystemMessage(content=system_prompt),
|
29 |
-
UserMessage(content=user_prompt),
|
30 |
-
],
|
31 |
-
max_tokens=4096,
|
32 |
-
temperature=1.0,
|
33 |
-
top_p=1.0,
|
34 |
-
model=model_name,
|
35 |
-
)
|
36 |
-
# print("---------AI RESPONSE RECEIVED-----------")
|
37 |
-
return response.choices[0].message.content
|
38 |
-
|
39 |
-
|
40 |
# PDF To Text Conversion Function
|
41 |
def convertPdfIntoText(file):
|
42 |
if file is None:
|
@@ -50,79 +24,109 @@ def convertPdfIntoText(file):
|
|
50 |
|
51 |
|
52 |
# Gradio Interface Setup
|
53 |
-
with gr.Blocks(title="Quizy", css=css, head=js) as
|
54 |
gr.Markdown("# Quizy: Quiz Generator")
|
55 |
gr.Markdown("Upload a PDF file to generate a quiz based on its content.")
|
56 |
|
57 |
file_input = gr.File(label="Upload PDF File", file_types=[".pdf"])
|
58 |
-
|
59 |
|
60 |
with gr.Accordion("Quiz", open=False, visible=False) as quiz_accordion:
|
61 |
output_container = gr.HTML()
|
|
|
62 |
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
return "<p>No file uploaded.</p>", gr.Accordion(visible=False)
|
67 |
|
68 |
-
|
69 |
|
70 |
-
|
71 |
-
|
72 |
|
73 |
-
|
74 |
-
|
75 |
-
|
|
|
|
|
76 |
|
77 |
-
|
|
|
78 |
|
79 |
-
|
80 |
-
|
81 |
-
str(i): question["answer"] for i, question in enumerate(quiz_data["quiz"])
|
82 |
-
}
|
83 |
-
html += f"<div id='quiz-answers' style='display:none;'>{json.dumps(answers_json)}</div>"
|
84 |
|
85 |
-
|
86 |
-
|
|
|
87 |
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
94 |
html += "<div class='options'>"
|
95 |
-
|
96 |
-
|
|
|
|
|
97 |
html += "</div>"
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
|
|
|
|
|
|
|
|
|
|
104 |
html += "</div>"
|
105 |
-
elif question_type == "multiple_choice":
|
106 |
-
if "options" in question:
|
107 |
-
html += "<div class='options'>"
|
108 |
-
for option in question["options"]:
|
109 |
-
html += f"<div><input type='checkbox' name='q{i}[]' value='{option}'> {option}</div>"
|
110 |
-
html += "</div>"
|
111 |
-
elif question_type == "fill_in_the_blank":
|
112 |
-
html += "<input type='text' placeholder='Enter your answer here...'>"
|
113 |
|
|
|
|
|
|
|
|
|
114 |
html += "</div>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
|
116 |
-
|
117 |
-
|
118 |
-
html += "</div>"
|
119 |
|
120 |
-
|
121 |
|
122 |
-
|
123 |
fn=generate_quiz,
|
124 |
inputs=file_input,
|
125 |
outputs=[output_container, quiz_accordion],
|
126 |
)
|
127 |
|
128 |
-
|
|
|
|
|
1 |
import json
|
2 |
import gradio as gr
|
3 |
import PyPDF2 as pypdf
|
|
|
4 |
from stylesheet import style
|
5 |
from dotenv import load_dotenv
|
6 |
+
from prompts import prompts
|
7 |
+
from ai import ask_ai
|
|
|
8 |
|
9 |
load_dotenv()
|
10 |
|
11 |
css, js = style()
|
12 |
|
13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
# PDF To Text Conversion Function
|
15 |
def convertPdfIntoText(file):
|
16 |
if file is None:
|
|
|
24 |
|
25 |
|
26 |
# Gradio Interface Setup
|
27 |
+
with gr.Blocks(title="Quizy", css=css, head=js) as app:
|
28 |
gr.Markdown("# Quizy: Quiz Generator")
|
29 |
gr.Markdown("Upload a PDF file to generate a quiz based on its content.")
|
30 |
|
31 |
file_input = gr.File(label="Upload PDF File", file_types=[".pdf"])
|
32 |
+
generate_btn = gr.Button("Generate Quiz", visible=True, elem_id="generate-btn")
|
33 |
|
34 |
with gr.Accordion("Quiz", open=False, visible=False) as quiz_accordion:
|
35 |
output_container = gr.HTML()
|
36 |
+
submit_btn = gr.Button("Submit", elem_id="submit-btn", visible=False)
|
37 |
|
38 |
+
def generate_quiz(file):
|
39 |
+
if file is None:
|
40 |
+
return "<p>No file uploaded.</p>", gr.Accordion(visible=False)
|
|
|
41 |
|
42 |
+
inputData = convertPdfIntoText(file)
|
43 |
|
44 |
+
systemPrompt, userPrompt = prompts(inputData)
|
45 |
+
response = ask_ai(systemPrompt, userPrompt)
|
46 |
|
47 |
+
clean_response = (
|
48 |
+
response.strip().removeprefix("```json").removesuffix("```")
|
49 |
+
)
|
50 |
+
clean_response = clean_response.strip()
|
51 |
+
quiz_data = json.loads(clean_response)
|
52 |
|
53 |
+
html = "<div class='quiz-container'>"
|
54 |
+
html += "<form id='quiz-form' method='post' onsubmit='return false;'>"
|
55 |
|
56 |
+
for i, question in enumerate(quiz_data["quiz"]):
|
57 |
+
question_type = question["type"]
|
|
|
|
|
|
|
58 |
|
59 |
+
answer_json = json.dumps(question["answer"]).replace('"', """)
|
60 |
+
html += f"<div class='question' data-id='{i}' data-type='{question_type}' data-answer='{answer_json}'>"
|
61 |
+
html += f"<h3>Question {i+1}: {question['question']}</h3>"
|
62 |
|
63 |
+
if question_type == "single_choice":
|
64 |
+
if "options" in question:
|
65 |
+
html += "<div class='options'>"
|
66 |
+
for option in question["options"]:
|
67 |
+
html += f"<div><input type='radio' name='q{i}' value='{option}'> {option}</div>"
|
68 |
+
html += "</div>"
|
69 |
+
elif question_type == "true_false":
|
70 |
html += "<div class='options'>"
|
71 |
+
html += (
|
72 |
+
f"<div><input type='radio' name='q{i}' value='True'> True</div>"
|
73 |
+
)
|
74 |
+
html += f"<div><input type='radio' name='q{i}' value='False'> False</div>"
|
75 |
html += "</div>"
|
76 |
+
elif question_type == "multiple_choice":
|
77 |
+
if "options" in question:
|
78 |
+
html += "<div class='options'>"
|
79 |
+
for option in question["options"]:
|
80 |
+
html += f"<div><input type='checkbox' name='q{i}[]' value='{option}'> {option}</div>"
|
81 |
+
html += "</div>"
|
82 |
+
elif question_type == "fill_in_the_blank":
|
83 |
+
html += (
|
84 |
+
"<input type='text' placeholder='Enter your answer here...'>"
|
85 |
+
)
|
86 |
+
|
87 |
html += "</div>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
|
89 |
+
# Updated button to use validateForm first and then call checkAnswers if validation passes
|
90 |
+
html += "<button type='button' class='submit-btn' onclick='if(validateForm()) checkAnswers(event);'>Submit Quiz</button>"
|
91 |
+
html += "</form>" # Close the form
|
92 |
+
html += "<div id='result'></div>"
|
93 |
html += "</div>"
|
94 |
+
html += "<details id='quiz-answers' style='display:none;'>"
|
95 |
+
html += "<summary><h2>Solutions:</h2></summary>"
|
96 |
+
html += "<div class='answers-container'>"
|
97 |
+
|
98 |
+
for i, question in enumerate(quiz_data["quiz"]):
|
99 |
+
question_type = question["type"]
|
100 |
+
html += "<div class='answer-item'>"
|
101 |
+
html += f"<h3>Question {i+1}: {question['question']}</h3>"
|
102 |
+
|
103 |
+
# Format the answer based on question type
|
104 |
+
if question_type == "multiple_choice":
|
105 |
+
if isinstance(question["answer"], list):
|
106 |
+
answer_text = ", ".join(question["answer"])
|
107 |
+
html += f"<p><strong>Correct Answer:</strong> {answer_text}</p>"
|
108 |
+
else:
|
109 |
+
html += f"<p><strong>Correct Answer:</strong> {question['answer']}</p>"
|
110 |
+
elif question_type == "fill_in_the_blank":
|
111 |
+
html += (
|
112 |
+
f"<p><strong>Correct Answer:</strong> {question['answer']}</p>"
|
113 |
+
)
|
114 |
+
else: # single_choice or true_false
|
115 |
+
html += (
|
116 |
+
f"<p><strong>Correct Answer:</strong> {question['answer']}</p>"
|
117 |
+
)
|
118 |
+
|
119 |
+
html += "</div>"
|
120 |
|
121 |
+
html += "</div>" # Close answers-container
|
122 |
+
html += "</details>" # Close quiz-answers
|
|
|
123 |
|
124 |
+
return html, gr.Accordion(visible=True)
|
125 |
|
126 |
+
generate_btn.click(
|
127 |
fn=generate_quiz,
|
128 |
inputs=file_input,
|
129 |
outputs=[output_container, quiz_accordion],
|
130 |
)
|
131 |
|
132 |
+
app.launch()
|
requirements.txt
CHANGED
Binary files a/requirements.txt and b/requirements.txt differ
|
|
stylesheet.py
CHANGED
@@ -7,6 +7,34 @@ def style():
|
|
7 |
border: 1px solid #ddd;
|
8 |
border-radius: 5px;
|
9 |
margin-top: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
}
|
11 |
#result {
|
12 |
display: none;
|
@@ -22,10 +50,143 @@ def style():
|
|
22 |
color: #333333;
|
23 |
border-radius: 5px;
|
24 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
"""
|
26 |
js = """
|
27 |
<script>
|
28 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
let score = 0;
|
30 |
let total = 0;
|
31 |
const questions = document.querySelectorAll(".question");
|
@@ -75,8 +236,18 @@ def style():
|
|
75 |
score++;
|
76 |
}
|
77 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
});
|
79 |
-
|
80 |
// Display the result
|
81 |
resultElement.style.display = "block";
|
82 |
if (score === total) {
|
@@ -94,7 +265,9 @@ def style():
|
|
94 |
resultElement.style.border = "1px solid #ddd";
|
95 |
resultElement.style.borderRadius = "5px";
|
96 |
resultElement.style.marginTop = "20px";
|
|
|
|
|
97 |
}
|
98 |
</script>
|
99 |
"""
|
100 |
-
return css, js
|
|
|
7 |
border: 1px solid #ddd;
|
8 |
border-radius: 5px;
|
9 |
margin-top: 20px;
|
10 |
+
cursor: pointer;
|
11 |
+
transition: all 0.3s ease;
|
12 |
+
}
|
13 |
+
.submit-btn.disabled {
|
14 |
+
background-color: #cccccc;
|
15 |
+
color: #666666;
|
16 |
+
cursor: not-allowed;
|
17 |
+
opacity: 0.7;
|
18 |
+
}
|
19 |
+
.validation-alert {
|
20 |
+
background-color: #ffdddd;
|
21 |
+
color: #990000;
|
22 |
+
padding: 10px;
|
23 |
+
margin-bottom: 15px;
|
24 |
+
border-radius: 5px;
|
25 |
+
font-weight: bold;
|
26 |
+
text-align: center;
|
27 |
+
animation: fadeIn 0.5s;
|
28 |
+
}
|
29 |
+
.invalid-question {
|
30 |
+
border: 2px solid #ff0000;
|
31 |
+
padding: 10px;
|
32 |
+
border-radius: 5px;
|
33 |
+
margin-bottom: 10px;
|
34 |
+
}
|
35 |
+
@keyframes fadeIn {
|
36 |
+
0% { opacity: 0; }
|
37 |
+
100% { opacity: 1; }
|
38 |
}
|
39 |
#result {
|
40 |
display: none;
|
|
|
50 |
color: #333333;
|
51 |
border-radius: 5px;
|
52 |
}
|
53 |
+
#quiz-answers {
|
54 |
+
margin-top: 30px;
|
55 |
+
padding: 20px;
|
56 |
+
background-color: #dfdfdf;
|
57 |
+
border: 1px solid #ddd;
|
58 |
+
border-radius: 5px;
|
59 |
+
}
|
60 |
+
#quiz-answers summary {
|
61 |
+
display: inline-block;
|
62 |
+
padding-bottom: 5px;
|
63 |
+
cursor: pointer;
|
64 |
+
}
|
65 |
+
#quiz-answers summary, #quiz-answers h2 {
|
66 |
+
color: #383838;
|
67 |
+
}
|
68 |
+
.answers-container {
|
69 |
+
display: flex;
|
70 |
+
flex-direction: column;
|
71 |
+
gap: 20px;
|
72 |
+
}
|
73 |
+
|
74 |
+
.answer-item {
|
75 |
+
padding: 15px;
|
76 |
+
background-color: #e1ece4;
|
77 |
+
border: 2px solid lightgreen;
|
78 |
+
border-radius: 5px;
|
79 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
80 |
+
}
|
81 |
+
|
82 |
+
.answer-item h3 {
|
83 |
+
margin-top: 0;
|
84 |
+
color: #333;
|
85 |
+
font-size: 18px;
|
86 |
+
}
|
87 |
+
|
88 |
+
.answer-item p {
|
89 |
+
margin-bottom: 0;
|
90 |
+
color: #009214;
|
91 |
+
font-size: 16px;
|
92 |
+
font-weight: 500;
|
93 |
+
}
|
94 |
+
.answer-item p strong {
|
95 |
+
color: #1636d5;
|
96 |
+
}
|
97 |
"""
|
98 |
js = """
|
99 |
<script>
|
100 |
+
function validateForm() {
|
101 |
+
const questions = document.querySelectorAll(".question");
|
102 |
+
let isValid = true;
|
103 |
+
|
104 |
+
questions.forEach((question) => {
|
105 |
+
const questionType = question.getAttribute("data-type");
|
106 |
+
const inputs = question.querySelectorAll("input");
|
107 |
+
|
108 |
+
if (questionType === "single_choice" || questionType === "true_false") {
|
109 |
+
// Check if at least one radio button is selected
|
110 |
+
let hasChecked = false;
|
111 |
+
inputs.forEach((input) => {
|
112 |
+
if (input.checked) {
|
113 |
+
hasChecked = true;
|
114 |
+
}
|
115 |
+
});
|
116 |
+
if (!hasChecked) {
|
117 |
+
isValid = false;
|
118 |
+
// Highlight the question that needs attention
|
119 |
+
question.style.border = "2px solid red";
|
120 |
+
question.style.padding = "10px";
|
121 |
+
question.style.borderRadius = "5px";
|
122 |
+
} else {
|
123 |
+
question.style.border = "none";
|
124 |
+
question.style.padding = "0";
|
125 |
+
}
|
126 |
+
} else if (questionType === "multiple_choice") {
|
127 |
+
// Check if at least one checkbox is selected
|
128 |
+
let hasChecked = false;
|
129 |
+
inputs.forEach((input) => {
|
130 |
+
if (input.checked) {
|
131 |
+
hasChecked = true;
|
132 |
+
}
|
133 |
+
});
|
134 |
+
if (!hasChecked) {
|
135 |
+
isValid = false;
|
136 |
+
question.style.border = "2px solid red";
|
137 |
+
question.style.padding = "10px";
|
138 |
+
question.style.borderRadius = "5px";
|
139 |
+
} else {
|
140 |
+
question.style.border = "none";
|
141 |
+
question.style.padding = "0";
|
142 |
+
}
|
143 |
+
} else if (questionType === "fill_in_the_blank") {
|
144 |
+
// Check if the text input has a value
|
145 |
+
if (inputs[0].value.trim() === "") {
|
146 |
+
isValid = false;
|
147 |
+
question.style.border = "2px solid red";
|
148 |
+
question.style.padding = "10px";
|
149 |
+
question.style.borderRadius = "5px";
|
150 |
+
} else {
|
151 |
+
question.style.border = "none";
|
152 |
+
question.style.padding = "0";
|
153 |
+
}
|
154 |
+
}
|
155 |
+
});
|
156 |
+
|
157 |
+
return isValid;
|
158 |
+
}
|
159 |
+
|
160 |
+
function checkAnswers(event) {
|
161 |
+
event.preventDefault();
|
162 |
+
|
163 |
+
// First validate the form
|
164 |
+
if (!validateForm()) {
|
165 |
+
// Show an alert message
|
166 |
+
const alertDiv = document.createElement('div');
|
167 |
+
alertDiv.className = 'validation-alert';
|
168 |
+
alertDiv.innerHTML = '<p>Please answer all questions before submitting!</p>';
|
169 |
+
alertDiv.style.backgroundColor = '#ffdddd';
|
170 |
+
alertDiv.style.color = '#990000';
|
171 |
+
alertDiv.style.padding = '10px';
|
172 |
+
alertDiv.style.borderRadius = '5px';
|
173 |
+
alertDiv.style.marginBottom = '15px';
|
174 |
+
alertDiv.style.fontWeight = 'bold';
|
175 |
+
alertDiv.style.textAlign = 'center';
|
176 |
+
|
177 |
+
// Insert at the top of the form
|
178 |
+
const form = document.getElementById('quiz-form');
|
179 |
+
const existingAlert = document.querySelector('.validation-alert');
|
180 |
+
if (existingAlert) {
|
181 |
+
form.removeChild(existingAlert);
|
182 |
+
}
|
183 |
+
form.insertBefore(alertDiv, form.firstChild);
|
184 |
+
|
185 |
+
// Scroll to the top
|
186 |
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
187 |
+
return;
|
188 |
+
}
|
189 |
+
|
190 |
let score = 0;
|
191 |
let total = 0;
|
192 |
const questions = document.querySelectorAll(".question");
|
|
|
236 |
score++;
|
237 |
}
|
238 |
}
|
239 |
+
}); // Remove any validation alerts once submitted successfully
|
240 |
+
const existingAlert = document.querySelector('.validation-alert');
|
241 |
+
if (existingAlert) {
|
242 |
+
existingAlert.remove();
|
243 |
+
}
|
244 |
+
|
245 |
+
// Remove any highlight on questions
|
246 |
+
document.querySelectorAll('.question').forEach(q => {
|
247 |
+
q.style.border = 'none';
|
248 |
+
q.style.padding = '0';
|
249 |
});
|
250 |
+
|
251 |
// Display the result
|
252 |
resultElement.style.display = "block";
|
253 |
if (score === total) {
|
|
|
265 |
resultElement.style.border = "1px solid #ddd";
|
266 |
resultElement.style.borderRadius = "5px";
|
267 |
resultElement.style.marginTop = "20px";
|
268 |
+
answerContainer = document.getElementById('quiz-answers');
|
269 |
+
answerContainer.style.display = 'block';
|
270 |
}
|
271 |
</script>
|
272 |
"""
|
273 |
+
return css, js
|