Spaces:
Sleeping
Sleeping
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
|