upload files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- Dockerfile +12 -0
- Longformer_checkpoint/__init__.py +0 -0
- Longformer_checkpoint/config.json +56 -0
- __init__.py +0 -0
- app.py +6 -0
- configs.py +3 -0
- data/__init__.py +0 -0
- data/dataset.py +26 -0
- inference/__init__.py +0 -0
- inference/__pycache__/__init__.cpython-312.pyc +0 -0
- inference/__pycache__/infer_single.cpython-312.pyc +0 -0
- inference/infer_single.py +85 -0
- init.sh +1 -0
- instance/users.db +0 -0
- models/__init__.py +0 -0
- models/__pycache__/__init__.cpython-312.pyc +0 -0
- models/__pycache__/model.cpython-312.pyc +0 -0
- models/model.py +144 -0
- my_app/__init__.py +63 -0
- my_app/__pycache__/__init__.cpython-312.pyc +0 -0
- my_app/__pycache__/database.cpython-312.pyc +0 -0
- my_app/database.py +28 -0
- my_app/static/js/dashboard_charts.js +191 -0
- my_app/static/js/home_page.js +65 -0
- my_app/static/js/particles_init.js +55 -0
- my_app/static/styles.css +347 -0
- my_app/templates/403.html +14 -0
- my_app/templates/about_us.html +65 -0
- my_app/templates/base.html +75 -0
- my_app/templates/coherence_cohesion.html +106 -0
- my_app/templates/dashboard.html +111 -0
- my_app/templates/error.html +13 -0
- my_app/templates/grammatical_range_accuracy.html +104 -0
- my_app/templates/index.html +67 -0
- my_app/templates/infer.html +181 -0
- my_app/templates/lexical_resource.html +104 -0
- my_app/templates/login.html +49 -0
- my_app/templates/register.html +77 -0
- my_app/templates/rubric_explanation.html +28 -0
- my_app/templates/task_response.html +104 -0
- my_app/views/__init__.py +9 -0
- my_app/views/__pycache__/__init__.cpython-310.pyc +0 -0
- my_app/views/__pycache__/__init__.cpython-311.pyc +0 -0
- my_app/views/__pycache__/__init__.cpython-312.pyc +0 -0
- my_app/views/__pycache__/about.cpython-311.pyc +0 -0
- my_app/views/__pycache__/about.cpython-312.pyc +0 -0
- my_app/views/__pycache__/auth.cpython-310.pyc +0 -0
- my_app/views/__pycache__/auth.cpython-311.pyc +0 -0
- my_app/views/__pycache__/auth.cpython-312.pyc +0 -0
- my_app/views/__pycache__/dashboard.cpython-310.pyc +0 -0
Dockerfile
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.12
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
EXPOSE 3478
|
| 11 |
+
|
| 12 |
+
CMD ["python", "app.py"]
|
Longformer_checkpoint/__init__.py
ADDED
|
File without changes
|
Longformer_checkpoint/config.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"CustomLongformerForSequenceClassification"
|
| 4 |
+
],
|
| 5 |
+
"attention_mode": "longformer",
|
| 6 |
+
"attention_probs_dropout_prob": 0.1,
|
| 7 |
+
"attention_window": [
|
| 8 |
+
512,
|
| 9 |
+
512,
|
| 10 |
+
512,
|
| 11 |
+
512,
|
| 12 |
+
512,
|
| 13 |
+
512,
|
| 14 |
+
512,
|
| 15 |
+
512,
|
| 16 |
+
512,
|
| 17 |
+
512,
|
| 18 |
+
512,
|
| 19 |
+
512
|
| 20 |
+
],
|
| 21 |
+
"bos_token_id": 0,
|
| 22 |
+
"eos_token_id": 2,
|
| 23 |
+
"gradient_checkpointing": false,
|
| 24 |
+
"hidden_act": "gelu",
|
| 25 |
+
"hidden_dropout_prob": 0.1,
|
| 26 |
+
"hidden_size": 768,
|
| 27 |
+
"id2label": {
|
| 28 |
+
"0": "LABEL_0",
|
| 29 |
+
"1": "LABEL_1",
|
| 30 |
+
"2": "LABEL_2",
|
| 31 |
+
"3": "LABEL_3"
|
| 32 |
+
},
|
| 33 |
+
"ignore_attention_mask": false,
|
| 34 |
+
"initializer_range": 0.02,
|
| 35 |
+
"intermediate_size": 3072,
|
| 36 |
+
"label2id": {
|
| 37 |
+
"LABEL_0": 0,
|
| 38 |
+
"LABEL_1": 1,
|
| 39 |
+
"LABEL_2": 2,
|
| 40 |
+
"LABEL_3": 3
|
| 41 |
+
},
|
| 42 |
+
"layer_norm_eps": 1e-05,
|
| 43 |
+
"max_position_embeddings": 4098,
|
| 44 |
+
"model_type": "longformer",
|
| 45 |
+
"num_attention_heads": 12,
|
| 46 |
+
"num_hidden_layers": 12,
|
| 47 |
+
"onnx_export": false,
|
| 48 |
+
"output_hidden_states": true,
|
| 49 |
+
"pad_token_id": 1,
|
| 50 |
+
"problem_type": "regression",
|
| 51 |
+
"sep_token_id": 2,
|
| 52 |
+
"torch_dtype": "float32",
|
| 53 |
+
"transformers_version": "4.51.3",
|
| 54 |
+
"type_vocab_size": 1,
|
| 55 |
+
"vocab_size": 50265
|
| 56 |
+
}
|
__init__.py
ADDED
|
File without changes
|
app.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from my_app import create_app
|
| 2 |
+
|
| 3 |
+
app = create_app()
|
| 4 |
+
|
| 5 |
+
if __name__ == '__main__':
|
| 6 |
+
app.run(debug=True,host='0.0.0.0')
|
configs.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
SECRET_KEY = 'secret_key_here'
|
| 2 |
+
SQLALCHEMY_DATABASE_URI = 'sqlite:///users.db'
|
| 3 |
+
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
data/__init__.py
ADDED
|
File without changes
|
data/dataset.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
class EssayDataset(torch.utils.data.Dataset):
|
| 3 |
+
def __init__(self, dataframe, tokenizer, max_length):
|
| 4 |
+
self.data = dataframe
|
| 5 |
+
self.tokenizer = tokenizer
|
| 6 |
+
self.max_length = max_length
|
| 7 |
+
|
| 8 |
+
def __len__(self):
|
| 9 |
+
return len(self.data)
|
| 10 |
+
|
| 11 |
+
def __getitem__(self, idx):
|
| 12 |
+
text = self.data.iloc[idx]['train_input']
|
| 13 |
+
labels = self.data.iloc[idx]['labels']
|
| 14 |
+
encoding = self.tokenizer(
|
| 15 |
+
text,
|
| 16 |
+
max_length=self.max_length,
|
| 17 |
+
padding='max_length',
|
| 18 |
+
truncation=True,
|
| 19 |
+
return_tensors='pt'
|
| 20 |
+
)
|
| 21 |
+
return {
|
| 22 |
+
'input_ids': encoding['input_ids'].flatten(),
|
| 23 |
+
'attention_mask': encoding['attention_mask'].flatten(),
|
| 24 |
+
'labels': torch.tensor(labels, dtype=torch.float)
|
| 25 |
+
}
|
| 26 |
+
|
inference/__init__.py
ADDED
|
File without changes
|
inference/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (155 Bytes). View file
|
|
|
inference/__pycache__/infer_single.cpython-312.pyc
ADDED
|
Binary file (4.98 kB). View file
|
|
|
inference/infer_single.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from utils.data_utils import *
|
| 2 |
+
from utils.prompts import *
|
| 3 |
+
import torch
|
| 4 |
+
from my_app import *
|
| 5 |
+
|
| 6 |
+
def replace_single_newlines(text):
|
| 7 |
+
return re.sub(r'(?<!\n)\n(?!\n)', '\\\\n\\\\n', text)
|
| 8 |
+
|
| 9 |
+
def generate_full_prompt(topic, essay, cefr_stat):
|
| 10 |
+
essay = replace_single_newlines(essay)
|
| 11 |
+
paragraph_cnt = len(essay.replace('\\n\\n', '\\n').split('\\n'))
|
| 12 |
+
word_cnt = len(essay.split())
|
| 13 |
+
stat = f"The essay has {word_cnt} words and {paragraph_cnt} paragraphs.\n"
|
| 14 |
+
full_prompt = feedback_prompt_1 + "\n ##{{PROMPT}}\n```\n" + topic + "\n```\n ##{{ESSAY}}\n```\n" + essay + "\n```\n##CEFR Analysis\n" + str(cefr_stat) + "\n\n##{{STATS}}\n" + stat + '\n' + feedback_prompt_2
|
| 15 |
+
return full_prompt
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def generate_and_score_essay(topic, essay):
|
| 19 |
+
global MODELS_LOADED, LONGFORMER_TOKENIZER, LONGFORMER_MODEL, QWEN_TOKENIZER, QWEN_MODEL
|
| 20 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 21 |
+
LONGFORMER_MODEL = LONGFORMER_MODEL.to(device)
|
| 22 |
+
QWEN_MODEL = QWEN_MODEL.to(device)
|
| 23 |
+
|
| 24 |
+
cefr_results = get_cefr_stats(essay)
|
| 25 |
+
full_prompt = generate_full_prompt(topic=topic, essay=essay, cefr_stat=cefr_results)
|
| 26 |
+
essay = replace_single_newlines(essay)
|
| 27 |
+
paragraph_cnt = len(essay.replace('\\n\\n', '\\n').split('\\n'))
|
| 28 |
+
text = QWEN_TOKENIZER.apply_chat_template(
|
| 29 |
+
[{"role": "user", "content": full_prompt}],
|
| 30 |
+
tokenize=False,
|
| 31 |
+
add_generation_prompt=True,
|
| 32 |
+
enable_thinking=False
|
| 33 |
+
)
|
| 34 |
+
inputs = QWEN_TOKENIZER(
|
| 35 |
+
text,
|
| 36 |
+
return_tensors="pt",
|
| 37 |
+
padding=True,
|
| 38 |
+
truncation=True,
|
| 39 |
+
padding_side='left'
|
| 40 |
+
).to(device)
|
| 41 |
+
with torch.inference_mode():
|
| 42 |
+
outputs = QWEN_MODEL.generate(
|
| 43 |
+
**inputs,
|
| 44 |
+
max_new_tokens=1500,
|
| 45 |
+
use_cache=True,
|
| 46 |
+
pad_token_id=QWEN_TOKENIZER.eos_token_id
|
| 47 |
+
)
|
| 48 |
+
generated_ids = outputs[0][inputs.input_ids.shape[1]:]
|
| 49 |
+
full_feedback = QWEN_TOKENIZER.decode(
|
| 50 |
+
generated_ids,
|
| 51 |
+
skip_special_tokens=True,
|
| 52 |
+
clean_up_tokenization_spaces=True # Fix spaces/newlines
|
| 53 |
+
)
|
| 54 |
+
output_match = re.search(r"{(.*?)}", full_feedback, re.DOTALL)
|
| 55 |
+
response = output_match.group(1).strip() if output_match else full_feedback
|
| 56 |
+
feedback_components = extract_feedback_keys_values(response)
|
| 57 |
+
feedback_components = dict(feedback_components)
|
| 58 |
+
feedback_components['word_count'] = len(essay.split())
|
| 59 |
+
feedback_components['paragraph_count'] = paragraph_cnt
|
| 60 |
+
feedback_components['cefr_stat'] = cefr_results
|
| 61 |
+
score_input = create_train_input({
|
| 62 |
+
'topic': topic,
|
| 63 |
+
'essay': essay,
|
| 64 |
+
'Corrected_essay': feedback_components.get('Corrected_essay', ''),
|
| 65 |
+
'TR_feedback': feedback_components.get('TR_feedback', ''),
|
| 66 |
+
'CC_feedback': feedback_components.get('CC_feedback', ''),
|
| 67 |
+
'LR_feedback': feedback_components.get('LR_feedback', ''),
|
| 68 |
+
'GRA_feedback': feedback_components.get('GRA_feedback', ''),
|
| 69 |
+
'word_count':feedback_components.get('word_count', ''),
|
| 70 |
+
'paragraph_count': feedback_components.get('paragraph_count', ''),
|
| 71 |
+
'cefr_stat': feedback_components.get('cefr_stat', '')
|
| 72 |
+
})
|
| 73 |
+
score_inputs = LONGFORMER_TOKENIZER(
|
| 74 |
+
score_input,
|
| 75 |
+
return_tensors="pt",
|
| 76 |
+
max_length=2048,
|
| 77 |
+
truncation=True,
|
| 78 |
+
padding=True
|
| 79 |
+
).to(device)
|
| 80 |
+
LONGFORMER_MODEL.eval()
|
| 81 |
+
with torch.no_grad():
|
| 82 |
+
outputs = LONGFORMER_MODEL(**score_inputs) # Get full outputs dictionary
|
| 83 |
+
scores = outputs['logits'].cpu().numpy()
|
| 84 |
+
scores = [round(x) for x in scores[0]]
|
| 85 |
+
return scores, feedback_components
|
init.sh
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
python -m spacy download en_core_web_sm
|
instance/users.db
ADDED
|
Binary file (20.5 kB). View file
|
|
|
models/__init__.py
ADDED
|
File without changes
|
models/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (152 Bytes). View file
|
|
|
models/__pycache__/model.cpython-312.pyc
ADDED
|
Binary file (6.69 kB). View file
|
|
|
models/model.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from transformers import LongformerPreTrainedModel, LongformerModel
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
class AttentionPooling(nn.Module):
|
| 7 |
+
def __init__(self, hidden_dim):
|
| 8 |
+
super().__init__()
|
| 9 |
+
self.hidden_dim = hidden_dim
|
| 10 |
+
self.query = nn.Linear(hidden_dim, hidden_dim)
|
| 11 |
+
self.energy = nn.Linear(hidden_dim, 1)
|
| 12 |
+
|
| 13 |
+
# Initialize weights
|
| 14 |
+
nn.init.xavier_uniform_(self.query.weight)
|
| 15 |
+
nn.init.xavier_uniform_(self.energy.weight)
|
| 16 |
+
self.query.bias.data.zero_()
|
| 17 |
+
self.energy.bias.data.zero_()
|
| 18 |
+
|
| 19 |
+
def forward(self, hidden_states, attention_mask=None):
|
| 20 |
+
# Compute attention scores
|
| 21 |
+
transformed = torch.tanh(self.query(hidden_states)) # (batch_size, seq_len, hidden_dim)
|
| 22 |
+
scores = self.energy(transformed).squeeze(-1) # (batch_size, seq_len)
|
| 23 |
+
|
| 24 |
+
# Apply attention mask if provided
|
| 25 |
+
if attention_mask is not None:
|
| 26 |
+
scores = scores.masked_fill(attention_mask == 0, float('-inf'))
|
| 27 |
+
|
| 28 |
+
# Compute attention weights
|
| 29 |
+
weights = F.softmax(scores, dim=-1) # (batch_size, seq_len)
|
| 30 |
+
|
| 31 |
+
# Apply attention pooling
|
| 32 |
+
pooled = torch.sum(hidden_states * weights.unsqueeze(-1), dim=1) # (batch_size, hidden_dim)
|
| 33 |
+
return pooled
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class CustomLongformerForSequenceClassification(LongformerPreTrainedModel):
|
| 38 |
+
"""Longformer model with attention pooling for sequence classification.
|
| 39 |
+
|
| 40 |
+
Uses attention pooling over the last four hidden layers instead of CLS token pooling.
|
| 41 |
+
"""
|
| 42 |
+
def __init__(self, config):
|
| 43 |
+
super().__init__(config)
|
| 44 |
+
self.num_labels = config.num_labels
|
| 45 |
+
self.config = config
|
| 46 |
+
|
| 47 |
+
# Longformer backbone
|
| 48 |
+
self.longformer = LongformerModel(config)
|
| 49 |
+
self.dropout = nn.Dropout(config.hidden_dropout_prob)
|
| 50 |
+
|
| 51 |
+
# Attention pooling for each layer
|
| 52 |
+
self.attention_poolers = nn.ModuleList([
|
| 53 |
+
AttentionPooling(config.hidden_size) for _ in range(4)
|
| 54 |
+
])
|
| 55 |
+
|
| 56 |
+
# Final classifier
|
| 57 |
+
self.classifier = nn.Linear(config.hidden_size * 4, config.num_labels)
|
| 58 |
+
|
| 59 |
+
# Initialize weights
|
| 60 |
+
self.post_init()
|
| 61 |
+
|
| 62 |
+
def forward(self, input_ids=None, attention_mask=None, labels=None, **kwargs):
|
| 63 |
+
outputs = self.longformer(
|
| 64 |
+
input_ids=input_ids,
|
| 65 |
+
attention_mask=attention_mask,
|
| 66 |
+
output_hidden_states=True,
|
| 67 |
+
**kwargs
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
# Get last four hidden layers
|
| 71 |
+
last_four_layers = outputs.hidden_states[-4:]
|
| 72 |
+
|
| 73 |
+
# Apply attention pooling to each layer
|
| 74 |
+
pooled = []
|
| 75 |
+
for layer, pooler in zip(last_four_layers, self.attention_poolers):
|
| 76 |
+
pooled.append(pooler(layer, attention_mask=attention_mask))
|
| 77 |
+
|
| 78 |
+
# Concatenate pooled representations
|
| 79 |
+
concatenated = torch.cat(pooled, dim=1)
|
| 80 |
+
concatenated = self.dropout(concatenated)
|
| 81 |
+
logits = self.classifier(concatenated)
|
| 82 |
+
|
| 83 |
+
# Compute loss if labels provided
|
| 84 |
+
loss = None
|
| 85 |
+
if labels is not None:
|
| 86 |
+
if hasattr(self, 'loss_fct'):
|
| 87 |
+
loss = self.loss_fct(logits, labels)
|
| 88 |
+
else:
|
| 89 |
+
loss = F.mse_loss(logits, labels.float())
|
| 90 |
+
|
| 91 |
+
return {'loss': loss, 'logits': logits}
|
| 92 |
+
|
| 93 |
+
class CustomLongformerForSequenceClassification(LongformerPreTrainedModel):
|
| 94 |
+
"""Longformer model with attention pooling for sequence classification."""
|
| 95 |
+
def __init__(self, config):
|
| 96 |
+
super().__init__(config)
|
| 97 |
+
self.num_labels = config.num_labels
|
| 98 |
+
self.config = config
|
| 99 |
+
|
| 100 |
+
# Longformer backbone
|
| 101 |
+
self.longformer = LongformerModel(config)
|
| 102 |
+
self.dropout = nn.Dropout(config.hidden_dropout_prob)
|
| 103 |
+
|
| 104 |
+
# Attention pooling for each layer
|
| 105 |
+
self.attention_poolers = nn.ModuleList([
|
| 106 |
+
AttentionPooling(config.hidden_size) for _ in range(4)
|
| 107 |
+
])
|
| 108 |
+
|
| 109 |
+
# Final classifier
|
| 110 |
+
self.classifier = nn.Linear(config.hidden_size * 4, config.num_labels)
|
| 111 |
+
|
| 112 |
+
# Initialize weights
|
| 113 |
+
self.post_init()
|
| 114 |
+
|
| 115 |
+
def forward(self, input_ids=None, attention_mask=None, labels=None, **kwargs):
|
| 116 |
+
outputs = self.longformer(
|
| 117 |
+
input_ids=input_ids,
|
| 118 |
+
attention_mask=attention_mask,
|
| 119 |
+
output_hidden_states=True,
|
| 120 |
+
**kwargs
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# Get last four hidden layers
|
| 124 |
+
last_four_layers = outputs.hidden_states[-4:]
|
| 125 |
+
|
| 126 |
+
# Apply attention pooling to each layer
|
| 127 |
+
pooled = []
|
| 128 |
+
for layer, pooler in zip(last_four_layers, self.attention_poolers):
|
| 129 |
+
pooled.append(pooler(layer, attention_mask=attention_mask))
|
| 130 |
+
|
| 131 |
+
# Concatenate pooled representations
|
| 132 |
+
concatenated = torch.cat(pooled, dim=1)
|
| 133 |
+
concatenated = self.dropout(concatenated)
|
| 134 |
+
logits = self.classifier(concatenated)
|
| 135 |
+
|
| 136 |
+
# Compute loss if labels provided
|
| 137 |
+
loss = None
|
| 138 |
+
if labels is not None:
|
| 139 |
+
if hasattr(self, 'loss_fct'):
|
| 140 |
+
loss = self.loss_fct(logits, labels)
|
| 141 |
+
else:
|
| 142 |
+
loss = F.mse_loss(logits.view(-1), labels.float().view(-1))
|
| 143 |
+
|
| 144 |
+
return {'loss': loss, 'logits': logits}
|
my_app/__init__.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask
|
| 2 |
+
from flask_sqlalchemy import SQLAlchemy
|
| 3 |
+
from flask_login import LoginManager
|
| 4 |
+
from flask import render_template
|
| 5 |
+
from transformers import LongformerTokenizer, AutoTokenizer, AutoModelForCausalLM, LongformerConfig
|
| 6 |
+
from models.model import *
|
| 7 |
+
from utils.util_func import *
|
| 8 |
+
from safetensors.torch import load_file
|
| 9 |
+
|
| 10 |
+
db = SQLAlchemy()
|
| 11 |
+
login_manager = LoginManager()
|
| 12 |
+
|
| 13 |
+
MODELS_LOADED = False
|
| 14 |
+
LONGFORMER_TOKENIZER = None
|
| 15 |
+
LONGFORMER_MODEL = None
|
| 16 |
+
QWEN_TOKENIZER = None
|
| 17 |
+
QWEN_MODEL = None
|
| 18 |
+
MODEL_SESSION = None
|
| 19 |
+
|
| 20 |
+
def load_models():
|
| 21 |
+
global MODELS_LOADED, LONGFORMER_TOKENIZER, LONGFORMER_MODEL, QWEN_TOKENIZER, QWEN_MODEL
|
| 22 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 23 |
+
if not MODELS_LOADED:
|
| 24 |
+
LONGFORMER_TOKENIZER = LongformerTokenizer.from_pretrained('allenai/longformer-base-4096', device='auto')
|
| 25 |
+
config = LongformerConfig.from_json_file("checkpoints/Longformer_checkpoint/config.json")
|
| 26 |
+
LONGFORMER_MODEL = CustomLongformerForSequenceClassification(config).from_pretrained('SFM2001/LongFormerScorer')
|
| 27 |
+
LONGFORMER_MODEL = LONGFORMER_MODEL.to(device)
|
| 28 |
+
LONGFORMER_MODEL.eval()
|
| 29 |
+
|
| 30 |
+
model_name = 'Qwen/Qwen3-1.7B'
|
| 31 |
+
QWEN_TOKENIZER = AutoTokenizer.from_pretrained(model_name, device='auto')
|
| 32 |
+
QWEN_TOKENIZER.pad_token_id = QWEN_TOKENIZER.eos_token_id
|
| 33 |
+
QWEN_MODEL = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16).half()
|
| 34 |
+
MODELS_LOADED = True
|
| 35 |
+
|
| 36 |
+
def create_app():
|
| 37 |
+
set_seed(42)
|
| 38 |
+
load_models()
|
| 39 |
+
app = Flask(__name__)
|
| 40 |
+
app.config.from_pyfile('../configs.py')
|
| 41 |
+
db.init_app(app)
|
| 42 |
+
login_manager.init_app(app)
|
| 43 |
+
login_manager.login_view = 'auth.login'
|
| 44 |
+
@login_manager.user_loader
|
| 45 |
+
def load_user(user_id):
|
| 46 |
+
return User.query.get(int(user_id))
|
| 47 |
+
|
| 48 |
+
with app.app_context():
|
| 49 |
+
from .views import auth_bp, dashboard_bp, infer_bp, about_bp, error_bp
|
| 50 |
+
app.register_blueprint(auth_bp)
|
| 51 |
+
app.register_blueprint(dashboard_bp)
|
| 52 |
+
app.register_blueprint(infer_bp)
|
| 53 |
+
app.register_blueprint(about_bp)
|
| 54 |
+
app.register_blueprint(error_bp)
|
| 55 |
+
@app.errorhandler(Exception)
|
| 56 |
+
def handle_all_exceptions(e):
|
| 57 |
+
code = getattr(e, 'code', 500)
|
| 58 |
+
error_message = str(e) if hasattr(e, 'description') else "Something went wrong."
|
| 59 |
+
return render_template('error.html', code=code, error_message=error_message), code
|
| 60 |
+
from .database import User, History
|
| 61 |
+
db.create_all()
|
| 62 |
+
|
| 63 |
+
return app
|
my_app/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (4.12 kB). View file
|
|
|
my_app/__pycache__/database.cpython-312.pyc
ADDED
|
Binary file (2.8 kB). View file
|
|
|
my_app/database.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask_login import UserMixin
|
| 2 |
+
from werkzeug.security import generate_password_hash, check_password_hash
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
from . import db
|
| 5 |
+
|
| 6 |
+
class User(UserMixin, db.Model):
|
| 7 |
+
id = db.Column(db.Integer, primary_key=True)
|
| 8 |
+
email = db.Column(db.String(100), unique=True, nullable=False)
|
| 9 |
+
nickname = db.Column(db.String(100), unique=True, nullable=False)
|
| 10 |
+
password_hash = db.Column(db.String(200))
|
| 11 |
+
|
| 12 |
+
def set_password(self, password):
|
| 13 |
+
self.password_hash = generate_password_hash(password)
|
| 14 |
+
|
| 15 |
+
def check_password(self, password):
|
| 16 |
+
return check_password_hash(self.password_hash, password)
|
| 17 |
+
|
| 18 |
+
class History(db.Model):
|
| 19 |
+
id = db.Column(db.Integer, primary_key=True)
|
| 20 |
+
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
| 21 |
+
topic = db.Column(db.String(255), nullable=False)
|
| 22 |
+
essay = db.Column(db.Text, nullable=False)
|
| 23 |
+
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
| 24 |
+
score_tr = db.Column(db.Integer, nullable=False)
|
| 25 |
+
score_cc = db.Column(db.Integer, nullable=False)
|
| 26 |
+
score_lr = db.Column(db.Integer, nullable=False)
|
| 27 |
+
score_gra = db.Column(db.Integer, nullable=False)
|
| 28 |
+
user = db.relationship('User', backref=db.backref('histories', lazy=True))
|
my_app/static/js/dashboard_charts.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function showModal(content) {
|
| 2 |
+
document.getElementById('content-text').innerText = content;
|
| 3 |
+
$('#contentModal').modal('show');
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
function selectAll(source) {
|
| 7 |
+
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
| 8 |
+
for (let i = 0; i < checkboxes.length; i++) {
|
| 9 |
+
if (checkboxes[i] !== source)
|
| 10 |
+
checkboxes[i].checked = source.checked;
|
| 11 |
+
}
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
function initializeCharts(scoresTR, scoresCC, scoresLR, scoresGRA, creationTimes) {
|
| 15 |
+
const essayNumbers = [...Array(scoresTR.length).keys()].map(x => x + 1); // Array from 1 to the length of scores
|
| 16 |
+
|
| 17 |
+
const rubricChartData = {
|
| 18 |
+
labels: essayNumbers,
|
| 19 |
+
datasets: [
|
| 20 |
+
{
|
| 21 |
+
label: 'Task Response',
|
| 22 |
+
data: scoresTR,
|
| 23 |
+
borderColor: 'rgba(54, 162, 235, 1.0)',
|
| 24 |
+
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
| 25 |
+
fill: false,
|
| 26 |
+
pointRadius: 5
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
label: 'Coherence and Cohesion',
|
| 30 |
+
data: scoresCC,
|
| 31 |
+
borderColor: 'rgba(255, 99, 132, 1.0)',
|
| 32 |
+
backgroundColor: 'rgba(255, 99, 132, 0.6)',
|
| 33 |
+
fill: false,
|
| 34 |
+
pointRadius: 5
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
label: 'Lexical Resource',
|
| 38 |
+
data: scoresLR,
|
| 39 |
+
borderColor: 'rgba(75, 192, 192, 1.0)',
|
| 40 |
+
backgroundColor: 'rgba(75, 192, 192, 0.6)',
|
| 41 |
+
fill: false,
|
| 42 |
+
pointRadius: 5
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
label: 'Grammatical Range and Accuracy',
|
| 46 |
+
data: scoresGRA,
|
| 47 |
+
borderColor: 'rgba(153, 102, 255, 1.0)',
|
| 48 |
+
backgroundColor: 'rgba(153, 102, 255, 0.6)',
|
| 49 |
+
fill: false,
|
| 50 |
+
pointRadius: 5
|
| 51 |
+
}
|
| 52 |
+
]
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
const rubricChartOptions = {
|
| 56 |
+
responsive: true,
|
| 57 |
+
plugins: {
|
| 58 |
+
tooltip: {
|
| 59 |
+
callbacks: {
|
| 60 |
+
label: function (tooltipItem) {
|
| 61 |
+
const index = tooltipItem.dataIndex;
|
| 62 |
+
switch (tooltipItem.dataset.label) {
|
| 63 |
+
case 'Task Response':
|
| 64 |
+
return `Task Response Score: ${tooltipItem.raw}, Time: ${creationTimes[index]}`;
|
| 65 |
+
case 'Coherence and Cohesion':
|
| 66 |
+
return `Coherence and Cohesion Score: ${tooltipItem.raw}, Time: ${creationTimes[index]}`;
|
| 67 |
+
case 'Lexical Resource':
|
| 68 |
+
return `Lexical Resource Score: ${tooltipItem.raw}, Time: ${creationTimes[index]}`;
|
| 69 |
+
case 'Grammatical Range and Accuracy':
|
| 70 |
+
return `Grammatical Range and Accuracy Score: ${tooltipItem.raw}, Time: ${creationTimes[index]}`;
|
| 71 |
+
default:
|
| 72 |
+
return `Score: ${tooltipItem.raw}, Time: ${creationTimes[index]}`;
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
},
|
| 77 |
+
legend: {
|
| 78 |
+
display: true,
|
| 79 |
+
labels: {
|
| 80 |
+
color: 'white',
|
| 81 |
+
padding: 20
|
| 82 |
+
},
|
| 83 |
+
position: 'top',
|
| 84 |
+
align: 'center'
|
| 85 |
+
}
|
| 86 |
+
},
|
| 87 |
+
scales: {
|
| 88 |
+
x: {
|
| 89 |
+
title: {
|
| 90 |
+
display: true,
|
| 91 |
+
text: 'Essay Number',
|
| 92 |
+
color: 'white'
|
| 93 |
+
},
|
| 94 |
+
ticks: {
|
| 95 |
+
color: 'white'
|
| 96 |
+
},
|
| 97 |
+
grid: {
|
| 98 |
+
display: true
|
| 99 |
+
}
|
| 100 |
+
},
|
| 101 |
+
y: {
|
| 102 |
+
title: {
|
| 103 |
+
display: true,
|
| 104 |
+
text: 'Score',
|
| 105 |
+
color: 'white'
|
| 106 |
+
},
|
| 107 |
+
ticks: {
|
| 108 |
+
color: 'white'
|
| 109 |
+
},
|
| 110 |
+
min: 0,
|
| 111 |
+
max: 10,
|
| 112 |
+
grid: {
|
| 113 |
+
display: true,
|
| 114 |
+
color: 'rgba(255, 255, 255, 0.2)'
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
};
|
| 119 |
+
|
| 120 |
+
new Chart(document.getElementById('RubricChart'), {
|
| 121 |
+
type: 'line',
|
| 122 |
+
data: rubricChartData,
|
| 123 |
+
options: rubricChartOptions
|
| 124 |
+
});
|
| 125 |
+
|
| 126 |
+
const essaysPerDay = {};
|
| 127 |
+
creationTimes.forEach(time => {
|
| 128 |
+
const date = time.split(' ')[0];
|
| 129 |
+
essaysPerDay[date] = (essaysPerDay[date] || 0) + 1;
|
| 130 |
+
});
|
| 131 |
+
const sortedEssaysArray = Object.entries(essaysPerDay).sort((a, b) => new Date(a[0]) - new Date(b[0]));
|
| 132 |
+
const sortedEssaysPerDay = Object.fromEntries(sortedEssaysArray);
|
| 133 |
+
|
| 134 |
+
const dates = Object.keys(sortedEssaysPerDay);
|
| 135 |
+
const essayCounts = Object.values(sortedEssaysPerDay);
|
| 136 |
+
|
| 137 |
+
const essayCountChartData = {
|
| 138 |
+
labels: dates,
|
| 139 |
+
datasets: [{
|
| 140 |
+
label: 'Essays Per Day',
|
| 141 |
+
data: essayCounts,
|
| 142 |
+
borderColor: 'rgba(255, 165, 0, 1.0)',
|
| 143 |
+
backgroundColor: 'rgba(145, 128, 128, 0.5)',
|
| 144 |
+
fill: true,
|
| 145 |
+
pointRadius: 5
|
| 146 |
+
}]
|
| 147 |
+
};
|
| 148 |
+
|
| 149 |
+
const essayCountChartOptions = {
|
| 150 |
+
responsive: true,
|
| 151 |
+
plugins: {
|
| 152 |
+
legend: {
|
| 153 |
+
display: false
|
| 154 |
+
}
|
| 155 |
+
},
|
| 156 |
+
scales: {
|
| 157 |
+
x: {
|
| 158 |
+
title: {
|
| 159 |
+
display: true,
|
| 160 |
+
text: 'Date',
|
| 161 |
+
color: 'white'
|
| 162 |
+
},
|
| 163 |
+
ticks: {
|
| 164 |
+
color: 'white'
|
| 165 |
+
},
|
| 166 |
+
grid: {
|
| 167 |
+
display: true
|
| 168 |
+
}
|
| 169 |
+
},
|
| 170 |
+
y: {
|
| 171 |
+
title: {
|
| 172 |
+
display: true,
|
| 173 |
+
text: 'Number of Essays',
|
| 174 |
+
color: 'white'
|
| 175 |
+
},
|
| 176 |
+
ticks: {
|
| 177 |
+
color: 'white'
|
| 178 |
+
},
|
| 179 |
+
grid: {
|
| 180 |
+
display: true,
|
| 181 |
+
color: 'rgba(255, 255, 255, 0.2)' // Light grid lines
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
};
|
| 186 |
+
new Chart(document.getElementById('EssayCountChart'), {
|
| 187 |
+
type: 'bar',
|
| 188 |
+
data: essayCountChartData,
|
| 189 |
+
options: essayCountChartOptions
|
| 190 |
+
});
|
| 191 |
+
}
|
my_app/static/js/home_page.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
$(document).ready(function() {
|
| 2 |
+
$('#toggle-sidebar').on('click', function() {
|
| 3 |
+
const sidebar = $('#sidebar');
|
| 4 |
+
sidebar.toggleClass('active');
|
| 5 |
+
|
| 6 |
+
if (sidebar.hasClass('active')) {
|
| 7 |
+
$(this).text("✖");
|
| 8 |
+
} else {
|
| 9 |
+
$(this).text("☰");
|
| 10 |
+
}
|
| 11 |
+
});
|
| 12 |
+
|
| 13 |
+
$('#close-sidebar').on('click', function() {
|
| 14 |
+
const sidebar = $('#sidebar');
|
| 15 |
+
sidebar.removeClass('active');
|
| 16 |
+
$('#toggle-sidebar').text("☰");
|
| 17 |
+
});
|
| 18 |
+
|
| 19 |
+
var isEmpty = $('#isempty').val() === 'True';
|
| 20 |
+
if (isEmpty) {
|
| 21 |
+
$('#alertModal').modal('show');
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
$('#essay').on('keydown', function(e) {
|
| 25 |
+
if (e.key === 'Enter') {
|
| 26 |
+
e.preventDefault();
|
| 27 |
+
var start = this.selectionStart;
|
| 28 |
+
var end = this.selectionEnd;
|
| 29 |
+
var text = this.value;
|
| 30 |
+
this.value = text.substring(0, start) + "\n" + text.substring(end);
|
| 31 |
+
this.selectionStart = this.selectionEnd = start + 1;
|
| 32 |
+
}
|
| 33 |
+
});
|
| 34 |
+
|
| 35 |
+
let countdownSeconds;
|
| 36 |
+
const countdownDisplay = document.getElementById("countdown-timer");
|
| 37 |
+
let countdownInterval;
|
| 38 |
+
|
| 39 |
+
function updateCountdown() {
|
| 40 |
+
const minutes = Math.floor(countdownSeconds / 60);
|
| 41 |
+
const seconds = countdownSeconds % 60;
|
| 42 |
+
countdownDisplay.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
| 43 |
+
countdownSeconds--;
|
| 44 |
+
if (countdownSeconds >= 0) {
|
| 45 |
+
countdownInterval = setTimeout(updateCountdown, 1000);
|
| 46 |
+
} else {
|
| 47 |
+
countdownDisplay.textContent = "Time's up!";
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
$('#start-countdown').on('click', function() {
|
| 52 |
+
const time = countdownDisplay.textContent.trim();
|
| 53 |
+
const timeParts = time.split(':');
|
| 54 |
+
const minutesInput = parseInt(timeParts[0], 10) || 0;
|
| 55 |
+
const secondsInput = parseInt(timeParts[1], 10) || 0;
|
| 56 |
+
countdownSeconds = minutesInput * 60 + secondsInput;
|
| 57 |
+
|
| 58 |
+
clearTimeout(countdownInterval);
|
| 59 |
+
updateCountdown();
|
| 60 |
+
});
|
| 61 |
+
|
| 62 |
+
$('#stop-countdown').on('click', function() {
|
| 63 |
+
clearTimeout(countdownInterval);
|
| 64 |
+
});
|
| 65 |
+
});
|
my_app/static/js/particles_init.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener("DOMContentLoaded", function () {
|
| 2 |
+
particlesJS("particles-js", {
|
| 3 |
+
"particles": {
|
| 4 |
+
"number": {
|
| 5 |
+
"value": 380,
|
| 6 |
+
"density": {
|
| 7 |
+
"enable": true,
|
| 8 |
+
"value_area": 800
|
| 9 |
+
}
|
| 10 |
+
},
|
| 11 |
+
"color": {
|
| 12 |
+
"value": "#b3b3cc"
|
| 13 |
+
},
|
| 14 |
+
"shape": {
|
| 15 |
+
"type": "circle",
|
| 16 |
+
"stroke": {
|
| 17 |
+
"width": 0,
|
| 18 |
+
"color": "#000000"
|
| 19 |
+
}
|
| 20 |
+
},
|
| 21 |
+
"opacity": {
|
| 22 |
+
"value": 0.5,
|
| 23 |
+
"random": false
|
| 24 |
+
},
|
| 25 |
+
"size": {
|
| 26 |
+
"value": 3,
|
| 27 |
+
"random": true
|
| 28 |
+
},
|
| 29 |
+
"line_linked": {
|
| 30 |
+
"enable": true,
|
| 31 |
+
"distance": 150,
|
| 32 |
+
"color": "#b3b3cc",
|
| 33 |
+
"opacity": 0.4,
|
| 34 |
+
"width": 1
|
| 35 |
+
},
|
| 36 |
+
"move": {
|
| 37 |
+
"enable": true,
|
| 38 |
+
"speed": 1
|
| 39 |
+
}
|
| 40 |
+
},
|
| 41 |
+
"interactivity": {
|
| 42 |
+
"events": {
|
| 43 |
+
"onhover": {
|
| 44 |
+
"enable": true,
|
| 45 |
+
"mode": "grab"
|
| 46 |
+
},
|
| 47 |
+
"onclick": {
|
| 48 |
+
"enable": true,
|
| 49 |
+
"mode": "push"
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
},
|
| 53 |
+
"retina_detect": true
|
| 54 |
+
});
|
| 55 |
+
});
|
my_app/static/styles.css
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--background-color: rgb(46, 44, 46);
|
| 3 |
+
--navbar-color: rgb(52, 45, 53, 0.0);
|
| 4 |
+
--footer-background-color: var(--navbar-color);
|
| 5 |
+
--footer-text-color: rgb(145, 128, 128);
|
| 6 |
+
--title-color: #d6cdcd;
|
| 7 |
+
--button-bg-color: #505050;
|
| 8 |
+
--button-hover-bg-color: #404040;
|
| 9 |
+
--button-border-color: #404040;
|
| 10 |
+
--button-hover-border-color: #303030;
|
| 11 |
+
--input-bg-color: rgba(255, 255, 255, 0.5);
|
| 12 |
+
--input-text-color: #d6cdcd;
|
| 13 |
+
--alert-color: var(--input-text-color);
|
| 14 |
+
--toggle-color: #45586d;
|
| 15 |
+
--link-color: var(--title-color);
|
| 16 |
+
--link-hover-color: var(--title-color);
|
| 17 |
+
--modal-background-color: var(--background-color);
|
| 18 |
+
--modal-text-color: var(--title-color);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
#particles-js {
|
| 22 |
+
position: fixed;
|
| 23 |
+
top: 0;
|
| 24 |
+
left: 0;
|
| 25 |
+
width: 100%;
|
| 26 |
+
height: 100%;
|
| 27 |
+
background-color: var(--background-color);
|
| 28 |
+
background-image: url("");
|
| 29 |
+
background-repeat: no-repeat;
|
| 30 |
+
background-size: cover;
|
| 31 |
+
background-position: 50% 50%;
|
| 32 |
+
z-index: -1;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
body {
|
| 36 |
+
margin: 0;
|
| 37 |
+
padding: 0;
|
| 38 |
+
height: 100vh;
|
| 39 |
+
display: flex;
|
| 40 |
+
flex-direction: column;
|
| 41 |
+
min-height: 100vh;
|
| 42 |
+
color: var(--title-color);
|
| 43 |
+
font-family: 'Georgia', serif;
|
| 44 |
+
-webkit-background-size: cover;
|
| 45 |
+
-moz-background-size: cover;
|
| 46 |
+
-o-background-size: cover;
|
| 47 |
+
background-size: cover;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
input[type="text"], input[type="email"], input[type="password"] {
|
| 51 |
+
background-color: rgba(10, 9, 9, 0.5);
|
| 52 |
+
border: 1px solid #100f0f;
|
| 53 |
+
color: white;
|
| 54 |
+
border-radius: 4px;
|
| 55 |
+
padding: 10px;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
textarea {
|
| 59 |
+
background-color:transparent;
|
| 60 |
+
background: transparent;
|
| 61 |
+
resize: vertical;
|
| 62 |
+
overflow-y: auto;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.modal-content {
|
| 66 |
+
background-color: var(--modal-background-color);
|
| 67 |
+
color: var(--modal-background-color);
|
| 68 |
+
z-index: 1050;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
.modal-header .close {
|
| 72 |
+
color: var(--modal-text-color);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
body, h1, h2, h3, p, a {
|
| 76 |
+
font-family: 'Georgia', serif;
|
| 77 |
+
text-align: center;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
table {
|
| 81 |
+
margin-top: 20px;
|
| 82 |
+
color: var(--title-color);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
thead {
|
| 86 |
+
color: var(--title-color);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
tbody {
|
| 90 |
+
color: var(--title-color);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.footer {
|
| 94 |
+
background-color: var(--footer-background-color);
|
| 95 |
+
color: var(--footer-text-color);
|
| 96 |
+
padding: 10px;
|
| 97 |
+
text-align: center;
|
| 98 |
+
margin-top: 20px;
|
| 99 |
+
position: relative;
|
| 100 |
+
bottom: 0;
|
| 101 |
+
width: 100%;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.navbar {
|
| 105 |
+
margin-bottom: 20px;
|
| 106 |
+
background-color: var(--navbar-color);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
a.nav-link {
|
| 110 |
+
color: var(--link-color);
|
| 111 |
+
text-decoration: none;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
a.nav-link:hover {
|
| 115 |
+
color: var(--link-hover-color);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.center-text {
|
| 119 |
+
text-align: center;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.btn-center {
|
| 123 |
+
display: flex;
|
| 124 |
+
justify-content: center;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.footer a {
|
| 128 |
+
color: var(--link-color);
|
| 129 |
+
text-decoration: none;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
.footer a:hover {
|
| 133 |
+
color: var(--link-hover-color);
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
textarea {
|
| 137 |
+
background-color: #1e0606 ;
|
| 138 |
+
border: 1px solid #ccc;
|
| 139 |
+
resize: vertical;
|
| 140 |
+
overflow-y: auto;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.btn-primary {
|
| 144 |
+
background-color: var(--button-bg-color);
|
| 145 |
+
border-color: var(--button-border-color);
|
| 146 |
+
margin-bottom: 20px;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.btn-primary:hover {
|
| 150 |
+
background-color: var(--button-hover-bg-color);
|
| 151 |
+
border-color: var(--button-hover-border-color);
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
h1, h2 {
|
| 155 |
+
color: var(--title-color);
|
| 156 |
+
padding-top: 20px;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.table td, .table th {
|
| 160 |
+
text-align: left;
|
| 161 |
+
vertical-align: middle;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.text-center {
|
| 165 |
+
margin-top: 20px;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.btn-danger {
|
| 169 |
+
width: 100px;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.btn-info {
|
| 173 |
+
width: 100px;
|
| 174 |
+
height: 50;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.form-text.text-muted {
|
| 178 |
+
color: var(--alert-color) !important;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.btn-toggle {
|
| 182 |
+
border: none;
|
| 183 |
+
background: none;
|
| 184 |
+
color: var(--toggle-color);
|
| 185 |
+
cursor: pointer;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.content-hidden {
|
| 189 |
+
display: none;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.container {
|
| 193 |
+
flex: 1;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
.modal-content {
|
| 197 |
+
background-color: var(--modal-background-color);
|
| 198 |
+
color: var(--modal-text-color);
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
.modal-header .close {
|
| 202 |
+
color: var(--modal-text-color);
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
.flip-card {
|
| 206 |
+
background-color: transparent;
|
| 207 |
+
width: 100%;
|
| 208 |
+
height: 200px;
|
| 209 |
+
perspective: 1000px;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
.flip-card-inner {
|
| 213 |
+
position: relative;
|
| 214 |
+
width: 100%;
|
| 215 |
+
height: 100%;
|
| 216 |
+
text-align: center;
|
| 217 |
+
transition: transform 0.8s;
|
| 218 |
+
transform-style: preserve-3d;
|
| 219 |
+
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
.flip-card:hover .flip-card-inner {
|
| 223 |
+
transform: rotateY(180deg);
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
.flip-card-front,
|
| 227 |
+
.flip-card-back {
|
| 228 |
+
position: absolute;
|
| 229 |
+
width: 100%;
|
| 230 |
+
height: 100%;
|
| 231 |
+
-webkit-backface-visibility: hidden;
|
| 232 |
+
backface-visibility: hidden;
|
| 233 |
+
display: flex;
|
| 234 |
+
flex-direction: column;
|
| 235 |
+
justify-content: center;
|
| 236 |
+
align-items: center;
|
| 237 |
+
padding: 20px;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.flip-card-front {
|
| 241 |
+
background-color: #313840;
|
| 242 |
+
color: white;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
.flip-card-back {
|
| 246 |
+
background-color: #8fa2b5;
|
| 247 |
+
color: black;
|
| 248 |
+
transform: rotateY(180deg);
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
.sidebar {
|
| 253 |
+
position: fixed;
|
| 254 |
+
top: 0;
|
| 255 |
+
left: -300px;
|
| 256 |
+
width: 300px;
|
| 257 |
+
height: 100%;
|
| 258 |
+
background-color: #343a40;
|
| 259 |
+
color: white;
|
| 260 |
+
transition: 0.3s;
|
| 261 |
+
z-index: 1000;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.sidebar.active {
|
| 265 |
+
left: 0;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
.sidebar-content {
|
| 269 |
+
padding: 20px;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
.flashcards {
|
| 273 |
+
display: flex;
|
| 274 |
+
flex-wrap: wrap;
|
| 275 |
+
justify-content: space-around;
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
.flashcard {
|
| 279 |
+
background-color: white;
|
| 280 |
+
width: 900px;
|
| 281 |
+
height: 900px;
|
| 282 |
+
margin: 20px;
|
| 283 |
+
perspective: 1000px;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
.flashcard-inner {
|
| 287 |
+
position: relative;
|
| 288 |
+
width: 100%;
|
| 289 |
+
height: 100%;
|
| 290 |
+
text-align: center;
|
| 291 |
+
transition: transform 0.8s;
|
| 292 |
+
transform-style: preserve-3d;
|
| 293 |
+
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
| 294 |
+
border-radius: 10px;
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
.flashcard:hover .flashcard-inner {
|
| 298 |
+
transform: rotateY(180deg);
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
.flashcard-front, .flashcard-back {
|
| 302 |
+
position: absolute;
|
| 303 |
+
width: 100%;
|
| 304 |
+
height: 100%;
|
| 305 |
+
-webkit-backface-visibility: hidden;
|
| 306 |
+
backface-visibility: hidden;
|
| 307 |
+
border-radius: 10px;
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
.flashcard-front {
|
| 311 |
+
background-color: #091d2b;
|
| 312 |
+
color: white;
|
| 313 |
+
display: flex;
|
| 314 |
+
align-items: center;
|
| 315 |
+
justify-content: center;
|
| 316 |
+
font-size: 24px;
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
.flashcard-back {
|
| 320 |
+
background-color: rgb(207, 214, 234);
|
| 321 |
+
color: black;
|
| 322 |
+
transform: rotateY(180deg);
|
| 323 |
+
display: flex;
|
| 324 |
+
align-items: center;
|
| 325 |
+
justify-content: center;
|
| 326 |
+
padding: 20px;
|
| 327 |
+
box-sizing: border-box;
|
| 328 |
+
text-align: justify;
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
.col-lg-7 {
|
| 332 |
+
background-color: rgba(68, 68, 68, 0.85);
|
| 333 |
+
padding: 20px;
|
| 334 |
+
border-radius: 10px;
|
| 335 |
+
color: white;
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
.col-lg-7 h5 {
|
| 339 |
+
font-size: 1.2rem;
|
| 340 |
+
margin-top: 15px;
|
| 341 |
+
color: #f8f9fa;
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
.col-lg-7 p {
|
| 345 |
+
font-size: 1rem;
|
| 346 |
+
line-height: 1.6;
|
| 347 |
+
}
|
my_app/templates/403.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<title>Access Denied</title>
|
| 5 |
+
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
| 6 |
+
</head>
|
| 7 |
+
<body>
|
| 8 |
+
<div class="container text-center">
|
| 9 |
+
<h1>Access Denied</h1>
|
| 10 |
+
<p>You do not have permission to access this page.</p>
|
| 11 |
+
<a href="{{ url_for('index') }}" class="btn btn-primary">Go Home</a>
|
| 12 |
+
</div>
|
| 13 |
+
</body>
|
| 14 |
+
</html>
|
my_app/templates/about_us.html
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="container mt-4" style="background-color: rgba(0, 0, 0, 0.3); color: white; padding: 20px; border-radius: 10px;">
|
| 5 |
+
<h2>About Us</h2>
|
| 6 |
+
<p style="text-indent: 40px; text-align:left;">
|
| 7 |
+
Welcome to the project page for SimpleAES, a comprehensive system designed to evaluate English
|
| 8 |
+
essays based on the IELTS rubrics. The primary goal of this project is to develop an automated tool that
|
| 9 |
+
can accurately score essays on four critical criteria: Task Response, Coherence and Cohesion,
|
| 10 |
+
Lexical Resource, and Grammatical Range and Accuracy. This project aims to provide a reliable
|
| 11 |
+
and objective means of essay assessment, helping both learning and evaluation processes in educational
|
| 12 |
+
settings.
|
| 13 |
+
</p>
|
| 14 |
+
<br>
|
| 15 |
+
<p style="text-indent: 40px; text-align:left;">
|
| 16 |
+
The IELTS (International English Language Testing System) is a widely recognized standard for
|
| 17 |
+
assessing English language proficiency. It is used by educational institutions, employers,
|
| 18 |
+
and immigration authorities worldwide. To meet the specified standards set by IELTS, this project
|
| 19 |
+
leverages advanced natural language processing (NLP) techniques and neural network to evaluate essays
|
| 20 |
+
with a high degree of precision and consistency.
|
| 21 |
+
</p>
|
| 22 |
+
<br>
|
| 23 |
+
<p style="text-indent: 40px; text-align:left;">
|
| 24 |
+
The project focuses on the following four criteria:
|
| 25 |
+
</p>
|
| 26 |
+
<ul>
|
| 27 |
+
<li style="text-indent: 40px; text-align:left;">
|
| 28 |
+
<strong>Task Response:</strong> This criterion evaluates how well the essay addresses the prompt.
|
| 29 |
+
It checks if the writer has answered all parts of the question, developed a clear position,
|
| 30 |
+
and supported their arguments with relevant examples. An effective task response ensures that the
|
| 31 |
+
essay stays on topic and thoroughly covers all aspects of the task.
|
| 32 |
+
</li>
|
| 33 |
+
<br>
|
| 34 |
+
<li style="text-indent: 40px; text-align:left;">
|
| 35 |
+
<strong>Coherence and Cohesion:</strong> This criterion assesses the logical flow and organization
|
| 36 |
+
of the essay. It examines how ideas are connected and how effectively the essay uses cohesive
|
| 37 |
+
devices (such as conjunctions and transitions) to link sentences and paragraphs. Good coherence
|
| 38 |
+
and cohesion make an essay easy to follow and understand.
|
| 39 |
+
</li>
|
| 40 |
+
<br>
|
| 41 |
+
<li style="text-indent: 40px; text-align:left;">
|
| 42 |
+
<strong>Lexical Resource:</strong> This criterion evaluates the range and accuracy of vocabulary
|
| 43 |
+
used in the essay. It looks at the writer’s ability to use a wide variety of words and phrases
|
| 44 |
+
accurately and appropriately. This includes the use of idiomatic expressions, collocations, and less
|
| 45 |
+
common lexical items that enhance the essay's overall quality.
|
| 46 |
+
</li>
|
| 47 |
+
<br>
|
| 48 |
+
<li style="text-indent: 40px; text-align:left;">
|
| 49 |
+
<strong>Grammatical Range and Accuracy:</strong> This criterion assesses the writer's command of
|
| 50 |
+
grammatical structures. It evaluates the variety and correctness of sentence structures, verb tenses,
|
| 51 |
+
punctuation, and syntax. An essay that demonstrates a high level of grammatical proficiency is easier to
|
| 52 |
+
read and more effective in communicating its message.
|
| 53 |
+
</li>
|
| 54 |
+
</ul>
|
| 55 |
+
<br>
|
| 56 |
+
<p style="text-indent: 40px; text-align:left;">
|
| 57 |
+
By implementing this automated essay scoring system, the project aims to provide students
|
| 58 |
+
and educators with a powerful tool for improving writing skills and ensuring fair and consistent
|
| 59 |
+
evaluation. The system can be used for practice, feedback, and formal assessment, helping learners to
|
| 60 |
+
identify areas for improvement and track their progress over time. Ultimately, the project seeks to
|
| 61 |
+
enhance the teaching and learning of English writing by leveraging technology to provide high-quality,
|
| 62 |
+
accessible, and efficient essay evaluations.
|
| 63 |
+
</p>
|
| 64 |
+
</div>
|
| 65 |
+
{% endblock %}
|
my_app/templates/base.html
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>{% block title %}{% endblock %}</title>
|
| 5 |
+
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
| 6 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
| 7 |
+
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
|
| 8 |
+
<script src="{{ url_for('static', filename='js/particles_init.js') }}"></script>
|
| 9 |
+
<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> -->
|
| 10 |
+
<script>
|
| 11 |
+
window.addEventListener('beforeunload', function() {
|
| 12 |
+
if (typeof sessionStorage !== 'undefined') {
|
| 13 |
+
sessionStorage.clear();
|
| 14 |
+
}
|
| 15 |
+
});
|
| 16 |
+
</script>
|
| 17 |
+
</head>
|
| 18 |
+
<body>
|
| 19 |
+
<div id="particles-js"></div>
|
| 20 |
+
<div class="overlay-content">
|
| 21 |
+
<nav class="navbar navbar-expand-lg">
|
| 22 |
+
<div class="container-fluid">
|
| 23 |
+
<a class="nav-link" href="{{ url_for('about.about_us') }}">About Us</a>
|
| 24 |
+
<a class="nav-link" href="{{ url_for('infer.rubric_explanation') }}">Rubric Explanation</a>
|
| 25 |
+
<a class="nav-link" href="{{ url_for('infer.index') }}">Home</a>
|
| 26 |
+
{% if current_user.is_authenticated %}
|
| 27 |
+
<a class="nav-link" href="{{ url_for('dashboard.dashboard') }}">Dashboard [ {{ current_user.nickname }} ]</a>
|
| 28 |
+
<a class="nav-link" href="{{ url_for('auth.logout') }}">Logout</a>
|
| 29 |
+
{% else %}
|
| 30 |
+
<span class="navbar-text">
|
| 31 |
+
No user is logged in.
|
| 32 |
+
</span>
|
| 33 |
+
<a class="nav-link" href="{{ url_for('auth.login') }}">Login</a>
|
| 34 |
+
<a class="nav-link" href="{{ url_for('auth.register') }}">Register</a>
|
| 35 |
+
{% endif %}
|
| 36 |
+
</div>
|
| 37 |
+
</nav>
|
| 38 |
+
|
| 39 |
+
<div class="container">
|
| 40 |
+
{% block content %}
|
| 41 |
+
{% endblock %}
|
| 42 |
+
</div>
|
| 43 |
+
|
| 44 |
+
<div class="footer">
|
| 45 |
+
<p>Visit our <a href="https://github.com/mengsifei/SimpleAES">GitHub</a></p>
|
| 46 |
+
</div>
|
| 47 |
+
|
| 48 |
+
<!-- Modals -->
|
| 49 |
+
<div class="modal fade" id="alertModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
| 50 |
+
<div class="modal-dialog" role="document">
|
| 51 |
+
<div class="modal-content">
|
| 52 |
+
<div class="modal-header">
|
| 53 |
+
<h5 class="modal-title" id="exampleModalLabel">Alert</h5>
|
| 54 |
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
| 55 |
+
<span aria-hidden="true">×</span>
|
| 56 |
+
</button>
|
| 57 |
+
</div>
|
| 58 |
+
<div class="modal-body">
|
| 59 |
+
Please provide both a topic and an essay text.
|
| 60 |
+
</div>
|
| 61 |
+
<div class="modal-footer">
|
| 62 |
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">OK</button>
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
|
| 68 |
+
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
| 69 |
+
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.0/dist/umd/popper.min.js"></script>
|
| 70 |
+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
| 71 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
| 72 |
+
{% block extra_scripts %}{% endblock %}
|
| 73 |
+
</div>
|
| 74 |
+
</body>
|
| 75 |
+
</html>
|
my_app/templates/coherence_cohesion.html
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="container mt-4">
|
| 5 |
+
<h2>Coherence & Cohesion</h2>
|
| 6 |
+
<div class="flashcards">
|
| 7 |
+
<div class="flashcard">
|
| 8 |
+
<div class="flashcard-inner">
|
| 9 |
+
<div class="flashcard-front">
|
| 10 |
+
Band 9
|
| 11 |
+
</div>
|
| 12 |
+
<div class="flashcard-back">
|
| 13 |
+
The message can be followed effortlessly. Cohesion is used in such a way that it very rarely attracts attention. Any lapses in coherence or cohesion are minimal. Paragraphing is skilfully managed.
|
| 14 |
+
</div>
|
| 15 |
+
</div>
|
| 16 |
+
</div>
|
| 17 |
+
<div class="flashcard">
|
| 18 |
+
<div class="flashcard-inner">
|
| 19 |
+
<div class="flashcard-front">
|
| 20 |
+
Band 8
|
| 21 |
+
</div>
|
| 22 |
+
<div class="flashcard-back">
|
| 23 |
+
The message can be followed with ease. Information and ideas are logically sequenced, and cohesion is well managed. Occasional lapses in coherence and cohesion may occur. Paragraphing is used sufficiently and appropriately.
|
| 24 |
+
</div>
|
| 25 |
+
</div>
|
| 26 |
+
</div>
|
| 27 |
+
<div class="flashcard">
|
| 28 |
+
<div class="flashcard-inner">
|
| 29 |
+
<div class="flashcard-front">
|
| 30 |
+
Band 7
|
| 31 |
+
</div>
|
| 32 |
+
<div class="flashcard-back">
|
| 33 |
+
Information and ideas are logically organised, and there is a clear progression throughout the response.
|
| 34 |
+
A range of cohesive devices including reference and substitution is used flexibly but with some inaccuracies or some over/under use.
|
| 35 |
+
Paragraphing is generally used effectively to support overall coherence, and the sequencing of ideas within a paragraph is generally logical.
|
| 36 |
+
</div>
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
<div class="flashcard">
|
| 40 |
+
<div class="flashcard-inner">
|
| 41 |
+
<div class="flashcard-front">
|
| 42 |
+
Band 6
|
| 43 |
+
</div>
|
| 44 |
+
<div class="flashcard-back">
|
| 45 |
+
Information and ideas are generally arranged coherently and there is a clear overall progression. Cohesive devices are used to some good effect but cohesion within and/or between sentences may be faulty or mechanical due to misuse, overuse or omission. The use of reference and substitution may lack flexibility or clarity and result in some repetition or error. Paragraphing may not always be logical and/or the central topic may not always be clear.
|
| 46 |
+
</div>
|
| 47 |
+
</div>
|
| 48 |
+
</div>
|
| 49 |
+
<div class="flashcard">
|
| 50 |
+
<div class="flashcard-inner">
|
| 51 |
+
<div class="flashcard-front">
|
| 52 |
+
Band 5
|
| 53 |
+
</div>
|
| 54 |
+
<div class="flashcard-back">
|
| 55 |
+
Organisation is evident but is not wholly logical and there may be a lack of overall progression. Nevertheless, there is a sense of underlying coherence to the response. The relationship of ideas can be followed but the sentences are not fluently linked to each other. There may be limited/overuse of cohesive devices with some inaccuracy. The writing may be repetitive due to inadequate and/or inaccurate use of reference and substitution. Paragraphing may be inadequate or missing.
|
| 56 |
+
</div>
|
| 57 |
+
</div>
|
| 58 |
+
</div>
|
| 59 |
+
<div class="flashcard">
|
| 60 |
+
<div class="flashcard-inner">
|
| 61 |
+
<div class="flashcard-front">
|
| 62 |
+
Band 4
|
| 63 |
+
</div>
|
| 64 |
+
<div class="flashcard-back">
|
| 65 |
+
Information and ideas are evident but not arranged coherently and there is no clear progression within the response. Relationships between ideas can be unclear and/or inadequately marked. There is some use of basic cohesive devices, which may be inaccurate or repetitive. There is inaccurate use or a lack of substitution or referencing. There may be no paragraphing and/or no clear main topic within paragraphs.
|
| 66 |
+
</div>
|
| 67 |
+
</div>
|
| 68 |
+
</div>
|
| 69 |
+
<div class="flashcard">
|
| 70 |
+
<div class="flashcard-inner">
|
| 71 |
+
<div class="flashcard-front">
|
| 72 |
+
Band 3
|
| 73 |
+
</div>
|
| 74 |
+
<div class="flashcard-back">
|
| 75 |
+
There is no apparent logical organisation. Ideas are discernible but difficult to relate to each other. There is minimal use of sequencers or cohesive devices. Those used do not necessarily indicate a logical relationship between ideas. There is difficulty in identifying referencing. Any attempts at paragraphing are unhelpful.
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
</div>
|
| 79 |
+
<div class="flashcard">
|
| 80 |
+
<div class="flashcard-inner">
|
| 81 |
+
<div class="flashcard-front">
|
| 82 |
+
Band 2
|
| 83 |
+
</div>
|
| 84 |
+
<div class="flashcard-back">
|
| 85 |
+
There is little evidence of control of organisational features.
|
| 86 |
+
</div>
|
| 87 |
+
</div>
|
| 88 |
+
</div>
|
| 89 |
+
<div class="flashcard">
|
| 90 |
+
<div class="flashcard-inner">
|
| 91 |
+
<div class="flashcard-front">
|
| 92 |
+
Band 1
|
| 93 |
+
</div>
|
| 94 |
+
<div class="flashcard-back">
|
| 95 |
+
Responses of 20 words or fewer are rated at Band 1. There is no apparent control of organisational features.
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
<a href="{{ url_for('infer.rubric_explanation') }}" class="btn btn-primary">Go Back to Rubric Explanation Page</a>
|
| 101 |
+
</div>
|
| 102 |
+
{% endblock %}
|
| 103 |
+
|
| 104 |
+
{% block extra_css %}
|
| 105 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/flashcards.css') }}">
|
| 106 |
+
{% endblock %}
|
my_app/templates/dashboard.html
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="container mt-4">
|
| 5 |
+
<h2 class="center-text">Dashboard</h2>
|
| 6 |
+
<form method="POST" action="{{ url_for('dashboard.delete_histories') }}">
|
| 7 |
+
<div class="row mt-4">
|
| 8 |
+
<div class="col-lg-6 mb-4">
|
| 9 |
+
<h4>Rubric Score Dynamics</h4>
|
| 10 |
+
<canvas id="RubricChart"></canvas>
|
| 11 |
+
</div>
|
| 12 |
+
<div class="col-lg-6 mb-4">
|
| 13 |
+
<h4>Number of Essays Checked per Day</h4>
|
| 14 |
+
<canvas id="EssayCountChart"></canvas>
|
| 15 |
+
</div>
|
| 16 |
+
</div>
|
| 17 |
+
<table class="table">
|
| 18 |
+
<thead>
|
| 19 |
+
<tr>
|
| 20 |
+
<th scope="col">#</th>
|
| 21 |
+
<th scope="col"><input id="select-all" type="checkbox" onclick="selectAll(this)"></th>
|
| 22 |
+
<th scope="col">Time</th>
|
| 23 |
+
<th scope="col">Topic</th>
|
| 24 |
+
<th scope="col">Essay</th>
|
| 25 |
+
<th scope="col">Task Response</th>
|
| 26 |
+
<th scope="col">Coherence and Cohesion</th>
|
| 27 |
+
<th scope="col">Lexical Resource</th>
|
| 28 |
+
<th scope="col">Grammatical Range and Accuracy</th>
|
| 29 |
+
</tr>
|
| 30 |
+
</thead>
|
| 31 |
+
<tbody>
|
| 32 |
+
{% for history in histories %}
|
| 33 |
+
<tr>
|
| 34 |
+
<td>{{ loop.index }}</td>
|
| 35 |
+
<td><input type="checkbox" name="history_ids" value="{{ history.id }}"></td>
|
| 36 |
+
<td>{{ history.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
| 37 |
+
<td onclick="showModal('{{ history.topic }}')">{{ history.topic | truncate(10) }}</td>
|
| 38 |
+
<td onclick="showModal('{{ history.essay }}')">{{ history.essay | truncate(100) }}</td>
|
| 39 |
+
<td>{{ history.score_tr | int }}</td>
|
| 40 |
+
<td>{{ history.score_cc | int }}</td>
|
| 41 |
+
<td>{{ history.score_lr | int }}</td>
|
| 42 |
+
<td>{{ history.score_gra | int }}</td>
|
| 43 |
+
</tr>
|
| 44 |
+
{% endfor %}
|
| 45 |
+
</tbody>
|
| 46 |
+
</table>
|
| 47 |
+
<div class="text-center">
|
| 48 |
+
<button type="submit" class="btn btn-danger">Delete Selected</button>
|
| 49 |
+
</div>
|
| 50 |
+
</form>
|
| 51 |
+
<nav aria-label="Page navigation example" style="margin-top: 50px;">
|
| 52 |
+
<ul class="pagination justify-content-center">
|
| 53 |
+
{% if pagination.has_prev %}
|
| 54 |
+
<li class="page-item">
|
| 55 |
+
<a class="page-link" href="{{ url_for('dashboard.dashboard', page=pagination.prev_num) }}">Previous</a>
|
| 56 |
+
</li>
|
| 57 |
+
{% else %}
|
| 58 |
+
<li class="page-item disabled"><span class="page-link">Previous</span></li>
|
| 59 |
+
{% endif %}
|
| 60 |
+
|
| 61 |
+
{% for page in pagination.iter_pages(left_edge=2, left_current=1, right_current=2, right_edge=2) %}
|
| 62 |
+
<li class="page-item {{ 'active' if page == pagination.page else '' }}">
|
| 63 |
+
{% if page %}
|
| 64 |
+
<a class="page-link" href="{{ url_for('dashboard.dashboard', page=page) }}">{{ page }}</a>
|
| 65 |
+
{% else %}
|
| 66 |
+
<span class="page-link">...</span>
|
| 67 |
+
{% endif %}
|
| 68 |
+
</li>
|
| 69 |
+
{% endfor %}
|
| 70 |
+
|
| 71 |
+
{% if pagination.has_next %}
|
| 72 |
+
<li class="page-item">
|
| 73 |
+
<a class="page-link" href="{{ url_for('dashboard.dashboard', page=pagination.next_num) }}">Next</a>
|
| 74 |
+
</li>
|
| 75 |
+
{% else %}
|
| 76 |
+
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
| 77 |
+
{% endif %}
|
| 78 |
+
</ul>
|
| 79 |
+
</nav>
|
| 80 |
+
</div>
|
| 81 |
+
<div class="modal fade" id="contentModal" tabindex="-1" role="dialog">
|
| 82 |
+
<div class="modal-dialog modal-lg" role="document">
|
| 83 |
+
<div class="modal-content">
|
| 84 |
+
<div class="modal-header">
|
| 85 |
+
<h5 class="modal-title">Full Content</h5>
|
| 86 |
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
| 87 |
+
<span aria-hidden="true">×</span>
|
| 88 |
+
</button>
|
| 89 |
+
</div>
|
| 90 |
+
<div class="modal-body" style="max-height:400px; overflow-y:auto;">
|
| 91 |
+
<p id="content-text"></p>
|
| 92 |
+
</div>
|
| 93 |
+
</div>
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
|
| 98 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 99 |
+
<script>
|
| 100 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 101 |
+
const scoresTR = JSON.parse('{{ scores_tr|tojson|safe }}');
|
| 102 |
+
const scoresCC = JSON.parse('{{ scores_cc|tojson|safe }}');
|
| 103 |
+
const scoresLR = JSON.parse('{{ scores_lr|tojson|safe }}');
|
| 104 |
+
const scoresGRA = JSON.parse('{{ scores_gra|tojson|safe }}');
|
| 105 |
+
const creationTimes = JSON.parse('{{ creation_times|tojson|safe }}');
|
| 106 |
+
initializeCharts(scoresTR, scoresCC, scoresLR, scoresGRA, creationTimes);
|
| 107 |
+
});
|
| 108 |
+
</script>
|
| 109 |
+
<script src="{{ url_for('static', filename='js/dashboard_charts.js') }}"></script>
|
| 110 |
+
|
| 111 |
+
{% endblock %}
|
my_app/templates/error.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Error{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<div class="container mt-4">
|
| 7 |
+
<h2>Error</h2>
|
| 8 |
+
<div class="alert alert-danger" role="alert">
|
| 9 |
+
{{ error_message }}
|
| 10 |
+
</div>
|
| 11 |
+
<a href="{{ url_for('infer.index') }}" class="btn btn-primary">Go Back to Home</a>
|
| 12 |
+
</div>
|
| 13 |
+
{% endblock %}
|
my_app/templates/grammatical_range_accuracy.html
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="container mt-4">
|
| 5 |
+
<h2>Grammatical Range & Accuracy</h2>
|
| 6 |
+
<div class="flashcards">
|
| 7 |
+
<div class="flashcard">
|
| 8 |
+
<div class="flashcard-inner">
|
| 9 |
+
<div class="flashcard-front">
|
| 10 |
+
Band 9
|
| 11 |
+
</div>
|
| 12 |
+
<div class="flashcard-back">
|
| 13 |
+
A wide range of structures is used with full flexibility and control. Punctuation and grammar are used appropriately throughout. Minor errors are extremely rare and have minimal impact on communication.
|
| 14 |
+
</div>
|
| 15 |
+
</div>
|
| 16 |
+
</div>
|
| 17 |
+
<div class="flashcard">
|
| 18 |
+
<div class="flashcard-inner">
|
| 19 |
+
<div class="flashcard-front">
|
| 20 |
+
Band 8
|
| 21 |
+
</div>
|
| 22 |
+
<div class="flashcard-back">
|
| 23 |
+
A wide range of structures is flexibly and accurately used. The majority of sentences are error-free, and punctuation is well managed. Occasional, non-systematic errors and inappropriacies occur, but have minimal impact on communication.
|
| 24 |
+
</div>
|
| 25 |
+
</div>
|
| 26 |
+
</div>
|
| 27 |
+
<div class="flashcard">
|
| 28 |
+
<div class="flashcard-inner">
|
| 29 |
+
<div class="flashcard-front">
|
| 30 |
+
Band 7
|
| 31 |
+
</div>
|
| 32 |
+
<div class="flashcard-back">
|
| 33 |
+
A variety of complex structures is used with some flexibility and accuracy. Grammar and punctuation are generally well controlled, and error-free sentences are frequent. A few errors in grammar may persist, but these do not impede communication.
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
</div>
|
| 37 |
+
<div class="flashcard">
|
| 38 |
+
<div class="flashcard-inner">
|
| 39 |
+
<div class="flashcard-front">
|
| 40 |
+
Band 6
|
| 41 |
+
</div>
|
| 42 |
+
<div class="flashcard-back">
|
| 43 |
+
A mix of simple and complex sentence forms is used but flexibility is limited. Examples of more complex structures are not marked by the same level of accuracy as in simple structures. Errors in grammar and punctuation occur, but rarely impede communication.
|
| 44 |
+
</div>
|
| 45 |
+
</div>
|
| 46 |
+
</div>
|
| 47 |
+
<div class="flashcard">
|
| 48 |
+
<div class="flashcard-inner">
|
| 49 |
+
<div class="flashcard-front">
|
| 50 |
+
Band 5
|
| 51 |
+
</div>
|
| 52 |
+
<div class="flashcard-back">
|
| 53 |
+
The range of structures is limited and rather repetitive. Although complex sentences are attempted, they tend to be faulty, and the greatest accuracy is achieved on simple sentences. Grammatical errors may be frequent and cause some difficulty for the reader. Punctuation may be faulty.
|
| 54 |
+
</div>
|
| 55 |
+
</div>
|
| 56 |
+
</div>
|
| 57 |
+
<div class="flashcard">
|
| 58 |
+
<div class="flashcard-inner">
|
| 59 |
+
<div class="flashcard-front">
|
| 60 |
+
Band 4
|
| 61 |
+
</div>
|
| 62 |
+
<div class="flashcard-back">
|
| 63 |
+
A very limited range of structures is used. Subordinate clauses are rare and simple sentences predominate. Some structures are produced accurately but grammatical errors are frequent and may impede meaning. Punctuation is often faulty or inadequate.
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
<div class="flashcard">
|
| 68 |
+
<div class="flashcard-inner">
|
| 69 |
+
<div class="flashcard-front">
|
| 70 |
+
Band 3
|
| 71 |
+
</div>
|
| 72 |
+
<div class="flashcard-back">
|
| 73 |
+
Sentence forms are attempted, but errors in grammar and punctuation predominate (except in memorised phrases or those taken from the input material). This prevents most meaning from coming through.
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
<div class="flashcard">
|
| 78 |
+
<div class="flashcard-inner">
|
| 79 |
+
<div class="flashcard-front">
|
| 80 |
+
Band 2
|
| 81 |
+
</div>
|
| 82 |
+
<div class="flashcard-back">
|
| 83 |
+
There is little or no evidence of sentence forms (except in memorised phrases).
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
<div class="flashcard">
|
| 88 |
+
<div class="flashcard-inner">
|
| 89 |
+
<div class="flashcard-front">
|
| 90 |
+
Band 1
|
| 91 |
+
</div>
|
| 92 |
+
<div class="flashcard-back">
|
| 93 |
+
Responses of 20 words or fewer are rated at Band 1. No rateable language is evident.
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
<a href="{{ url_for('infer.rubric_explanation') }}" class="btn btn-primary">Go Back to Rubric Explanation Page</a>
|
| 99 |
+
</div>
|
| 100 |
+
{% endblock %}
|
| 101 |
+
|
| 102 |
+
{% block extra_css %}
|
| 103 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/flashcards.css') }}">
|
| 104 |
+
{% endblock %}
|
my_app/templates/index.html
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Home Page{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<div class="container mt-4">
|
| 7 |
+
<!-- Sidebar with Toggle Button Inside -->
|
| 8 |
+
<div id="sidebar" class="sidebar">
|
| 9 |
+
<!-- Sidebar Toggle Button -->
|
| 10 |
+
<button class="btn btn-primary" id="toggle-sidebar" style="position: absolute; top: 350px; right: -45px; z-index: 999;">☰</button>
|
| 11 |
+
|
| 12 |
+
<!-- Sidebar Content -->
|
| 13 |
+
<div class="sidebar-content">
|
| 14 |
+
<h3 id="countdown-timer" contenteditable="true">5:00</h3> <!-- Starting value -->
|
| 15 |
+
<button class="btn btn-info mt-2 mr-2" id="start-countdown">Start</button>
|
| 16 |
+
<button class="btn btn-danger mt-2" id="stop-countdown">Stop</button>
|
| 17 |
+
<div class="mt-4">
|
| 18 |
+
<label for="draft-text">Draft Text:</label>
|
| 19 |
+
<textarea id="draft-text" class="form-control" rows="8" placeholder="Type your draft here..."></textarea>
|
| 20 |
+
</div>
|
| 21 |
+
</div>
|
| 22 |
+
</div>
|
| 23 |
+
|
| 24 |
+
<!-- Main Content: Essay Topic and Essay Content -->
|
| 25 |
+
<div style="margin-left: 30px;"> <!-- Adjust based on sidebar width -->
|
| 26 |
+
<h1 class="center-text">Enter Topic and Essay for Prediction</h1>
|
| 27 |
+
<form method="post" class="mt-4" id="essayForm">
|
| 28 |
+
<div class="mb-3">
|
| 29 |
+
<label for="topic" class="form-label">Essay Topic:</label>
|
| 30 |
+
<input type="text" id="topic" name="topic" class="form-control" autocomplete="off">
|
| 31 |
+
</div>
|
| 32 |
+
<div class="mb-3">
|
| 33 |
+
<label for="essay" class="form-label">Essay Content:</label>
|
| 34 |
+
<textarea style="background-color:rgba(10, 9, 9, 0.5); color:rgb(255, 255, 255); border: 1px solid #100f0f;" id="essay" name="essay" class="form-control" rows="8" autocomplete="off"></textarea>
|
| 35 |
+
</div>
|
| 36 |
+
<div class="btn-center">
|
| 37 |
+
<button type="submit" class="btn btn-primary">Submit</button>
|
| 38 |
+
</div>
|
| 39 |
+
<input type="hidden" id="isempty" value="{{ isempty }}">
|
| 40 |
+
</form>
|
| 41 |
+
</div>
|
| 42 |
+
|
| 43 |
+
<!-- Alert Modal -->
|
| 44 |
+
<div class="modal fade" id="alertModal" tabindex="-1" role="dialog" aria-labelledby="alertModalLabel" aria-hidden="true">
|
| 45 |
+
<div class="modal-dialog" role="document">
|
| 46 |
+
<div class="modal-content">
|
| 47 |
+
<div class="modal-header">
|
| 48 |
+
<h5 class="modal-title" id="alertModalLabel">Alert</h5>
|
| 49 |
+
<button type="button" class="close" data-dismiss="modal">
|
| 50 |
+
<span aria-hidden="true">×</span>
|
| 51 |
+
</button>
|
| 52 |
+
</div>
|
| 53 |
+
<div class="modal-body">
|
| 54 |
+
Please provide both a topic and an essay text.
|
| 55 |
+
</div>
|
| 56 |
+
<div class="modal-footer">
|
| 57 |
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">OK</button>
|
| 58 |
+
</div>
|
| 59 |
+
</div>
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
{% endblock %}
|
| 64 |
+
|
| 65 |
+
{% block extra_scripts %}
|
| 66 |
+
<script src="{{ url_for('static', filename='js/home_page.js') }}"></script>
|
| 67 |
+
{% endblock %}
|
my_app/templates/infer.html
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Prediction Results{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<div class="container mt-4">
|
| 7 |
+
<!-- Topic Card (same width as comparison) -->
|
| 8 |
+
<div class="row">
|
| 9 |
+
<div class="col-12 mb-4">
|
| 10 |
+
<div class="card border-primary">
|
| 11 |
+
<div class="card-header bg-secondary text-white">
|
| 12 |
+
<h4 class="mb-0">Topic</h4>
|
| 13 |
+
</div>
|
| 14 |
+
<div class="card-body">
|
| 15 |
+
<p class="card-text" style="color: black;">{{ topic }}</p>
|
| 16 |
+
</div>
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
</div>
|
| 20 |
+
|
| 21 |
+
<!-- Essay Comparison -->
|
| 22 |
+
<div class="row">
|
| 23 |
+
<div class="col-12 mb-4">
|
| 24 |
+
<div class="card border-primary">
|
| 25 |
+
<div class="card-header bg-secondary text-white">
|
| 26 |
+
<h4 class="mb-0">Essay Comparison</h4>
|
| 27 |
+
</div>
|
| 28 |
+
<div class="card-body p-0">
|
| 29 |
+
<div class="row g-0">
|
| 30 |
+
<!-- Original Essay -->
|
| 31 |
+
<div class="col-md-6 border-end">
|
| 32 |
+
<div class="p-3">
|
| 33 |
+
<h5 class="text-center text-primary">Original Essay</h5>
|
| 34 |
+
<div class="original-essay" id="originalEssay" style="color: black; height: 400px; overflow-y: auto;">
|
| 35 |
+
{{ essay|safe }}
|
| 36 |
+
</div>
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<!-- Corrected Essay -->
|
| 41 |
+
<div class="col-md-6">
|
| 42 |
+
<div class="p-3">
|
| 43 |
+
<h5 class="text-center text-success">Corrected Essay</h5>
|
| 44 |
+
<div class="corrected-essay" id="correctedEssay" style="color: black; height: 400px; overflow-y: auto;">
|
| 45 |
+
{{ corrected_essay|safe }}
|
| 46 |
+
</div>
|
| 47 |
+
</div>
|
| 48 |
+
</div>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<!-- Rubric Cards (now below comparison) -->
|
| 56 |
+
<div class="row">
|
| 57 |
+
{% set rubrics = [
|
| 58 |
+
('Task Response', result[0], tr , 'task_response'),
|
| 59 |
+
('Coherence and Cohesion', result[1], cc, 'coherence_cohesion'),
|
| 60 |
+
('Lexical Resource', result[2], lr, 'lexical_resource'),
|
| 61 |
+
('Grammatical Range and Accuracy', result[3], gra, 'grammatical_accuracy')
|
| 62 |
+
] %}
|
| 63 |
+
|
| 64 |
+
{% for rubric, score, explanation, link in rubrics %}
|
| 65 |
+
<div class="col-md-3 mb-4"> <!-- Changed to col-md-3 for 4 cards in a row -->
|
| 66 |
+
<div class="card h-100 bg-light border-info"> <!-- Changed to light bg with info border -->
|
| 67 |
+
<div class="card-body">
|
| 68 |
+
<h5 class="card-title" style="color: black;">{{ rubric }}</h5>
|
| 69 |
+
<div class="score-display mb-3 text-center"> <!-- Centered score -->
|
| 70 |
+
<span class="score">{{ score|round|int }}</span>/9
|
| 71 |
+
</div>
|
| 72 |
+
<p class="card-text text-truncate-multiline text-dark"> <!-- Dark text for better readability -->
|
| 73 |
+
{{ explanation }}
|
| 74 |
+
</p>
|
| 75 |
+
<div class="d-flex justify-content-between mt-3">
|
| 76 |
+
<button type="button" class="btn btn-sm btn-outline-info" data-bs-toggle="modal" data-bs-target="#feedbackModal{{ loop.index }}">
|
| 77 |
+
View Feedback
|
| 78 |
+
</button>
|
| 79 |
+
<a href="{{ url_for('infer.rubric_explanation', rubric=link) }}" class="btn btn-sm btn-outline-info">
|
| 80 |
+
Details
|
| 81 |
+
</a>
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
<!-- Modal for each feedback item -->
|
| 87 |
+
<div class="modal fade" id="feedbackModal{{ loop.index }}" tabindex="-1" aria-labelledby="feedbackModalLabel{{ loop.index }}" aria-hidden="true">
|
| 88 |
+
<div class="modal-dialog modal-dialog-centered">
|
| 89 |
+
<div class="modal-content">
|
| 90 |
+
<div class="modal-header bg-info text-white">
|
| 91 |
+
<h5 class="modal-title" id="feedbackModalLabel{{ loop.index }}">{{ rubric }} Feedback</h5>
|
| 92 |
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
| 93 |
+
</div>
|
| 94 |
+
<div class="modal-body" style="max-height: 60vh; overflow-y: auto;">
|
| 95 |
+
{{ explanation }}
|
| 96 |
+
</div>
|
| 97 |
+
<div class="modal-footer">
|
| 98 |
+
<button type="button" class="btn btn-info" data-bs-dismiss="modal">Close</button>
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
{% endfor %}
|
| 105 |
+
</div>
|
| 106 |
+
|
| 107 |
+
<div class="text-center mt-4">
|
| 108 |
+
<a href="{{ url_for('infer.index') }}" class="btn btn-primary">Submit Another Essay</a>
|
| 109 |
+
</div>
|
| 110 |
+
</div>
|
| 111 |
+
{% endblock %}
|
| 112 |
+
|
| 113 |
+
{% block extra_scripts %}
|
| 114 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsdiff/5.0.1/diff.min.js"></script>
|
| 115 |
+
<script>
|
| 116 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 117 |
+
try {
|
| 118 |
+
const origEl = document.getElementById('originalEssay');
|
| 119 |
+
const corrEl = document.getElementById('correctedEssay');
|
| 120 |
+
if (!origEl || !corrEl) return;
|
| 121 |
+
|
| 122 |
+
const originalText = origEl.textContent;
|
| 123 |
+
const correctedText = corrEl.textContent;
|
| 124 |
+
|
| 125 |
+
// 2) do the diff
|
| 126 |
+
const diff = JsDiff.diffWordsWithSpace(originalText, correctedText);
|
| 127 |
+
|
| 128 |
+
// if nothing really changed, bail out
|
| 129 |
+
if (diff.length <= 1) return;
|
| 130 |
+
|
| 131 |
+
// 3) clear out the old
|
| 132 |
+
corrEl.innerHTML = '';
|
| 133 |
+
|
| 134 |
+
// 4) re‐render with spans
|
| 135 |
+
diff.forEach(part => {
|
| 136 |
+
const span = document.createElement('span');
|
| 137 |
+
span.textContent = part.value;
|
| 138 |
+
if (part.added) span.classList.add('diff-ins');
|
| 139 |
+
if (part.removed) span.classList.add('diff-del');
|
| 140 |
+
corrEl.appendChild(span);
|
| 141 |
+
});
|
| 142 |
+
}
|
| 143 |
+
catch (e) {
|
| 144 |
+
console.error('Diff rendering error:', e);
|
| 145 |
+
}
|
| 146 |
+
});
|
| 147 |
+
</script>
|
| 148 |
+
<style>
|
| 149 |
+
.diff-ins {border-bottom:2px solid #28a745; }
|
| 150 |
+
.diff-del {text-decoration: line-through; color: #dc3545; }
|
| 151 |
+
|
| 152 |
+
/* Custom styles */
|
| 153 |
+
.score-display {
|
| 154 |
+
font-size: 1.2rem;
|
| 155 |
+
font-weight: bold;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
.score {
|
| 159 |
+
color: #0d6efd; /* Changed to Bootstrap primary blue */
|
| 160 |
+
font-size: 1.5rem;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
.text-truncate-multiline {
|
| 164 |
+
display: -webkit-box;
|
| 165 |
+
-webkit-line-clamp: 3;
|
| 166 |
+
-webkit-box-orient: vertical;
|
| 167 |
+
overflow: hidden;
|
| 168 |
+
text-overflow: ellipsis;
|
| 169 |
+
min-height: 4.5em;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.card {
|
| 173 |
+
transition: transform 0.2s;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.card:hover {
|
| 177 |
+
transform: translateY(-5px);
|
| 178 |
+
box-shadow: 0 4px 15px rgba(13, 110, 253, 0.25); /* Primary blue shadow */
|
| 179 |
+
}
|
| 180 |
+
</style>
|
| 181 |
+
{% endblock %}
|
my_app/templates/lexical_resource.html
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="container mt-4">
|
| 5 |
+
<h2>Lexical Resource</h2>
|
| 6 |
+
<div class="flashcards">
|
| 7 |
+
<div class="flashcard">
|
| 8 |
+
<div class="flashcard-inner">
|
| 9 |
+
<div class="flashcard-front">
|
| 10 |
+
Band 9
|
| 11 |
+
</div>
|
| 12 |
+
<div class="flashcard-back">
|
| 13 |
+
Full flexibility and precise use are widely evident. A wide range of vocabulary is used accurately and appropriately with very natural and sophisticated control of lexical features. Minor errors in spelling and word formation are extremely rare and have minimal impact on communication.
|
| 14 |
+
</div>
|
| 15 |
+
</div>
|
| 16 |
+
</div>
|
| 17 |
+
<div class="flashcard">
|
| 18 |
+
<div class="flashcard-inner">
|
| 19 |
+
<div class="flashcard-front">
|
| 20 |
+
Band 8
|
| 21 |
+
</div>
|
| 22 |
+
<div class="flashcard-back">
|
| 23 |
+
A wide resource is fluently and flexibly used to convey precise meanings. There is skilful use of uncommon and/or idiomatic items when appropriate, despite occasional inaccuracies in word choice and collocation. Occasional errors in spelling and/or word formation may occur, but have minimal impact on communication.
|
| 24 |
+
</div>
|
| 25 |
+
</div>
|
| 26 |
+
</div>
|
| 27 |
+
<div class="flashcard">
|
| 28 |
+
<div class="flashcard-inner">
|
| 29 |
+
<div class="flashcard-front">
|
| 30 |
+
Band 7
|
| 31 |
+
</div>
|
| 32 |
+
<div class="flashcard-back">
|
| 33 |
+
The resource is sufficient to allow some flexibility and precision. There is some ability to use less common and/or idiomatic items. An awareness of style and collocation is evident, though inappropriacies occur. There are only a few errors in spelling and/or word formation and they do not detract from overall clarity.
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
</div>
|
| 37 |
+
<div class="flashcard">
|
| 38 |
+
<div class="flashcard-inner">
|
| 39 |
+
<div class="flashcard-front">
|
| 40 |
+
Band 6
|
| 41 |
+
</div>
|
| 42 |
+
<div class="flashcard-back">
|
| 43 |
+
The resource is generally adequate and appropriate for the task. The meaning is generally clear in spite of a rather restricted range or a lack of precision in word choice. If the writer is a risk-taker, there will be a wider range of vocabulary used but higher degrees of inaccuracy or inappropriacy. There are some errors in spelling and/or word formation, but these do not impede communication.
|
| 44 |
+
</div>
|
| 45 |
+
</div>
|
| 46 |
+
</div>
|
| 47 |
+
<div class="flashcard">
|
| 48 |
+
<div class="flashcard-inner">
|
| 49 |
+
<div class="flashcard-front">
|
| 50 |
+
Band 5
|
| 51 |
+
</div>
|
| 52 |
+
<div class="flashcard-back">
|
| 53 |
+
The resource is limited but minimally adequate for the task. Simple vocabulary may be used accurately but the range does not permit much variation in expression. There may be frequent lapses in the appropriacy of word choice and a lack of flexibility is apparent in frequent simplifications and/or repetitions. Errors in spelling and/or word formation may be noticeable and may cause some difficulty for the reader.
|
| 54 |
+
</div>
|
| 55 |
+
</div>
|
| 56 |
+
</div>
|
| 57 |
+
<div class="flashcard">
|
| 58 |
+
<div class="flashcard-inner">
|
| 59 |
+
<div class="flashcard-front">
|
| 60 |
+
Band 4
|
| 61 |
+
</div>
|
| 62 |
+
<div class="flashcard-back">
|
| 63 |
+
The resource is limited and inadequate for or unrelated to the task. Vocabulary is basic and may be used repetitively. There may be inappropriate use of lexical chunks (e.g. memorised phrases, formulaic language and/or language from the input material). Inappropriate word choice and/or errors in word formation and/or in spelling may impede meaning.
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
<div class="flashcard">
|
| 68 |
+
<div class="flashcard-inner">
|
| 69 |
+
<div class="flashcard-front">
|
| 70 |
+
Band 3
|
| 71 |
+
</div>
|
| 72 |
+
<div class="flashcard-back">
|
| 73 |
+
The resource is inadequate (which may be due to the response being significantly underlength). Possible over-dependence on input material or memorised language. Control of word choice and/or spelling is very limited, and errors predominate. These errors may severely impede meaning.
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
<div class="flashcard">
|
| 78 |
+
<div class="flashcard-inner">
|
| 79 |
+
<div class="flashcard-front">
|
| 80 |
+
Band 2
|
| 81 |
+
</div>
|
| 82 |
+
<div class="flashcard-back">
|
| 83 |
+
The resource is extremely limited with few recognisable strings, apart from memorised phrases. There is no apparent control of word formation and/or spelling.
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
<div class="flashcard">
|
| 88 |
+
<div class="flashcard-inner">
|
| 89 |
+
<div class="flashcard-front">
|
| 90 |
+
Band 1
|
| 91 |
+
</div>
|
| 92 |
+
<div class="flashcard-back">
|
| 93 |
+
Responses of 20 words or fewer are rated at Band 1. No resource is apparent, except for a few isolated words.
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
<a href="{{ url_for('infer.rubric_explanation') }}" class="btn btn-primary">Go Back to Rubric Explanation Page</a>
|
| 99 |
+
</div>
|
| 100 |
+
{% endblock %}
|
| 101 |
+
|
| 102 |
+
{% block extra_css %}
|
| 103 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/flashcards.css') }}">
|
| 104 |
+
{% endblock %}
|
my_app/templates/login.html
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="container">
|
| 5 |
+
<h2 class="center-text">Login</h2>
|
| 6 |
+
<form method="POST">
|
| 7 |
+
<div class="mb-3">
|
| 8 |
+
<label for="email" class="form-label">Email:</label>
|
| 9 |
+
<input type="email" class="form-control" id="email" autocomplete="off" name="email" required>
|
| 10 |
+
</div>
|
| 11 |
+
<div class="mb-3">
|
| 12 |
+
<label for="password" class="form-label">Password:</label>
|
| 13 |
+
<input type="password" class="form-control" id="password" autocomplete="off" name="password" required>
|
| 14 |
+
</div>
|
| 15 |
+
<input type="hidden" id="message" value="{{ message }}">
|
| 16 |
+
<div class="btn-center">
|
| 17 |
+
<button type="submit" class="btn btn-primary">Login</button>
|
| 18 |
+
</div>
|
| 19 |
+
</form>
|
| 20 |
+
<!-- Modal HTML -->
|
| 21 |
+
<div class="modal fade" id="loginAlertModal" tabindex="-1" role="dialog" aria-labelledby="loginModalLabel" aria-hidden="true">
|
| 22 |
+
<div class="modal-dialog" role="document">
|
| 23 |
+
<div class="modal-content">
|
| 24 |
+
<div class="modal-header">
|
| 25 |
+
<h5 class="modal-title" id="loginModalLabel">Login Alert</h5>
|
| 26 |
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
| 27 |
+
<span aria-hidden="true">×</span>
|
| 28 |
+
</button>
|
| 29 |
+
</div>
|
| 30 |
+
<div class="modal-body">
|
| 31 |
+
<!-- Message will be inserted here via JavaScript -->
|
| 32 |
+
</div>
|
| 33 |
+
</div>
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
</div>
|
| 37 |
+
{% endblock %}
|
| 38 |
+
|
| 39 |
+
{% block extra_scripts %}
|
| 40 |
+
<script>
|
| 41 |
+
$(document).ready(function() {
|
| 42 |
+
var message = $('#message').val();
|
| 43 |
+
if (message) {
|
| 44 |
+
$('#loginAlertModal .modal-body').text(message);
|
| 45 |
+
$('#loginAlertModal').modal('show');
|
| 46 |
+
}
|
| 47 |
+
});
|
| 48 |
+
</script>
|
| 49 |
+
{% endblock %}
|
my_app/templates/register.html
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Register{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<div class="container">
|
| 7 |
+
<h2 class="center-text">Register</h2>
|
| 8 |
+
<form method="POST" id="registrationForm">
|
| 9 |
+
<div class="mb-3">
|
| 10 |
+
<label for="email" class="form-label">Email:</label>
|
| 11 |
+
<input type="email" class="form-control" id="email" autocomplete="off" name="email" required>
|
| 12 |
+
<small id="emailHelp" class="form-text text-muted">Your email here. Example: example@domain.com</small>
|
| 13 |
+
</div>
|
| 14 |
+
<div class="mb-3">
|
| 15 |
+
<label for="nickname" class="form-label">Nickname:</label>
|
| 16 |
+
<input type="text" class="form-control" id="nickname" autocomplete="off" name="nickname" required>
|
| 17 |
+
<small id="nicknameHelp" class="form-text text-muted">Your nickname here. Example: Nickname1</small>
|
| 18 |
+
</div>
|
| 19 |
+
<div class="mb-3">
|
| 20 |
+
<label for="password" class="form-label">Password:</label>
|
| 21 |
+
<input type="password" class="form-control" id="password" autocomplete="off" name="password" required>
|
| 22 |
+
<small id="passwordHelp" class="form-text text-muted">Your password should be more than 8 symbols.</small>
|
| 23 |
+
</div>
|
| 24 |
+
<div class="mb-3">
|
| 25 |
+
<label for="repeatPassword" class="form-label">Repeat Password:</label>
|
| 26 |
+
<input type="password" class="form-control" id="repeatPassword" autocomplete="off" name="repeatPassword" required>
|
| 27 |
+
</div>
|
| 28 |
+
<input type="hidden" id="message" value="{{ message }}">
|
| 29 |
+
<div class="btn-center">
|
| 30 |
+
<button type="submit" class="btn btn-primary">Register</button>
|
| 31 |
+
</div>
|
| 32 |
+
</form>
|
| 33 |
+
|
| 34 |
+
<!-- Registration Alert Modal -->
|
| 35 |
+
<div class="modal fade" id="registrationAlertModal" tabindex="-1" role="dialog" aria-labelledby="registrationModalLabel" aria-hidden="true">
|
| 36 |
+
<div class="modal-dialog" role="document">
|
| 37 |
+
<div class="modal-content">
|
| 38 |
+
<div class="modal-header">
|
| 39 |
+
<h5 class="modal-title" id="registrationModalLabel">Registration Alert</h5>
|
| 40 |
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
| 41 |
+
<span aria-hidden="true">×</span>
|
| 42 |
+
</button>
|
| 43 |
+
</div>
|
| 44 |
+
<div class="modal-body">
|
| 45 |
+
<!-- Message will be inserted here via JavaScript -->
|
| 46 |
+
</div>
|
| 47 |
+
<div class="modal-footer">
|
| 48 |
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">OK</button>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
{% endblock %}
|
| 55 |
+
{% block extra_scripts %}
|
| 56 |
+
<script>
|
| 57 |
+
$(document).ready(function() {
|
| 58 |
+
// Show message if provided
|
| 59 |
+
var message = $('#message').val();
|
| 60 |
+
if (message) {
|
| 61 |
+
$('#registrationAlertModal .modal-body').text(message);
|
| 62 |
+
$('#registrationAlertModal').modal('show');
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
// Validate passwords before submission
|
| 66 |
+
$('#registrationForm').on('submit', function(e) {
|
| 67 |
+
var password = $('#password').val();
|
| 68 |
+
var repeatPassword = $('#repeatPassword').val();
|
| 69 |
+
if (password !== repeatPassword) {
|
| 70 |
+
e.preventDefault(); // Prevent form submission
|
| 71 |
+
$('#registrationAlertModal .modal-body').text('Passwords do not match!');
|
| 72 |
+
$('#registrationAlertModal').modal('show');
|
| 73 |
+
}
|
| 74 |
+
});
|
| 75 |
+
});
|
| 76 |
+
</script>
|
| 77 |
+
{% endblock %}
|
my_app/templates/rubric_explanation.html
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Rubric Explanation{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<div class="container mt-4">
|
| 7 |
+
<h2 class="center-text">Explanation</h2>
|
| 8 |
+
<div class="row text-center mb-2">
|
| 9 |
+
<div class="col-md-7">
|
| 10 |
+
<a href="{{ url_for('infer.task_response') }}" class="btn btn-primary mt-3">Task Response Explanation</a>
|
| 11 |
+
</div>
|
| 12 |
+
<div class="col-md-5">
|
| 13 |
+
<a href="{{ url_for('infer.coherence_cohesion') }}" class="btn btn-primary mt-3">Coherence and Cohesion Explanation</a>
|
| 14 |
+
</div>
|
| 15 |
+
</div>
|
| 16 |
+
<div class="row text-center mb-2">
|
| 17 |
+
<div class="col-md-7">
|
| 18 |
+
<a href="{{ url_for('infer.lexical_resource') }}" class="btn btn-primary mt-3">Lexical Resource Explanation</a>
|
| 19 |
+
</div>
|
| 20 |
+
<div class="col-md-5">
|
| 21 |
+
<a href="{{ url_for('infer.grammatical_range_accuracy') }}" class="btn btn-primary mt-3">Grammatical Range and Accuracy Explanation</a>
|
| 22 |
+
</div>
|
| 23 |
+
</div>
|
| 24 |
+
<div class="btn-center">
|
| 25 |
+
<a href="{{ url_for('infer.index') }}" class="btn btn-primary mt-3">Back</a>
|
| 26 |
+
</div>
|
| 27 |
+
</div>
|
| 28 |
+
{% endblock %}
|
my_app/templates/task_response.html
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="container mt-4">
|
| 5 |
+
<h2>Task Response</h2>
|
| 6 |
+
<div class="flashcards">
|
| 7 |
+
<div class="flashcard">
|
| 8 |
+
<div class="flashcard-inner">
|
| 9 |
+
<div class="flashcard-front">
|
| 10 |
+
Band 9
|
| 11 |
+
</div>
|
| 12 |
+
<div class="flashcard-back">
|
| 13 |
+
The prompt is appropriately addressed and explored in depth. A clear and fully developed position is presented which directly answers the question/s. Ideas are relevant, fully extended and well supported. Any lapses in content or support are extremely rare.
|
| 14 |
+
</div>
|
| 15 |
+
</div>
|
| 16 |
+
</div>
|
| 17 |
+
<div class="flashcard">
|
| 18 |
+
<div class="flashcard-inner">
|
| 19 |
+
<div class="flashcard-front">
|
| 20 |
+
Band 8
|
| 21 |
+
</div>
|
| 22 |
+
<div class="flashcard-back">
|
| 23 |
+
The prompt is appropriately and sufficiently addressed. A clear and well-developed position is presented in response to the question/s. Ideas are relevant, well extended and supported. There may be occasional omissions or lapses in content.
|
| 24 |
+
</div>
|
| 25 |
+
</div>
|
| 26 |
+
</div>
|
| 27 |
+
<div class="flashcard">
|
| 28 |
+
<div class="flashcard-inner">
|
| 29 |
+
<div class="flashcard-front">
|
| 30 |
+
Band 7
|
| 31 |
+
</div>
|
| 32 |
+
<div class="flashcard-back">
|
| 33 |
+
The main parts of the prompt are appropriately addressed. A clear and developed position is presented. Main ideas are extended and supported but there may be a tendency to over-generalise or there may be a lack of focus and precision in supporting ideas/material.
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
</div>
|
| 37 |
+
<div class="flashcard">
|
| 38 |
+
<div class="flashcard-inner">
|
| 39 |
+
<div class="flashcard-front">
|
| 40 |
+
Band 6
|
| 41 |
+
</div>
|
| 42 |
+
<div class="flashcard-back">
|
| 43 |
+
The main parts of the prompt are addressed (though some may be more fully covered than others). An appropriate format is used. A position is presented that is directly relevant to the prompt, although the conclusions drawn may be unclear, unjustified or repetitive.
|
| 44 |
+
</div>
|
| 45 |
+
</div>
|
| 46 |
+
</div>
|
| 47 |
+
<div class="flashcard">
|
| 48 |
+
<div class="flashcard-inner">
|
| 49 |
+
<div class="flashcard-front">
|
| 50 |
+
Band 5
|
| 51 |
+
</div>
|
| 52 |
+
<div class="flashcard-back">
|
| 53 |
+
The main parts of the prompt are incompletely addressed. The format may be inappropriate in places. The writer expresses a position, but the development is not always clear. Some main ideas are put forward, but they are limited and are not sufficiently developed and/or there may be irrelevant detail.
|
| 54 |
+
</div>
|
| 55 |
+
</div>
|
| 56 |
+
</div>
|
| 57 |
+
<div class="flashcard">
|
| 58 |
+
<div class="flashcard-inner">
|
| 59 |
+
<div class="flashcard-front">
|
| 60 |
+
Band 4
|
| 61 |
+
</div>
|
| 62 |
+
<div class="flashcard-back">
|
| 63 |
+
The prompt is tackled in a minimal way, or the answer is tangential, possibly due to some misunderstanding of the prompt. The format may be inappropriate. A position is discernible, but the reader has to read carefully to find it. Main ideas are difficult to identify and such ideas that are identifiable may lack relevance, clarity and/or support.
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
<div class="flashcard">
|
| 68 |
+
<div class="flashcard-inner">
|
| 69 |
+
<div class="flashcard-front">
|
| 70 |
+
Band 3
|
| 71 |
+
</div>
|
| 72 |
+
<div class="flashcard-back">
|
| 73 |
+
No part of the prompt is adequately addressed, or the prompt has been misunderstood. No relevant position can be identified, and/or there is little direct response to the question/s. There are few ideas, and these may be irrelevant or insufficiently developed.
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
<div class="flashcard">
|
| 78 |
+
<div class="flashcard-inner">
|
| 79 |
+
<div class="flashcard-front">
|
| 80 |
+
Band 2
|
| 81 |
+
</div>
|
| 82 |
+
<div class="flashcard-back">
|
| 83 |
+
The content is barely related to the prompt. No position can be identified. There may be glimpses of one or two ideas without development. There is little relevant message, or the entire response may be off-topic.
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
<div class="flashcard">
|
| 88 |
+
<div class="flashcard-inner">
|
| 89 |
+
<div class="flashcard-front">
|
| 90 |
+
Band 1
|
| 91 |
+
</div>
|
| 92 |
+
<div class="flashcard-back">
|
| 93 |
+
Responses of 20 words or fewer are rated at Band 1. The content is wholly unrelated to the prompt. Any copied rubric must be discounted.
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
<a href="{{ url_for('infer.rubric_explanation') }}" class="btn btn-primary">Go Back to Rubric Explanation Page</a>
|
| 99 |
+
</div>
|
| 100 |
+
{% endblock %}
|
| 101 |
+
|
| 102 |
+
{% block extra_css %}
|
| 103 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/flashcards.css') }}">
|
| 104 |
+
{% endblock %}
|
my_app/views/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Blueprint
|
| 2 |
+
|
| 3 |
+
auth_bp = Blueprint('auth', __name__, template_folder='templates')
|
| 4 |
+
dashboard_bp = Blueprint('dashboard', __name__, template_folder='templates')
|
| 5 |
+
infer_bp = Blueprint('infer', __name__, template_folder='templates')
|
| 6 |
+
about_bp = Blueprint('about', __name__, template_folder='templates')
|
| 7 |
+
error_bp = Blueprint('error', __name__, template_folder='templates')
|
| 8 |
+
|
| 9 |
+
from . import auth, dashboard, infer, about, error
|
my_app/views/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (508 Bytes). View file
|
|
|
my_app/views/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (744 Bytes). View file
|
|
|
my_app/views/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (604 Bytes). View file
|
|
|
my_app/views/__pycache__/about.cpython-311.pyc
ADDED
|
Binary file (544 Bytes). View file
|
|
|
my_app/views/__pycache__/about.cpython-312.pyc
ADDED
|
Binary file (472 Bytes). View file
|
|
|
my_app/views/__pycache__/auth.cpython-310.pyc
ADDED
|
Binary file (1.81 kB). View file
|
|
|
my_app/views/__pycache__/auth.cpython-311.pyc
ADDED
|
Binary file (3.6 kB). View file
|
|
|
my_app/views/__pycache__/auth.cpython-312.pyc
ADDED
|
Binary file (3.18 kB). View file
|
|
|
my_app/views/__pycache__/dashboard.cpython-310.pyc
ADDED
|
Binary file (2.07 kB). View file
|
|
|