diff --git a/backend/__pycache__/extract_keywords.cpython-310.pyc b/backend/__pycache__/extract_keywords.cpython-310.pyc new file mode 100644 index 0000000..1bfa4b3 Binary files /dev/null and b/backend/__pycache__/extract_keywords.cpython-310.pyc differ diff --git a/backend/__pycache__/find_sentances.cpython-310.pyc b/backend/__pycache__/find_sentances.cpython-310.pyc new file mode 100644 index 0000000..f317f52 Binary files /dev/null and b/backend/__pycache__/find_sentances.cpython-310.pyc differ diff --git a/backend/server.py b/backend/server.py index 0d4abe0..535bb13 100644 --- a/backend/server.py +++ b/backend/server.py @@ -1,114 +1,138 @@ -import http.server +import json import socketserver -import urllib.parse +from http.server import BaseHTTPRequestHandler + +import librosa import torch -from transformers import T5ForConditionalGeneration, T5Tokenizer, pipeline -from transformers import AutoTokenizer, AutoModelForSeq2SeqLM -import json +from transformers import ( + T5ForConditionalGeneration, + T5Tokenizer, + Wav2Vec2ForCTC, + Wav2Vec2Tokenizer, + pipeline, +) + +IP = "127.0.0.1" +PORT = 8000 -IP='127.0.0.1' -PORT=8000 def summarize(text): - summarizer=pipeline('summarization') - return summarizer(text,max_length=110)[0]['summary_text'] + summarizer = pipeline("summarization") + return summarizer(text, max_length=110)[0]["summary_text"] -def generate_question(context,answer,model_path, tokenizer_path): +def generate_question(context, answer, model_path, tokenizer_path): model = T5ForConditionalGeneration.from_pretrained(model_path) tokenizer = T5Tokenizer.from_pretrained(tokenizer_path) - device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) - input_text=f'answer: {answer} context: {context}' + input_text = f"answer: {answer} context: {context}" - inputs=tokenizer.encode_plus( + inputs = tokenizer.encode_plus( input_text, - padding='max_length', + padding="max_length", truncation=True, max_length=512, - return_tensors='pt' + return_tensors="pt", ) - input_ids=inputs['input_ids'].to(device) - attention_mask=inputs['attention_mask'].to(device) + input_ids = inputs["input_ids"].to(device) + attention_mask = inputs["attention_mask"].to(device) with torch.no_grad(): - output=model.generate( - input_ids=input_ids, - attention_mask=attention_mask, - max_length=32 + output = model.generate( + input_ids=input_ids, attention_mask=attention_mask, max_length=32 ) generated_question = tokenizer.decode(output[0], skip_special_tokens=True) return generated_question -def generate_keyphrases(abstract, model_path,tokenizer_path): - device= torch.device('cuda' if torch.cuda.is_available() else 'cpu') + +def generate_keyphrases(abstract, model_path, tokenizer_path): + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = T5ForConditionalGeneration.from_pretrained(model_path) tokenizer = T5Tokenizer.from_pretrained(tokenizer_path) model.to(device) # tokenizer.to(device) - input_text=f'detect keyword: abstract: {abstract}' - input_ids=tokenizer.encode(input_text, truncation=True,padding='max_length',max_length=512,return_tensors='pt').to(device) - output=model.generate(input_ids) - keyphrases= tokenizer.decode(output[0],skip_special_tokens=True).split(',') - return [x.strip() for x in keyphrases if x != ''] + input_text = f"detect keyword: abstract: {abstract}" + input_ids = tokenizer.encode( + input_text, + truncation=True, + padding="max_length", + max_length=512, + return_tensors="pt", + ).to(device) + output = model.generate(input_ids) + keyphrases = tokenizer.decode(output[0], skip_special_tokens=True).split(",") + return [x.strip() for x in keyphrases if x != ""] + def generate_qa(text): # text_summary=summarize(text) - text_summary=text - + text_summary = text - modelA, modelB='./models/modelA','./models/modelB' + modelA, modelB = "./models/modelA", "./models/modelB" # tokenizerA, tokenizerB= './tokenizers/tokenizerA', './tokenizers/tokenizerB' - tokenizerA, tokenizerB= 't5-base', 't5-base' + tokenizerA, tokenizerB = "t5-base", "t5-base" - answers=generate_keyphrases(text_summary, modelA, tokenizerA) + answers = generate_keyphrases(text_summary, modelA, tokenizerA) - qa={} + qa = {} for answer in answers: - question= generate_question(text_summary, answer, modelB, tokenizerB) - qa[question]=answer - + question = generate_question(text_summary, answer, modelB, tokenizerB) + qa[question] = answer + return qa - +def process_audio(audio_file): + audio, _ = librosa.load(audio_file, sr=16000) + model = Wav2Vec2ForCTC.from_pretrained("facebook/wav2vec2-base-960h") + tokenizer = Wav2Vec2Tokenizer.from_pretrained("facebook/wav2vec2-base-960h") + input_values = tokenizer(audio, return_tensors="pt").input_values -class QARequestHandler(http.server.BaseHTTPRequestHandler): + logits = model(input_values).logits + + predicted_ids = torch.argmax(logits, dim=-1) + transcription = tokenizer.batch_decode(predicted_ids)[0] + print("transcription", transcription) + return transcription - def do_POST(self): +class QARequestHandler(BaseHTTPRequestHandler): + def do_POST(self): self.send_response(200) self.send_header("Content-type", "text/plain") self.end_headers() - content_length=int(self.headers["Content-Length"]) - post_data=self.rfile.read(content_length).decode('utf-8') + content_length = int(self.headers["Content-Length"]) + post_data = self.rfile.read(content_length) - # parsed_data=urllib.parse.parse_qs(post_data) parsed_data = json.loads(post_data) + input_type = parsed_data.get("input_type") + input_data = parsed_data.get("input_data") - - input_text=parsed_data.get('input_text') - - qa=generate_qa(input_text) - - + if input_type == "text": + qa = generate_qa(input_data) + elif input_type == "audio": + audio_text = process_audio(input_data) + qa = generate_qa(audio_text) + else: + qa = {} self.wfile.write(json.dumps(qa).encode("utf-8")) self.wfile.flush() + def main(): with socketserver.TCPServer((IP, PORT), QARequestHandler) as server: - print(f'Server started at http://{IP}:{PORT}') + print(f"Server started at http://{IP}:{PORT}") server.serve_forever() -if __name__=="__main__": - main() - \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/backend/test_server.py b/backend/test_server.py index c2bfd5e..3379f0f 100644 --- a/backend/test_server.py +++ b/backend/test_server.py @@ -1,16 +1,17 @@ import requests from sample_input import sample_input -url='http://127.0.0.1:8000' + +url = "http://127.0.0.1:8000" # sample_input=""" # Mitochondria are double-membraned organelles with an inner membrane that forms cristae. The enzymes within the inner membrane are essential for ATP production during oxidative phosphorylation. The outer membrane provides a protective barrier and contains porins to allow the passage of ions and molecules. The matrix, the innermost compartment, is involved in citric acid cycle and houses the mitochondrial DNA. The electron transport chain, present in the inner membrane, is responsible for electron transport and the generation of the electrochemical gradient. Overall, mitochondria function as the cell's powerhouses, producing energy through cellular respiration and maintaining cellular processes like apoptosis. # """ -response=requests.post(url,data={"input_text": sample_input}) +response = requests.post(url, data={"input_text": sample_input}) -result=response.json() +result = response.json() -for question,answer in result.items(): - print(f'Question: {question}') - print(f'Answer: {answer}') - print('-'*30) \ No newline at end of file +for question, answer in result.items(): + print(f"Question: {question}") + print(f"Answer: {answer}") + print("-" * 30) diff --git a/extension/html/text_input.html b/extension/html/text_input.html index f62c2da..044a6fa 100644 --- a/extension/html/text_input.html +++ b/extension/html/text_input.html @@ -2,8 +2,6 @@ EduAid: Text Input - @@ -15,7 +13,9 @@

EduAid

Generate QnA

- + +
OR
+
@@ -24,11 +24,9 @@

Generate QnA

-
-
diff --git a/extension/js/question_generation.js b/extension/js/question_generation.js index 58bdd94..4f80563 100644 --- a/extension/js/question_generation.js +++ b/extension/js/question_generation.js @@ -1,56 +1,61 @@ -document.addEventListener("DOMContentLoaded", function(){ - const saveButton= document.getElementById("save-button"); - const backButton= document.getElementById("back-button"); - const viewQuestionsButton = document.getElementById("view-questions-button"); - const qaPairs=JSON.parse(localStorage.getItem("qaPairs")); - const modalClose= document.querySelector("[data-close-modal]"); - const modal=document.querySelector("[data-modal]"); - - - viewQuestionsButton.addEventListener("click", function(){ - const modalQuestionList = document.getElementById("modal-question-list"); - modalQuestionList.innerHTML = ""; // Clear previous content - - for (const [question, answer] of Object.entries(qaPairs)) { - const questionElement = document.createElement("li"); - questionElement.textContent = `Question: ${question}, Answer: ${answer}`; - modalQuestionList.appendChild(questionElement) +document.addEventListener("DOMContentLoaded", function () { + const saveButton = document.getElementById("save-button"); + const backButton = document.getElementById("back-button"); + const viewQuestionsButton = document.getElementById("view-questions-button"); + const qaPairs = JSON.parse(localStorage.getItem("qaPairs")); + const modalClose = document.querySelector("[data-close-modal]"); + const modal = document.querySelector("[data-modal]"); + + viewQuestionsButton.addEventListener("click", function () { + const modalQuestionList = document.getElementById("modal-question-list"); + modalQuestionList.innerHTML = ""; + + for (const [question, answer] of Object.entries(qaPairs)) { + const questionElement = document.createElement("li"); + if (question.includes("Options:")) { + const options = question.split("Options: ")[1].split(", "); + const formattedOptions = options.map( + (opt, index) => `${String.fromCharCode(97 + index)}) ${opt}` + ); + questionElement.textContent = `Question: ${ + question.split(" Options:")[0] + }\n${formattedOptions.join("\n")}`; + } else { + questionElement.textContent = `Question: ${question}\n\nAnswer: ${answer}\n`; } - modal.showModal(); - }); - modalClose.addEventListener("click", function(){ - modal.close(); - }); - saveButton.addEventListener("click", async function(){ - let textContent= "EduAid Generated QnA:\n\n"; + modalQuestionList.appendChild(questionElement); + } + modal.showModal(); + }); - for (const [question,answer] of Object.entries(qaPairs)){ - textContent+= `Question: ${question}\nAnswer: ${answer}\n\n`; - } - const blob = new Blob([textContent], { type: "text/plain" }); - - // Create a URL for the Blob - const blobUrl = URL.createObjectURL(blob); - - // Create a temporary element to trigger the download - const downloadLink = document.createElement("a"); - downloadLink.href = blobUrl; - downloadLink.download = "questions_and_answers.txt"; - downloadLink.style.display = "none"; - - // Append the element to the document - document.body.appendChild(downloadLink); - - // Simulate a click on the link to trigger the download - downloadLink.click(); - - // Clean up: remove the temporary element and revoke the Blob URL - document.body.removeChild(downloadLink); - URL.revokeObjectURL(blobUrl); - }); - - backButton.addEventListener("click", function(){ - window.location.href="../html/text_input.html" - }); -}); \ No newline at end of file + modalClose.addEventListener("click", function () { + modal.close(); + }); + saveButton.addEventListener("click", async function () { + let textContent = "EduAid Generated QnA:\n\n"; + + for (const [question, answer] of Object.entries(qaPairs)) { + textContent += `Question: ${question}\nAnswer: ${answer}\n\n`; + } + const blob = new Blob([textContent], { type: "text/plain" }); + + const blobUrl = URL.createObjectURL(blob); + + const downloadLink = document.createElement("a"); + downloadLink.href = blobUrl; + downloadLink.download = "questions_and_answers.txt"; + downloadLink.style.display = "none"; + + document.body.appendChild(downloadLink); + + downloadLink.click(); + + document.body.removeChild(downloadLink); + URL.revokeObjectURL(blobUrl); + }); + + backButton.addEventListener("click", function () { + window.location.href = "../html/text_input.html"; + }); +}); diff --git a/extension/js/text_input.js b/extension/js/text_input.js index bf65bb9..a88186b 100644 --- a/extension/js/text_input.js +++ b/extension/js/text_input.js @@ -1,79 +1,83 @@ document.addEventListener("DOMContentLoaded", function () { - const nextButton = document.getElementById("next-button"); - const backButton = document.getElementById("back-button"); - const textInput = document.getElementById("text-input"); - const fileInput = document.getElementById("file-upload"); - const loadingScreen = document.getElementById("loading-screen"); - - - fileInput.addEventListener("change", async function () { + const nextButton = document.getElementById("next-button"); + const backButton = document.getElementById("back-button"); + const textInput = document.getElementById("text-input"); + const fileInput = document.getElementById("file-upload"); + const loadingScreen = document.getElementById("loading-screen"); + const audioInput = document.getElementById("audio-input"); + + fileInput.addEventListener("change", async function () { + const file = fileInput.files[0]; + if (file) { + const fileReader = new FileReader(); + fileReader.onload = async function (event) { + const pdfData = new Uint8Array(event.target.result); + const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise; + let pdfText = ""; + + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const pageText = await page.getTextContent(); + const pageStrings = pageText.items.map((item) => item.str); + pdfText += pageStrings.join(" "); + } + + textInput.value = pdfText; + }; + fileReader.readAsArrayBuffer(file); + } + }); + + nextButton.addEventListener("click", async function () { + loadingScreen.style.display = "flex"; + const inputText = textInput.value; + const inputAudio = audioInput.value; + + if (inputText.trim() === "" && fileInput.files.length > 0) { const file = fileInput.files[0]; - if (file) { - const fileReader = new FileReader(); - fileReader.onload = async function (event) { - const pdfData = new Uint8Array(event.target.result); - const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise; - let pdfText = ""; - - for (let i = 1; i <= pdf.numPages; i++) { - const page = await pdf.getPage(i); - const pageText = await page.getTextContent(); - const pageStrings = pageText.items.map(item => item.str); - pdfText += pageStrings.join(" "); - } - - textInput.value = pdfText; - }; - fileReader.readAsArrayBuffer(file); - } - }); + const fileReader = new FileReader(); + fileReader.onload = async function (event) { + const uploadedPdfData = new Uint8Array(event.target.result); + await sendToBackend(uploadedPdfData, "pdf"); + }; + fileReader.readAsArrayBuffer(file); + } else if (inputText.trim() !== "") { + await sendToBackend(inputText, "text"); + } else if (audioInput.value.trim() !== "") { + await sendToBackend(audioInput.value, "audio"); + } + else { + alert("Please enter text or upload a PDF file."); + loadingScreen.style.display = "none"; + } + }); - nextButton.addEventListener("click", async function () { - loadingScreen.style.display = "flex" - const inputText = textInput.value; - - if (inputText.trim() === "" && fileInput.files.length > 0) { - const file = fileInput.files[0]; - const fileReader = new FileReader(); - fileReader.onload = async function (event) { - const uploadedPdfData = new Uint8Array(event.target.result); - await sendToBackend(uploadedPdfData,"pdf"); - }; - fileReader.readAsArrayBuffer(file); - } else if (inputText.trim() !== "") { - await sendToBackend(inputText,"text"); - } else { - alert("Please enter text or upload a PDF file."); - loadingScreen.style.display = "none"; - } - }); - - backButton.addEventListener("click", function () { - window.location.href = "../html/index.html"; + backButton.addEventListener("click", function () { + window.location.href = "../html/index.html"; + }); + + async function sendToBackend(data, dataType) { + let formData; + let contentType; + formData = JSON.stringify({ input_data: data, input_type: dataType }); + contentType = "application/json; charset=UTF-8"; + + const response = await fetch("http://127.0.0.1:8000", { + method: "POST", + body: formData, + headers: { + "Content-Type": contentType, + }, }); - - async function sendToBackend(data, dataType) { - let formData; - let contentType; - formData = JSON.stringify({ 'input_text': data }); - contentType = "application/json; charset=UTF-8"; - - const response = await fetch("http://127.0.0.1:8000", { - method: "POST", - body: formData, - headers: { - "Content-Type": contentType, - }, - }); - - if (response.ok) { - const responseData = await response.json(); - // console.log("Response data:\n"+responseData); - localStorage.setItem("qaPairs", JSON.stringify(responseData)); - window.location.href = "../html/question_generation.html"; - } else { - console.error("Backend request failed."); - } - loadingScreen.style.display = "none"; + + if (response.ok) { + const responseData = await response.json(); + // console.log("Response data:\n"+responseData); + localStorage.setItem("qaPairs", JSON.stringify(responseData)); + window.location.href = "../html/question_generation.html"; + } else { + console.error("Backend request failed."); } - }); \ No newline at end of file + loadingScreen.style.display = "none"; + } +}); diff --git a/extension/manifest.json b/extension/manifest.json index dabae1a..a8ea075 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -4,7 +4,7 @@ "version": "1.0", "description": "Generate quizzes with AI-powered questions.", "permissions": ["activeTab", "storage"], - "host_permissions":["http://127.0.0.1:8000/*"], + "host_permissions":["http://localhost:8000/*","http://127.0.0.1:8000/*","http://localhost:8000/"], "icons": { "16": "./assets/aossie_logo.png" }, @@ -33,4 +33,4 @@ "matches": [""] } ] -} +} \ No newline at end of file diff --git a/extension/styles/index.css b/extension/styles/index.css index 8c0bae4..ded76ae 100644 --- a/extension/styles/index.css +++ b/extension/styles/index.css @@ -1,12 +1,8 @@ - - - body{ - /* background-color: rgb(18, 89, 231); */ background-color: #FBAB7E; background-image: linear-gradient(62deg, #FBAB7E 0%, #F7CE68 100%); font-family: 'Inter'; - font-weight: 400; /* Regular */ + font-weight: 400; } header{ @@ -29,7 +25,6 @@ h1 { flex-direction: column; align-items: center; justify-content: center; - /* height: calc(101vh - 102px); Adjust this height value as needed */ } h2 { @@ -49,8 +44,6 @@ p{ --black: #000; } - -/* Style the button */ button { background: linear-gradient(to right, var(--yellow) 0%, var(--green) 50%, var(--yellow) 100%); background-size: 500%; @@ -59,9 +52,6 @@ button { box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); color: var(--black); cursor: pointer; - /* font: 1.5em Raleway, sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; */ height: 1.5rem; letter-spacing: 0.05em; outline: none; @@ -75,12 +65,10 @@ button { margin-bottom: 1px; } -/* Style the button on hover */ button:hover { - background-position: 100%; /* Move the background gradient on hover */ + background-position: 100%; } -/* Add animation for button hover effect */ @keyframes gradient { 0% { background-position: 0% 50%; diff --git a/extension/styles/question_generation.css b/extension/styles/question_generation.css index 605639c..d41460e 100644 --- a/extension/styles/question_generation.css +++ b/extension/styles/question_generation.css @@ -1,5 +1,4 @@ body{ - /* background-color: rgb(18, 89, 231); */ background-color: #FBAB7E; background-image: linear-gradient(62deg, #FBAB7E 0%, #F7CE68 100%); font-family: 'Inter'; @@ -26,7 +25,6 @@ h1 { flex-direction: column; align-items: center; justify-content: center; - /* height: calc(101vh - 102px); Adjust this height value as needed */ } h2 { @@ -51,9 +49,6 @@ p{ --black: #000; } - -/* Style the button */ - button { display: inline-block; background: linear-gradient(to right, var(--yellow) 0%, var(--green) 50%, var(--yellow) 100%); @@ -63,9 +58,6 @@ button { box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); color: var(--black); cursor: pointer; - /* font: 1.5em Raleway, sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; */ height: 1.5rem; letter-spacing: 0.05em; outline: none; @@ -75,15 +67,14 @@ button { -ms-user-select: none; user-select: none; width: 3rem; - transition: background-position 0.7s ease; /* Add transition for background position */ + transition: background-position 0.7s ease; margin-bottom: 1px; } button:hover { - background-position: 100%; /* Move the background gradient on hover */ + background-position: 100%; } - - /* Add animation for button hover effect */ + @keyframes gradient { 0% { background-position: 0% 50%; diff --git a/extension/styles/text_input.css b/extension/styles/text_input.css index 19e1412..8a62e6c 100644 --- a/extension/styles/text_input.css +++ b/extension/styles/text_input.css @@ -1,49 +1,51 @@ -body{ - /* background-color: rgb(18, 89, 231); */ - background-color: #FBAB7E; - background-image: linear-gradient(62deg, #FBAB7E 0%, #F7CE68 100%); - font-family: 'Inter'; - font-weight: 400; /* Regular */ +body { + background-color: #fbab7e; + background-image: linear-gradient(62deg, #fbab7e 0%, #f7ce68 100%); + font-family: "Inter"; + font-weight: 400; /* Regular */ } -header{ - display: flex; - align-items: center; - padding: 10px 20px; - -} -img{ - width: 32px; - height: 32px; - margin-right: 10px; - +header { + display: flex; + align-items: center; + padding: 10px 20px; +} +img { + width: 32px; + height: 32px; + margin-right: 10px; } h1 { - font-size: 24px; - } - main { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - /* height: calc(101vh - 102px); Adjust this height value as needed */ - } + font-size: 24px; +} +main { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} - h2 { - font-size: 28px; - margin-bottom: 10px; - } -p{ - margin-left: 2px; - margin-right: 2px; - padding-left: 1px; - padding-right: 1px; - text-align: left; -} -#text-input{ - height: 30px; +h2 { + font-size: 28px; + margin-bottom: 10px; +} +p { + margin-left: 2px; + margin-right: 2px; + padding-left: 1px; + padding-right: 1px; + text-align: left; +} +#text-input { + height: 150px; width: 150px; margin-bottom: 10px; + resize: vertical; + overflow: auto; +} +#audio-input { + margin: 6px; + width: 150px; } :root { --yellow: #e8f222; @@ -51,21 +53,20 @@ p{ --black: #000; } - -/* Style the button */ - button { display: inline-block; - background: linear-gradient(to right, var(--yellow) 0%, var(--green) 50%, var(--yellow) 100%); + background: linear-gradient( + to right, + var(--yellow) 0%, + var(--green) 50%, + var(--yellow) 100% + ); background-size: 500%; border: none; border-radius: 2rem; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); color: var(--black); cursor: pointer; - /* font: 1.5em Raleway, sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; */ height: 1.5rem; letter-spacing: 0.05em; outline: none; @@ -75,22 +76,26 @@ button { -ms-user-select: none; user-select: none; width: 3rem; - transition: background-position 0.7s ease; /* Add transition for background position */ + transition: background-position 0.7s ease; margin-bottom: 1px; - } label { display: inline-block; - background: linear-gradient(to right, var(--yellow) 0%, var(--green) 50%, var(--yellow) 100%); + background: linear-gradient( + to right, + var(--yellow) 0%, + var(--green) 50%, + var(--yellow) 100% + ); background-size: 500%; border: none; border-radius: 1rem; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); color: var(--black); cursor: pointer; - font: 1em Inter, sans-serif; + font: 1em Inter, sans-serif; -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + -moz-osx-font-smoothing: grayscale; height: 1rem; letter-spacing: 0.05em; outline: none; @@ -100,21 +105,20 @@ label { -ms-user-select: none; user-select: none; width: 7rem; - transition: background-position 0.7s ease; + transition: background-position 0.7s ease; margin-bottom: 10px; - } -#upload-label{ +#upload-label { text-align: center; align-items: center; margin-top: 2px; } -/* Style the button on hover */ -label:hover, button:hover { - background-position: 100%; /* Move the background gradient on hover */ + +label:hover, +button:hover { + background-position: 100%; } -/* Add animation for button hover effect */ @keyframes gradient { 0% { background-position: 0% 50%; @@ -134,7 +138,6 @@ label:hover, button:hover { z-index: 9999; justify-content: center; align-items: center; - } .loading-spinner { @@ -149,6 +152,10 @@ label:hover, button:hover { } @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } }