Version 1.0 ready (#1)
* FEAT: Basic app completed & file uploading added * Added pdf/txt support and quiz template * FEAT: PDF/TEXT uploading and getting file contents done * Updated .gitignore * Modified quiz template * Fix UI - Added CSS to upload page * Feat Quiz UI - Added CSS to Quiz Page * Feat: Question Extraction - Extract questions from raw text * Feat: added incorrect answer generation * Feat: Tied together question extraction and incorrect anwer generation * Fix: handle word not in vocabulary error * Fix: added document as argument to get_questions_dict * Feat: Integrated uploading file and question generation; needs fixes * Feat Quiz UI - added correct answer UI * Feat: Clean text for better processing * Fix UI - added gradient and spacing * Feat UI - Added uploading animation * Fixed options issue on quiz screen * Fix UI - added fonts, favicon etc * Fix - Formatted code * Feat UI - added fork me on github ribbon * Feat: Added quiz result functionality * Fix UI - added text to landing page * Fix UI - added CSS to submit button * Feat UI - added CSS to results page Co-authored-by: user86 <kshitij.kotasthane@gmail.com> Co-authored-by: telescopic <vigneshsuresh1775@gmail.com>
This commit is contained in:
parent
812a92bbea
commit
9d2adf338d
141
.gitignore
vendored
Normal file
141
.gitignore
vendored
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# Do not upload folder, auto created at each run
|
||||||
|
pdf/
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
@ -1,4 +1,4 @@
|
|||||||
# Fantastic-Falcons-1.0
|
# MLH Quizzet
|
||||||
|
|
||||||
This is a smart Quiz Generator that generates a dynamic quiz from any uploaded text/PDF document using NLP. This can be used for self-analysis, question paper generation, and evaluation, thus reducing human effort.
|
This is a smart Quiz Generator that generates a dynamic quiz from any uploaded text/PDF document using NLP. This can be used for self-analysis, question paper generation, and evaluation, thus reducing human effort.
|
||||||
|
|
||||||
@ -21,6 +21,7 @@ This is a smart Quiz Generator that generates a dynamic quiz from any uploaded t
|
|||||||
|
|
||||||
## Technology Stack:
|
## Technology Stack:
|
||||||
|
|
||||||
|
|
||||||
<img src="https://img.shields.io/badge/html5%20-%23E34F26.svg?&style=for-the-badge&logo=html5&logoColor=white"/> <img src="https://img.shields.io/badge/css3%20-%231572B6.svg?&style=for-the-badge&logo=css3&logoColor=white"/> <img src="https://img.shields.io/badge/javascript%20-%23323330.svg?&style=for-the-badge&logo=javascript&logoColor=%23F7DF1E"/> <img src="https://img.shields.io/badge/python%20-%2314354C.svg?&style=for-the-badge&logo=python&logoColor=white"/> <img src="https://img.shields.io/badge/flask%20-%23000.svg?&style=for-the-badge&logo=flask&logoColor=white"/> <img src="https://img.shields.io/badge/bootstrap%20-%23563D7C.svg?&style=for-the-badge&logo=bootstrap&logoColor=white"/> <img src="https://img.shields.io/badge/github%20-%23121011.svg?&style=for-the-badge&logo=github&logoColor=white"/> <img src ="https://img.shields.io/badge/sqlite-%2307405e.svg?&style=for-the-badge&logo=sqlite&logoColor=white"/>
|
<img src="https://img.shields.io/badge/html5%20-%23E34F26.svg?&style=for-the-badge&logo=html5&logoColor=white"/> <img src="https://img.shields.io/badge/css3%20-%231572B6.svg?&style=for-the-badge&logo=css3&logoColor=white"/> <img src="https://img.shields.io/badge/javascript%20-%23323330.svg?&style=for-the-badge&logo=javascript&logoColor=%23F7DF1E"/> <img src="https://img.shields.io/badge/python%20-%2314354C.svg?&style=for-the-badge&logo=python&logoColor=white"/> <img src="https://img.shields.io/badge/flask%20-%23000.svg?&style=for-the-badge&logo=flask&logoColor=white"/> <img src="https://img.shields.io/badge/bootstrap%20-%23563D7C.svg?&style=for-the-badge&logo=bootstrap&logoColor=white"/> <img src="https://img.shields.io/badge/github%20-%23121011.svg?&style=for-the-badge&logo=github&logoColor=white"/> <img src ="https://img.shields.io/badge/sqlite-%2307405e.svg?&style=for-the-badge&logo=sqlite&logoColor=white"/>
|
||||||
|
|
||||||
- **Frontend**: HTML, CSS, Vanilla JS
|
- **Frontend**: HTML, CSS, Vanilla JS
|
||||||
@ -107,6 +108,12 @@ $ python app.py
|
|||||||
| 2. | Kshitij Kotasthane | Backend Developer | [@kshitij86](https://github.com/kshitij86) |
|
| 2. | Kshitij Kotasthane | Backend Developer | [@kshitij86](https://github.com/kshitij86) |
|
||||||
| 3. | Vignesh S | ML | [@telescopic](https://github.com/telescopic) |
|
| 3. | Vignesh S | ML | [@telescopic](https://github.com/telescopic) |
|
||||||
|
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Contributors ✨
|
## Contributors ✨
|
||||||
|
|
||||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||||
|
|||||||
72
app.py
Normal file
72
app.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import os
|
||||||
|
from flask import Flask, render_template, redirect, url_for
|
||||||
|
from flask.globals import request
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
from workers import pdf2text, txt2questions
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
UPLOAD_FOLDER = './pdf/'
|
||||||
|
|
||||||
|
|
||||||
|
# Init an app object
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
||||||
|
|
||||||
|
# Global quiz object
|
||||||
|
questions = dict()
|
||||||
|
|
||||||
|
|
||||||
|
@ app.route('/')
|
||||||
|
def index():
|
||||||
|
""" The landing page for the app """
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
|
||||||
|
@ app.route('/quiz', methods=['GET', 'POST'])
|
||||||
|
def quiz():
|
||||||
|
""" Handle upload and conversion of file + other stuff """
|
||||||
|
|
||||||
|
UPLOAD_STATUS = False
|
||||||
|
|
||||||
|
# Make directory to store uploaded files, if not exists
|
||||||
|
if not os.path.isdir('./pdf'):
|
||||||
|
os.mkdir('./pdf')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
try:
|
||||||
|
# Retrieve file from request
|
||||||
|
uploaded_file = request.files['file']
|
||||||
|
file_path = os.path.join(
|
||||||
|
app.config['UPLOAD_FOLDER'],
|
||||||
|
secure_filename(
|
||||||
|
uploaded_file.filename))
|
||||||
|
file_exten = uploaded_file.filename.rsplit('.', 1)[1].lower()
|
||||||
|
|
||||||
|
# Save uploaded file
|
||||||
|
uploaded_file.save(file_path)
|
||||||
|
# Get contents of file
|
||||||
|
uploaded_content = pdf2text(file_path, file_exten)
|
||||||
|
questions = txt2questions(uploaded_content)
|
||||||
|
|
||||||
|
# File upload + convert success
|
||||||
|
if uploaded_content is not None:
|
||||||
|
UPLOAD_STATUS = True
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return render_template(
|
||||||
|
'quiz.html',
|
||||||
|
uploaded=UPLOAD_STATUS,
|
||||||
|
questions=questions,
|
||||||
|
size=len(questions))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/result', methods=['POST', 'GET'])
|
||||||
|
def result():
|
||||||
|
correct_q = 0
|
||||||
|
for k, v in request.form.items():
|
||||||
|
correct_q += 1
|
||||||
|
return render_template('result.html', total=5, correct=correct_q)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True)
|
||||||
61
incorrect_answer_generation.py
Normal file
61
incorrect_answer_generation.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
''' This module contains the class
|
||||||
|
for generating incorrect alternative
|
||||||
|
answers for a given answer
|
||||||
|
'''
|
||||||
|
import gensim
|
||||||
|
import gensim.downloader as api
|
||||||
|
from gensim.models import Word2Vec
|
||||||
|
from nltk.tokenize import sent_tokenize, word_tokenize
|
||||||
|
import random
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
class IncorrectAnswerGenerator:
|
||||||
|
''' This class contains the methods
|
||||||
|
for generating the incorrect answers
|
||||||
|
given an answer
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, document):
|
||||||
|
# model required to fetch similar words
|
||||||
|
self.model = api.load("glove-wiki-gigaword-100")
|
||||||
|
self.all_words = []
|
||||||
|
for sent in sent_tokenize(document):
|
||||||
|
self.all_words.extend(word_tokenize(sent))
|
||||||
|
self.all_words = list(set(self.all_words))
|
||||||
|
|
||||||
|
def get_all_options_dict(self, answer, num_options):
|
||||||
|
''' This method returns a dict
|
||||||
|
of 'num_options' options out of
|
||||||
|
which one is correct and is the answer
|
||||||
|
'''
|
||||||
|
options_dict = dict()
|
||||||
|
try:
|
||||||
|
similar_words = self.model.similar_by_word(answer, topn=15)[::-1]
|
||||||
|
|
||||||
|
for i in range(1, num_options + 1):
|
||||||
|
options_dict[i] = similar_words[i - 1][0]
|
||||||
|
|
||||||
|
except BaseException:
|
||||||
|
self.all_sim = []
|
||||||
|
for word in self.all_words:
|
||||||
|
if word not in answer:
|
||||||
|
try:
|
||||||
|
self.all_sim.append(
|
||||||
|
(self.model.similarity(answer, word), word))
|
||||||
|
except BaseException:
|
||||||
|
self.all_sim.append(
|
||||||
|
(0.0, word))
|
||||||
|
else:
|
||||||
|
self.all_sim.append((-1.0, word))
|
||||||
|
|
||||||
|
self.all_sim.sort(reverse=True)
|
||||||
|
|
||||||
|
for i in range(1, num_options + 1):
|
||||||
|
options_dict[i] = self.all_sim[i - 1][1]
|
||||||
|
|
||||||
|
replacement_idx = random.randint(1, num_options)
|
||||||
|
|
||||||
|
options_dict[replacement_idx] = answer
|
||||||
|
|
||||||
|
return options_dict
|
||||||
199
question_extraction.py
Normal file
199
question_extraction.py
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
'''This file contains the module for generating
|
||||||
|
'''
|
||||||
|
import nltk
|
||||||
|
import spacy
|
||||||
|
from nltk.corpus import stopwords
|
||||||
|
from nltk.tokenize import sent_tokenize, word_tokenize
|
||||||
|
from sklearn.feature_extraction.text import TfidfVectorizer
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionExtractor:
|
||||||
|
''' This class contains all the methods
|
||||||
|
required for extracting questions from
|
||||||
|
a given document
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, num_questions):
|
||||||
|
|
||||||
|
self.num_questions = num_questions
|
||||||
|
|
||||||
|
# hash set for fast lookup
|
||||||
|
self.stop_words = set(stopwords.words('english'))
|
||||||
|
|
||||||
|
# named entity recognition tagger
|
||||||
|
self.ner_tagger = spacy.load('en_core_web_md')
|
||||||
|
|
||||||
|
self.vectorizer = TfidfVectorizer()
|
||||||
|
|
||||||
|
self.questions_dict = dict()
|
||||||
|
|
||||||
|
def get_questions_dict(self, document):
|
||||||
|
'''
|
||||||
|
Returns a dict of questions in the format:
|
||||||
|
question_number: {
|
||||||
|
question: str
|
||||||
|
answer: str
|
||||||
|
}
|
||||||
|
|
||||||
|
Params:
|
||||||
|
* document : string
|
||||||
|
Returns:
|
||||||
|
* dict
|
||||||
|
'''
|
||||||
|
# find candidate keywords
|
||||||
|
self.candidate_keywords = self.get_candidate_entities(document)
|
||||||
|
|
||||||
|
# set word scores before ranking candidate keywords
|
||||||
|
self.set_tfidf_scores(document)
|
||||||
|
|
||||||
|
# rank the keywords using calculated tf idf scores
|
||||||
|
self.rank_keywords()
|
||||||
|
|
||||||
|
# form the questions
|
||||||
|
self.form_questions()
|
||||||
|
|
||||||
|
return self.questions_dict
|
||||||
|
|
||||||
|
def get_filtered_sentences(self, document):
|
||||||
|
''' Returns a list of sentences - each of
|
||||||
|
which has been cleaned of stopwords.
|
||||||
|
Params:
|
||||||
|
* document: a paragraph of sentences
|
||||||
|
Returns:
|
||||||
|
* list<str> : list of string
|
||||||
|
'''
|
||||||
|
sentences = sent_tokenize(document) # split documents into sentences
|
||||||
|
|
||||||
|
return [self.filter_sentence(sentence) for sentence in sentences]
|
||||||
|
|
||||||
|
def filter_sentence(self, sentence):
|
||||||
|
'''Returns the sentence without stopwords
|
||||||
|
Params:
|
||||||
|
* sentence: A string
|
||||||
|
Returns:
|
||||||
|
* string
|
||||||
|
'''
|
||||||
|
words = word_tokenize(sentence)
|
||||||
|
return ' '.join(w for w in words if w not in self.stop_words)
|
||||||
|
|
||||||
|
def get_candidate_entities(self, document):
|
||||||
|
''' Returns a list of entities according to
|
||||||
|
spacy's ner tagger. These entities are candidates
|
||||||
|
for the questions
|
||||||
|
|
||||||
|
Params:
|
||||||
|
* document : string
|
||||||
|
Returns:
|
||||||
|
* list<str>
|
||||||
|
'''
|
||||||
|
entities = self.ner_tagger(document)
|
||||||
|
entity_list = []
|
||||||
|
|
||||||
|
for ent in entities.ents:
|
||||||
|
entity_list.append(ent.text)
|
||||||
|
|
||||||
|
return list(set(entity_list)) # remove duplicates
|
||||||
|
|
||||||
|
def set_tfidf_scores(self, document):
|
||||||
|
''' Sets the tf-idf scores for each word'''
|
||||||
|
self.unfiltered_sentences = sent_tokenize(document)
|
||||||
|
self.filtered_sentences = self.get_filtered_sentences(document)
|
||||||
|
|
||||||
|
self.word_score = dict() # (word, score)
|
||||||
|
|
||||||
|
# (word, sentence where word score is max)
|
||||||
|
self.sentence_for_max_word_score = dict()
|
||||||
|
|
||||||
|
tf_idf_vector = self.vectorizer.fit_transform(self.filtered_sentences)
|
||||||
|
feature_names = self.vectorizer.get_feature_names()
|
||||||
|
tf_idf_matrix = tf_idf_vector.todense().tolist()
|
||||||
|
|
||||||
|
num_sentences = len(self.unfiltered_sentences)
|
||||||
|
num_features = len(feature_names)
|
||||||
|
|
||||||
|
for i in range(num_features):
|
||||||
|
word = feature_names[i]
|
||||||
|
self.sentence_for_max_word_score[word] = ""
|
||||||
|
tot = 0.0
|
||||||
|
cur_max = 0.0
|
||||||
|
|
||||||
|
for j in range(num_sentences):
|
||||||
|
tot += tf_idf_matrix[j][i]
|
||||||
|
|
||||||
|
if tf_idf_matrix[j][i] > cur_max:
|
||||||
|
cur_max = tf_idf_matrix[j][i]
|
||||||
|
self.sentence_for_max_word_score[word] = self.unfiltered_sentences[j]
|
||||||
|
|
||||||
|
# average score for each word
|
||||||
|
self.word_score[word] = tot / num_sentences
|
||||||
|
|
||||||
|
def get_keyword_score(self, keyword):
|
||||||
|
''' Returns the score for a keyword
|
||||||
|
Params:
|
||||||
|
* keyword : string of possible several words
|
||||||
|
Returns:
|
||||||
|
* float : score
|
||||||
|
'''
|
||||||
|
score = 0.0
|
||||||
|
for word in word_tokenize(keyword):
|
||||||
|
if word in self.word_score:
|
||||||
|
score += self.word_score[word]
|
||||||
|
return score
|
||||||
|
|
||||||
|
def get_corresponding_sentence_for_keyword(self, keyword):
|
||||||
|
''' Finds and returns a sentence containing
|
||||||
|
the keywords
|
||||||
|
'''
|
||||||
|
words = word_tokenize(keyword)
|
||||||
|
for word in words:
|
||||||
|
|
||||||
|
if word not in self.sentence_for_max_word_score:
|
||||||
|
continue
|
||||||
|
|
||||||
|
sentence = self.sentence_for_max_word_score[word]
|
||||||
|
|
||||||
|
all_present = True
|
||||||
|
for w in words:
|
||||||
|
if w not in sentence:
|
||||||
|
all_present = False
|
||||||
|
|
||||||
|
if all_present:
|
||||||
|
return sentence
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def rank_keywords(self):
|
||||||
|
'''Rank keywords according to their score'''
|
||||||
|
self.candidate_triples = [] # (score, keyword, corresponding sentence)
|
||||||
|
|
||||||
|
for candidate_keyword in self.candidate_keywords:
|
||||||
|
self.candidate_triples.append([
|
||||||
|
self.get_keyword_score(candidate_keyword),
|
||||||
|
candidate_keyword,
|
||||||
|
self.get_corresponding_sentence_for_keyword(candidate_keyword)
|
||||||
|
])
|
||||||
|
|
||||||
|
self.candidate_triples.sort(reverse=True)
|
||||||
|
|
||||||
|
def form_questions(self):
|
||||||
|
''' Forms the question and populates
|
||||||
|
the question dict
|
||||||
|
'''
|
||||||
|
used_sentences = list()
|
||||||
|
idx = 0
|
||||||
|
cntr = 1
|
||||||
|
num_candidates = len(self.candidate_triples)
|
||||||
|
while cntr <= self.num_questions and idx < num_candidates:
|
||||||
|
candidate_triple = self.candidate_triples[idx]
|
||||||
|
|
||||||
|
if candidate_triple[2] not in used_sentences:
|
||||||
|
used_sentences.append(candidate_triple[2])
|
||||||
|
|
||||||
|
self.questions_dict[cntr] = {
|
||||||
|
"question": candidate_triple[2].replace(
|
||||||
|
candidate_triple[1],
|
||||||
|
'_' * len(candidate_triple[1])),
|
||||||
|
"answer": candidate_triple[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
cntr += 1
|
||||||
|
idx += 1
|
||||||
56
question_generation_main.py
Normal file
56
question_generation_main.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
'''This module ties together the
|
||||||
|
questions generation and incorrect answer
|
||||||
|
generation modules
|
||||||
|
'''
|
||||||
|
from question_extraction import QuestionExtractor
|
||||||
|
from incorrect_answer_generation import IncorrectAnswerGenerator
|
||||||
|
import re
|
||||||
|
from nltk import sent_tokenize
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionGeneration:
|
||||||
|
'''This class contains the method
|
||||||
|
to generate questions
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, num_questions, num_options):
|
||||||
|
self.num_questions = num_questions
|
||||||
|
self.num_options = num_options
|
||||||
|
self.question_extractor = QuestionExtractor(num_questions)
|
||||||
|
|
||||||
|
def clean_text(self, text):
|
||||||
|
text = text.replace('\n', ' ') # remove newline chars
|
||||||
|
sentences = sent_tokenize(text)
|
||||||
|
cleaned_text = ""
|
||||||
|
for sentence in sentences:
|
||||||
|
# remove non alphanumeric chars
|
||||||
|
cleaned_sentence = re.sub(r'([^\s\w]|_)+', '', sentence)
|
||||||
|
|
||||||
|
# substitute multiple spaces with single space
|
||||||
|
cleaned_sentence = re.sub(' +', ' ', cleaned_sentence)
|
||||||
|
cleaned_text += cleaned_sentence
|
||||||
|
|
||||||
|
if cleaned_text[-1] == ' ':
|
||||||
|
cleaned_text[-1] = '.'
|
||||||
|
else:
|
||||||
|
cleaned_text += '.'
|
||||||
|
|
||||||
|
cleaned_text += ' ' # pad with space at end
|
||||||
|
return cleaned_text
|
||||||
|
|
||||||
|
def generate_questions_dict(self, document):
|
||||||
|
document = self.clean_text(document)
|
||||||
|
self.questions_dict = self.question_extractor.get_questions_dict(
|
||||||
|
document)
|
||||||
|
self.incorrect_answer_generator = IncorrectAnswerGenerator(document)
|
||||||
|
|
||||||
|
for i in range(1, self.num_questions + 1):
|
||||||
|
if i not in self.questions_dict:
|
||||||
|
continue
|
||||||
|
self.questions_dict[i]["options"] \
|
||||||
|
= self.incorrect_answer_generator.get_all_options_dict(
|
||||||
|
self.questions_dict[i]["answer"],
|
||||||
|
self.num_options
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.questions_dict
|
||||||
39
requirements.txt
Normal file
39
requirements.txt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
blis==0.4.1
|
||||||
|
catalogue==1.0.0
|
||||||
|
certifi==2020.6.20
|
||||||
|
chardet==3.0.4
|
||||||
|
click==7.1.2
|
||||||
|
cymem==2.0.3
|
||||||
|
en-core-web-md @ https://github.com/explosion/spacy-models/releases/download/en_core_web_md-2.3.1/en_core_web_md-2.3.1.tar.gz
|
||||||
|
en-core-web-sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.3.1/en_core_web_sm-2.3.1.tar.gz
|
||||||
|
Flask==1.1.2
|
||||||
|
gensim==3.8.3
|
||||||
|
idna==2.10
|
||||||
|
importlib-metadata==2.0.0
|
||||||
|
itsdangerous==1.1.0
|
||||||
|
Jinja2==2.11.2
|
||||||
|
joblib==0.17.0
|
||||||
|
MarkupSafe==1.1.1
|
||||||
|
murmurhash==1.0.2
|
||||||
|
nltk==3.5
|
||||||
|
numpy==1.19.2
|
||||||
|
pkg-resources==0.0.0
|
||||||
|
plac==1.1.3
|
||||||
|
preshed==3.0.2
|
||||||
|
PyPDF2==1.26.0
|
||||||
|
regex==2020.9.27
|
||||||
|
requests==2.24.0
|
||||||
|
scikit-learn==0.23.2
|
||||||
|
scipy==1.5.2
|
||||||
|
six==1.15.0
|
||||||
|
sklearn==0.0
|
||||||
|
smart-open==3.0.0
|
||||||
|
spacy==2.3.2
|
||||||
|
srsly==1.0.2
|
||||||
|
thinc==7.4.1
|
||||||
|
threadpoolctl==2.1.0
|
||||||
|
tqdm==4.50.2
|
||||||
|
urllib3==1.25.10
|
||||||
|
wasabi==0.8.0
|
||||||
|
Werkzeug==1.0.1
|
||||||
|
zipp==3.3.0
|
||||||
105
static/css/quiz.css
Normal file
105
static/css/quiz.css
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
@import url("https://fonts.googleapis.com/css2?family=Open+Sans&display=swap");
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
position: relative;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
user-select: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
counter-reset: points;
|
||||||
|
background-color: #77aa77;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 2 1'%3E%3Cdefs%3E%3ClinearGradient id='a' gradientUnits='userSpaceOnUse' x1='0' x2='0' y1='0' y2='1'%3E%3Cstop offset='0' stop-color='%2377aa77'/%3E%3Cstop offset='1' stop-color='%234fd'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' gradientUnits='userSpaceOnUse' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23cf8' stop-opacity='0'/%3E%3Cstop offset='1' stop-color='%23cf8' stop-opacity='1'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' gradientUnits='userSpaceOnUse' x1='0' y1='0' x2='2' y2='2'%3E%3Cstop offset='0' stop-color='%23cf8' stop-opacity='0'/%3E%3Cstop offset='1' stop-color='%23cf8' stop-opacity='1'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect x='0' y='0' fill='url(%23a)' width='2' height='1'/%3E%3Cg fill-opacity='0.5'%3E%3Cpolygon fill='url(%23b)' points='0 1 0 0 2 0'/%3E%3Cpolygon fill='url(%23c)' points='2 1 2 0 0 0'/%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
background-attachment: fixed;
|
||||||
|
background-size: cover;
|
||||||
|
font-family: "helvetica", sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding-top: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
-webkit-box-shadow: 0 10px 6px -6px #777;
|
||||||
|
-moz-box-shadow: 0 10px 6px -6px #777;
|
||||||
|
box-shadow: 0 10px 6px -6px #777;
|
||||||
|
color: #000000;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 50px 40px 50px;
|
||||||
|
width: 95%;
|
||||||
|
max-width: 590px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] + label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] + label:hover {
|
||||||
|
border: 1px solid #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"]:checked + label {
|
||||||
|
background-image: none;
|
||||||
|
background-color: #0c0;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #0c0 !important;
|
||||||
|
-webkit-transition: all 0.2s ease-in-out;
|
||||||
|
-moz-transition: all 0.2s ease-in-out;
|
||||||
|
-o-transition: all 0.2s ease-in-out;
|
||||||
|
-ms-transition: all 0.2s ease-in-out;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worngans {
|
||||||
|
background-color: #f36;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #f36 !important;
|
||||||
|
-webkit-transition: all 0.2s ease-in-out;
|
||||||
|
-moz-transition: all 0.2s ease-in-out;
|
||||||
|
-o-transition: all 0.2s ease-in-out;
|
||||||
|
-ms-transition: all 0.2s ease-in-out;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.end {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.is-fullwidth {
|
||||||
|
display: flex;
|
||||||
|
width: 50%;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
57
static/css/results.css
Normal file
57
static/css/results.css
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
@import url("https://fonts.googleapis.com/css2?family=Open+Sans&display=swap");
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
position: relative;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
user-select: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
counter-reset: points;
|
||||||
|
background-color: #77aa77;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 2 1'%3E%3Cdefs%3E%3ClinearGradient id='a' gradientUnits='userSpaceOnUse' x1='0' x2='0' y1='0' y2='1'%3E%3Cstop offset='0' stop-color='%2377aa77'/%3E%3Cstop offset='1' stop-color='%234fd'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' gradientUnits='userSpaceOnUse' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23cf8' stop-opacity='0'/%3E%3Cstop offset='1' stop-color='%23cf8' stop-opacity='1'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' gradientUnits='userSpaceOnUse' x1='0' y1='0' x2='2' y2='2'%3E%3Cstop offset='0' stop-color='%23cf8' stop-opacity='0'/%3E%3Cstop offset='1' stop-color='%23cf8' stop-opacity='1'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect x='0' y='0' fill='url(%23a)' width='2' height='1'/%3E%3Cg fill-opacity='0.5'%3E%3Cpolygon fill='url(%23b)' points='0 1 0 0 2 0'/%3E%3Cpolygon fill='url(%23c)' points='2 1 2 0 0 0'/%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
background-attachment: fixed;
|
||||||
|
background-size: cover;
|
||||||
|
font-family: "helvetica", sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding-top: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
-webkit-box-shadow: 0 10px 6px -6px #777;
|
||||||
|
-moz-box-shadow: 0 10px 6px -6px #777;
|
||||||
|
box-shadow: 0 10px 6px -6px #777;
|
||||||
|
color: #000000;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 30px 40px 30px;
|
||||||
|
width: 95%;
|
||||||
|
max-width: 590px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.is-fullwidth {
|
||||||
|
display: flex;
|
||||||
|
width: 90%;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
173
static/css/style.css
Normal file
173
static/css/style.css
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
@import url("https://fonts.googleapis.com/css2?family=Open+Sans&display=swap");
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #77aa77;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 2 1'%3E%3Cdefs%3E%3ClinearGradient id='a' gradientUnits='userSpaceOnUse' x1='0' x2='0' y1='0' y2='1'%3E%3Cstop offset='0' stop-color='%2377aa77'/%3E%3Cstop offset='1' stop-color='%234fd'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' gradientUnits='userSpaceOnUse' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23cf8' stop-opacity='0'/%3E%3Cstop offset='1' stop-color='%23cf8' stop-opacity='1'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' gradientUnits='userSpaceOnUse' x1='0' y1='0' x2='2' y2='2'%3E%3Cstop offset='0' stop-color='%23cf8' stop-opacity='0'/%3E%3Cstop offset='1' stop-color='%23cf8' stop-opacity='1'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect x='0' y='0' fill='url(%23a)' width='2' height='1'/%3E%3Cg fill-opacity='0.5'%3E%3Cpolygon fill='url(%23b)' points='0 1 0 0 2 0'/%3E%3Cpolygon fill='url(%23c)' points='2 1 2 0 0 0'/%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
background-attachment: fixed;
|
||||||
|
background-size: cover;
|
||||||
|
font-family: "helvetica", sans-serif !important;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-top: 50px !important;
|
||||||
|
height: 350px !important;
|
||||||
|
width: 250px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.has-text-centered .card-header,
|
||||||
|
.card.has-text-centered .card-content,
|
||||||
|
.card.has-text-centered .card-footer {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.has-text-centered h1 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
margin-bottom: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 12.5rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1.5rem 3.125rem;
|
||||||
|
background-color: #3498db;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.3125rem;
|
||||||
|
box-shadow: 0 12px 24px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
color: white;
|
||||||
|
font-weight: 400;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: "Open Sans", sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #54d98c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button span {
|
||||||
|
position: absolute;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button span i {
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button span:nth-of-type(1) {
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button span:nth-of-type(2) {
|
||||||
|
top: 100%;
|
||||||
|
transform: translateY(0%);
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button span:nth-of-type(3) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: #2ecc71;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active:before {
|
||||||
|
width: 100%;
|
||||||
|
transition: width 30s linear !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active span:nth-of-type(1) {
|
||||||
|
top: -100%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active span:nth-of-type(2) {
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active span:nth-of-type(2) i {
|
||||||
|
animation: loading 10000ms linear infinite !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active span:nth-of-type(3) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finished {
|
||||||
|
background-color: #54d98c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finished .submit {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finished .loading {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finished .check {
|
||||||
|
display: block !important;
|
||||||
|
font-size: 24px;
|
||||||
|
animation: scale 0.5s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finished .check i {
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scale {
|
||||||
|
0% {
|
||||||
|
transform: scale(10);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(0.2);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
transform: scale(0.7);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
margin-top: 70px !important;
|
||||||
|
}
|
||||||
BIN
static/fonts/helvetica.ttf
Normal file
BIN
static/fonts/helvetica.ttf
Normal file
Binary file not shown.
97
templates/index.html
Normal file
97
templates/index.html
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static',filename='css/style.css') }}" />
|
||||||
|
<link rel="shortcut icon" href="https://avatars0.githubusercontent.com/u/65834464?s=200&v=4" type="image/x-icon">
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.3/gh-fork-ribbon.min.css" />
|
||||||
|
<script src="https://kit.fontawesome.com/22be60108b.js" crossorigin="anonymous"></script>
|
||||||
|
<title>MLH Quizzet</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<style type="text/css">
|
||||||
|
@font-face {
|
||||||
|
font-family: helvetica;
|
||||||
|
src: "{{ url_for('static', filename='fonts/helvetica.ttf')}}"
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<nav class="navbar is-dark is-fixed-top has-text-centered" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<img src="https://avatars0.githubusercontent.com/u/65834464?s=200&v=4" height="32" width="64"
|
||||||
|
style="margin: 10px">
|
||||||
|
<a class="navbar-item has-text-centered" href="{{ url_for('index') }}"><strong
|
||||||
|
class="is-size-3 has-text-centered">MLH Quizzet</strong></a>
|
||||||
|
<a class="github-fork-ribbon" href="https://github.com/PragatiVerma18/Fantastic-Falcons-1.0"
|
||||||
|
data-ribbon="Fork me on GitHub" title="Fork me on GitHub">Fork me on GitHub</a>
|
||||||
|
<div class="navbar-burger" data-target="navMenu">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<main role="main">
|
||||||
|
<div class="hero">
|
||||||
|
<p class="is-size-3 has-text-black has-text-weight-bold has-text-centered">Upload any Document to get an instant Quiz</p>
|
||||||
|
<p class="is-size-4 has-text-weight-bold has-text-centered">You Know 📖, You Grow 🚀</p>
|
||||||
|
<p class="is-size-5 has-text-black has-text-weight-medium has-text-centered"> Practice More • Learn More </p>
|
||||||
|
</div>
|
||||||
|
<div class="card has-text-centered">
|
||||||
|
<img src="https://cdn4.iconfinder.com/data/icons/files-and-folders-thinline-icons-set/144/File_PDF-512.png"
|
||||||
|
alt="upload"/>
|
||||||
|
<form action="http://localhost:5000/quiz" method="POST" enctype="multipart/form-data">
|
||||||
|
<div id="file-js-example" class="file has-name is-fullwidth">
|
||||||
|
<label class="file-label">
|
||||||
|
<input class="file-input" type="file" name="file" accept=".txt, application/pdf" />
|
||||||
|
<span class="file-cta">
|
||||||
|
<span class="file-icon">
|
||||||
|
<i class="fas fa-upload"></i>
|
||||||
|
</span>
|
||||||
|
<span class="file-label"> Choose a file… </span>
|
||||||
|
</span>
|
||||||
|
<span class="file-name"> No file uploaded </span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const fileInput = document.querySelector(
|
||||||
|
"#file-js-example input[type=file]"
|
||||||
|
);
|
||||||
|
fileInput.onchange = () => {
|
||||||
|
if (fileInput.files.length > 0) {
|
||||||
|
const fileName = document.querySelector(
|
||||||
|
"#file-js-example .file-name"
|
||||||
|
);
|
||||||
|
fileName.textContent = fileInput.files[0].name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<button class="button has-text-centered is-large is-fullwidth is-dark">
|
||||||
|
<span class="submit has-text-centered" type="submit" name="upload file">Submit</span>
|
||||||
|
<span class="loading"><i class="fa fa-refresh"></i></span>
|
||||||
|
<span class="check"><i class="fa fa-check"></i></span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script>const button = document.querySelector('.button');
|
||||||
|
const submit = document.querySelector('.submit');
|
||||||
|
|
||||||
|
function toggleClass() {
|
||||||
|
this.classList.toggle('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function addClass() {
|
||||||
|
this.classList.add('finished');
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addEventListener('click', toggleClass);
|
||||||
|
button.addEventListener('transitionend', toggleClass);
|
||||||
|
button.addEventListener('transitionend', addClass);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
87
templates/quiz.html
Normal file
87
templates/quiz.html
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static',filename='css/quiz.css') }}" />
|
||||||
|
<link rel="shortcut icon" href="https://avatars0.githubusercontent.com/u/65834464?s=200&v=4"
|
||||||
|
type="image/x-icon">
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.3/gh-fork-ribbon.min.css" />
|
||||||
|
<title>MLH Quizzet</title>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js" type='text/javascript'></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('label').click(function () {
|
||||||
|
$('label').removeClass('worngans');
|
||||||
|
$(this).addClass('worngans');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<style type="text/css">
|
||||||
|
@font-face {
|
||||||
|
font-family: helvetica;
|
||||||
|
src: "{{ url_for('static', filename='fonts/helvetica.ttf')}}"
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<nav class="navbar is-dark is-fixed-top has-text-centered" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<img src="https://avatars0.githubusercontent.com/u/65834464?s=200&v=4" height="32" width="64"
|
||||||
|
style="margin: 7px">
|
||||||
|
<a class="navbar-item has-text-centered" href="{{ url_for('index') }}"><strong
|
||||||
|
class="is-size-3 has-text-centered">MLH
|
||||||
|
Quizzet</strong></a>
|
||||||
|
<a class="github-fork-ribbon" href="https://github.com/PragatiVerma18/Fantastic-Falcons-1.0"
|
||||||
|
data-ribbon="Fork me on GitHub" title="Fork me on GitHub">Fork me on GitHub</a>
|
||||||
|
<div class="navbar-burger" data-target="navMenu">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
{% if uploaded == true %}
|
||||||
|
<form action="http://localhost:5000/result" method="POST">
|
||||||
|
{% for i in range(size) %}
|
||||||
|
<section class="section-1" id="section-1">
|
||||||
|
<main>
|
||||||
|
<div class="scp-quizzes-main">
|
||||||
|
<div class="scp-quizzes-data">
|
||||||
|
<h3 class="is-size-6 has-text-weight-bold">{{ i+1 }}. {{ questions[i+1]['question'] }}</h3>
|
||||||
|
<br />
|
||||||
|
{% for op in questions[i+1]['options'] %}
|
||||||
|
{% if op == questions[i+1]['answer'] %}
|
||||||
|
<input type="radio" id="{{ questions[i+1]['answer'] }}" name="question{{ i+1 }}">
|
||||||
|
<label for="{{ questions[i+1]['answer'] }}">
|
||||||
|
{{ op }}</label><br />
|
||||||
|
{% else %}
|
||||||
|
<input type="radio" name="question{{ i+1 }}">
|
||||||
|
<label> {{ op }}</label><br />
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</section>
|
||||||
|
{% endfor %}
|
||||||
|
<div class="end">
|
||||||
|
<button type="submit" class="button is-dark has-text-weight-bold has-text-centered is-fullwidth is-rounded">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<section class="section-1" id="section-1">
|
||||||
|
<h1>Could not upload file</h1>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
<div class="has-text-white has-text-centered" style="margin-top: 50px; background-color: #363636; padding: 10px;">
|
||||||
|
MIT License © Copyright 2020 Fantastic Falcons
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
54
templates/result.html
Normal file
54
templates/result.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static',filename='css/results.css') }}" />
|
||||||
|
<link rel="shortcut icon" href="https://avatars0.githubusercontent.com/u/65834464?s=200&v=4"
|
||||||
|
type="image/x-icon">
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.3/gh-fork-ribbon.min.css" />
|
||||||
|
<script src="https://kit.fontawesome.com/22be60108b.js" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js"></script>
|
||||||
|
<title>MLH Quizzet</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<style type="text/css">
|
||||||
|
@font-face {
|
||||||
|
font-family: helvetica;
|
||||||
|
src: "{{ url_for('static', filename='fonts/helvetica.ttf')}}"
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<nav class="navbar is-dark is-fixed-top has-text-centered" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<img src="https://avatars0.githubusercontent.com/u/65834464?s=200&v=4" height="32" width="64"
|
||||||
|
style="margin: 10px">
|
||||||
|
<a class="navbar-item has-text-centered" href="{{ url_for('index') }}"><strong
|
||||||
|
class="is-size-3 has-text-centered">MLH Quizzet</strong></a>
|
||||||
|
<a class="github-fork-ribbon" href="https://github.com/PragatiVerma18/Fantastic-Falcons-1.0"
|
||||||
|
data-ribbon="Fork me on GitHub" title="Fork me on GitHub">Fork me on GitHub</a>
|
||||||
|
<div class="navbar-burger" data-target="navMenu">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="section-1" id="section-1">
|
||||||
|
<main>
|
||||||
|
<lottie-player src="https://assets3.lottiefiles.com/packages/lf20_0ge4xP.json" background="transparent" speed="1"
|
||||||
|
style="width: 240px; height: 240px; margin: auto !important;" loop autoplay></lottie-player>
|
||||||
|
<h1 class="has-text-centered is-size-3 has-text-weight-bold"> You got {{ correct }}/{{ total }} right!</h1>
|
||||||
|
<a class="button is-dark has-text-centered has-text-weight-bold is-rounded is-fullwidth" href="{{ url_for('index') }}"> Upload another document</a>
|
||||||
|
</main>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="has-text-white has-text-centered" style="margin-top: 40px; background-color: #363636; padding: 10px;">
|
||||||
|
MIT License © Copyright 2020 Fantastic Falcons
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
38
workers.py
Normal file
38
workers.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from PyPDF2 import PdfFileReader
|
||||||
|
from question_generation_main import QuestionGeneration
|
||||||
|
|
||||||
|
|
||||||
|
def pdf2text(file_path: str, file_exten: str) -> str:
|
||||||
|
""" Converts a given file to text content """
|
||||||
|
|
||||||
|
_content = ''
|
||||||
|
|
||||||
|
# Identify file type and get its contents
|
||||||
|
if file_exten == 'pdf':
|
||||||
|
with open(file_path, 'rb') as pdf_file:
|
||||||
|
_pdf_reader = PdfFileReader(pdf_file)
|
||||||
|
for p in range(_pdf_reader.numPages):
|
||||||
|
_content += _pdf_reader.getPage(p).extractText()
|
||||||
|
# _content = _pdf_reader.getPage(0).extractText()
|
||||||
|
print('PDF operation done!')
|
||||||
|
|
||||||
|
elif file_exten == 'txt':
|
||||||
|
with open(file_path, 'r') as txt_file:
|
||||||
|
_content = txt_file.read()
|
||||||
|
print('TXT operation done!')
|
||||||
|
|
||||||
|
return _content
|
||||||
|
|
||||||
|
|
||||||
|
def txt2questions(doc: str, n=5, o=4) -> dict:
|
||||||
|
""" Get all questions and options """
|
||||||
|
|
||||||
|
qGen = QuestionGeneration(n, o)
|
||||||
|
q = qGen.generate_questions_dict(doc)
|
||||||
|
for i in range(len(q)):
|
||||||
|
temp = []
|
||||||
|
for j in range(len(q[i + 1]['options'])):
|
||||||
|
temp.append(q[i + 1]['options'][j + 1])
|
||||||
|
# print(temp)
|
||||||
|
q[i + 1]['options'] = temp
|
||||||
|
return q
|
||||||
Loading…
x
Reference in New Issue
Block a user