Spaces:
Runtime error
Runtime error
lucas-wa commited on
Commit ·
d1cad4b
1
Parent(s): 6558cd8
Adding build options
Browse files- .dockerignore +2 -2
- Dockerfile +1 -1
- server/app.py +36 -4
- server/data/load_data.py +31 -20
- server/llm/__init__.py +0 -0
- server/requirements.txt +2 -3
- web/package-lock.json +37 -0
- web/package.json +2 -0
- web/src/App.jsx +74 -20
- web/src/components/ui/separator.jsx +25 -0
- web/src/index.css +16 -0
.dockerignore
CHANGED
|
@@ -28,8 +28,8 @@ yarn-debug.log*
|
|
| 28 |
yarn-error.log*
|
| 29 |
|
| 30 |
# local env files
|
| 31 |
-
.env
|
| 32 |
-
.env*.local
|
| 33 |
|
| 34 |
# vercel
|
| 35 |
.vercel
|
|
|
|
| 28 |
yarn-error.log*
|
| 29 |
|
| 30 |
# local env files
|
| 31 |
+
*.env
|
| 32 |
+
*.env*.local
|
| 33 |
|
| 34 |
# vercel
|
| 35 |
.vercel
|
Dockerfile
CHANGED
|
@@ -6,7 +6,7 @@ RUN apt-get install -y python3 pip
|
|
| 6 |
|
| 7 |
WORKDIR /code
|
| 8 |
|
| 9 |
-
COPY server/
|
| 10 |
|
| 11 |
RUN pip install --no-cache-dir --upgrade -r /code/server/requirements.txt --break-system-packages
|
| 12 |
|
|
|
|
| 6 |
|
| 7 |
WORKDIR /code
|
| 8 |
|
| 9 |
+
COPY server/ /code/server/
|
| 10 |
|
| 11 |
RUN pip install --no-cache-dir --upgrade -r /code/server/requirements.txt --break-system-packages
|
| 12 |
|
server/app.py
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
|
|
|
|
|
| 1 |
from inference import rag_chain
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
assunto = "Bioquimica e Biofisica"
|
| 4 |
-
query = f"Quero que você gere questões de biologia, sendo do assunto: {assunto}."
|
| 5 |
-
res = rag_chain.invoke(f"""{query}""")
|
| 6 |
-
print(res)
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
from inference import rag_chain
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
from fastapi.staticfiles import StaticFiles
|
| 6 |
+
|
| 7 |
+
class Body(BaseModel):
|
| 8 |
+
subject: str
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
app = FastAPI()
|
| 12 |
+
|
| 13 |
+
app.add_middleware(
|
| 14 |
+
CORSMiddleware,
|
| 15 |
+
allow_origins=["*"],
|
| 16 |
+
allow_methods=["*"],
|
| 17 |
+
allow_headers=["*"],
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
@app.post("/generate_questions")
|
| 21 |
+
async def generate_questions(body: Body):
|
| 22 |
+
subject = body.subject
|
| 23 |
+
query = f"Quero que você gere questões de biologia, sendo do assunto: {subject}."
|
| 24 |
+
res = rag_chain.invoke(f"""{query}""")
|
| 25 |
+
return res
|
| 26 |
+
|
| 27 |
+
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
| 28 |
+
|
| 29 |
+
if __name__ == "__main__":
|
| 30 |
+
import uvicorn
|
| 31 |
+
uvicorn.run("app:app", host="0.0.0.0", port=8000)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
server/data/load_data.py
CHANGED
|
@@ -6,33 +6,44 @@ from langchain.retrievers.self_query.base import SelfQueryRetriever
|
|
| 6 |
from llm.gemini import gemini_embeddings, llm
|
| 7 |
from utils.questions_parser import parse_question
|
| 8 |
|
| 9 |
-
|
| 10 |
-
raise ValueError("DATA_PATH environment variable is not set")
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
|
| 15 |
|
| 16 |
-
|
| 17 |
-
map(lambda x: "##Questão" + x, data_loader[0].page_content.split("##Questão"))
|
| 18 |
-
)
|
| 19 |
|
| 20 |
-
|
|
|
|
| 21 |
|
| 22 |
-
|
| 23 |
-
try:
|
| 24 |
-
docs.append(parse_question(question))
|
| 25 |
-
except Exception as e:
|
| 26 |
-
print(e, question)
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
vectorstore_disk = Chroma(
|
| 34 |
-
persist_directory="./chroma_db", embedding_function=gemini_embeddings
|
| 35 |
-
)
|
| 36 |
metadata_field_info = [
|
| 37 |
AttributeInfo(
|
| 38 |
name="topico",
|
|
|
|
| 6 |
from llm.gemini import gemini_embeddings, llm
|
| 7 |
from utils.questions_parser import parse_question
|
| 8 |
|
| 9 |
+
try:
|
|
|
|
| 10 |
|
| 11 |
+
vectorstore = Chroma(
|
| 12 |
+
persist_directory="./chroma_db", embedding_function=gemini_embeddings
|
| 13 |
+
)
|
| 14 |
|
| 15 |
+
except Exception as e:
|
| 16 |
|
| 17 |
+
print(e)
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
if "DATA_PATH" not in os.environ:
|
| 20 |
+
raise ValueError("DATA_PATH environment variable is not set")
|
| 21 |
|
| 22 |
+
DATA_PATH = os.environ["DATA_PATH"]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
data_loader = TextLoader(DATA_PATH, encoding="UTF-8").load()
|
| 25 |
+
|
| 26 |
+
questions = list(
|
| 27 |
+
map(lambda x: "##Questão" + x, data_loader[0].page_content.split("##Questão"))
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
docs = []
|
| 31 |
+
|
| 32 |
+
for question in questions:
|
| 33 |
+
try:
|
| 34 |
+
docs.append(parse_question(question))
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(e, question)
|
| 37 |
+
|
| 38 |
+
db = Chroma.from_documents(docs, gemini_embeddings)
|
| 39 |
+
vectorstore = Chroma.from_documents(
|
| 40 |
+
documents=docs, embedding=gemini_embeddings, persist_directory="./chroma_db"
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
vectorstore_disk = Chroma(
|
| 44 |
+
persist_directory="./chroma_db", embedding_function=gemini_embeddings
|
| 45 |
+
)
|
| 46 |
|
|
|
|
|
|
|
|
|
|
| 47 |
metadata_field_info = [
|
| 48 |
AttributeInfo(
|
| 49 |
name="topico",
|
server/llm/__init__.py
ADDED
|
File without changes
|
server/requirements.txt
CHANGED
|
@@ -2,9 +2,8 @@ langchain==0.1.6
|
|
| 2 |
chromadb==0.5.0
|
| 3 |
lark==1.1.9
|
| 4 |
langchain-google-genai==1.0.1
|
| 5 |
-
# langchain-text-splitters==0.0.1
|
| 6 |
langchain-core==0.1.22
|
| 7 |
langchain-community==0.0.20
|
| 8 |
langsmith==0.0.87
|
| 9 |
-
python-daemon==2.1.2
|
| 10 |
-
|
|
|
|
| 2 |
chromadb==0.5.0
|
| 3 |
lark==1.1.9
|
| 4 |
langchain-google-genai==1.0.1
|
|
|
|
| 5 |
langchain-core==0.1.22
|
| 6 |
langchain-community==0.0.20
|
| 7 |
langsmith==0.0.87
|
| 8 |
+
# python-daemon==2.1.2
|
| 9 |
+
# langchain-text-splitters==0.0.1
|
web/package-lock.json
CHANGED
|
@@ -9,11 +9,13 @@
|
|
| 9 |
"version": "0.0.0",
|
| 10 |
"dependencies": {
|
| 11 |
"@radix-ui/react-select": "^2.0.0",
|
|
|
|
| 12 |
"class-variance-authority": "^0.7.0",
|
| 13 |
"clsx": "^2.1.1",
|
| 14 |
"lucide-react": "^0.377.0",
|
| 15 |
"react": "^18.2.0",
|
| 16 |
"react-dom": "^18.2.0",
|
|
|
|
| 17 |
"tailwind-merge": "^2.3.0",
|
| 18 |
"tailwindcss-animate": "^1.0.7"
|
| 19 |
},
|
|
@@ -1349,6 +1351,29 @@
|
|
| 1349 |
}
|
| 1350 |
}
|
| 1351 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1352 |
"node_modules/@radix-ui/react-slot": {
|
| 1353 |
"version": "1.0.2",
|
| 1354 |
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
|
@@ -4789,6 +4814,18 @@
|
|
| 4789 |
}
|
| 4790 |
}
|
| 4791 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4792 |
"node_modules/read-cache": {
|
| 4793 |
"version": "1.0.0",
|
| 4794 |
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
|
|
|
| 9 |
"version": "0.0.0",
|
| 10 |
"dependencies": {
|
| 11 |
"@radix-ui/react-select": "^2.0.0",
|
| 12 |
+
"@radix-ui/react-separator": "^1.0.3",
|
| 13 |
"class-variance-authority": "^0.7.0",
|
| 14 |
"clsx": "^2.1.1",
|
| 15 |
"lucide-react": "^0.377.0",
|
| 16 |
"react": "^18.2.0",
|
| 17 |
"react-dom": "^18.2.0",
|
| 18 |
+
"react-toastify": "^10.0.5",
|
| 19 |
"tailwind-merge": "^2.3.0",
|
| 20 |
"tailwindcss-animate": "^1.0.7"
|
| 21 |
},
|
|
|
|
| 1351 |
}
|
| 1352 |
}
|
| 1353 |
},
|
| 1354 |
+
"node_modules/@radix-ui/react-separator": {
|
| 1355 |
+
"version": "1.0.3",
|
| 1356 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.3.tgz",
|
| 1357 |
+
"integrity": "sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==",
|
| 1358 |
+
"dependencies": {
|
| 1359 |
+
"@babel/runtime": "^7.13.10",
|
| 1360 |
+
"@radix-ui/react-primitive": "1.0.3"
|
| 1361 |
+
},
|
| 1362 |
+
"peerDependencies": {
|
| 1363 |
+
"@types/react": "*",
|
| 1364 |
+
"@types/react-dom": "*",
|
| 1365 |
+
"react": "^16.8 || ^17.0 || ^18.0",
|
| 1366 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
| 1367 |
+
},
|
| 1368 |
+
"peerDependenciesMeta": {
|
| 1369 |
+
"@types/react": {
|
| 1370 |
+
"optional": true
|
| 1371 |
+
},
|
| 1372 |
+
"@types/react-dom": {
|
| 1373 |
+
"optional": true
|
| 1374 |
+
}
|
| 1375 |
+
}
|
| 1376 |
+
},
|
| 1377 |
"node_modules/@radix-ui/react-slot": {
|
| 1378 |
"version": "1.0.2",
|
| 1379 |
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
|
|
|
| 4814 |
}
|
| 4815 |
}
|
| 4816 |
},
|
| 4817 |
+
"node_modules/react-toastify": {
|
| 4818 |
+
"version": "10.0.5",
|
| 4819 |
+
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz",
|
| 4820 |
+
"integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==",
|
| 4821 |
+
"dependencies": {
|
| 4822 |
+
"clsx": "^2.1.0"
|
| 4823 |
+
},
|
| 4824 |
+
"peerDependencies": {
|
| 4825 |
+
"react": ">=18",
|
| 4826 |
+
"react-dom": ">=18"
|
| 4827 |
+
}
|
| 4828 |
+
},
|
| 4829 |
"node_modules/read-cache": {
|
| 4830 |
"version": "1.0.0",
|
| 4831 |
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
web/package.json
CHANGED
|
@@ -11,11 +11,13 @@
|
|
| 11 |
},
|
| 12 |
"dependencies": {
|
| 13 |
"@radix-ui/react-select": "^2.0.0",
|
|
|
|
| 14 |
"class-variance-authority": "^0.7.0",
|
| 15 |
"clsx": "^2.1.1",
|
| 16 |
"lucide-react": "^0.377.0",
|
| 17 |
"react": "^18.2.0",
|
| 18 |
"react-dom": "^18.2.0",
|
|
|
|
| 19 |
"tailwind-merge": "^2.3.0",
|
| 20 |
"tailwindcss-animate": "^1.0.7"
|
| 21 |
},
|
|
|
|
| 11 |
},
|
| 12 |
"dependencies": {
|
| 13 |
"@radix-ui/react-select": "^2.0.0",
|
| 14 |
+
"@radix-ui/react-separator": "^1.0.3",
|
| 15 |
"class-variance-authority": "^0.7.0",
|
| 16 |
"clsx": "^2.1.1",
|
| 17 |
"lucide-react": "^0.377.0",
|
| 18 |
"react": "^18.2.0",
|
| 19 |
"react-dom": "^18.2.0",
|
| 20 |
+
"react-toastify": "^10.0.5",
|
| 21 |
"tailwind-merge": "^2.3.0",
|
| 22 |
"tailwindcss-animate": "^1.0.7"
|
| 23 |
},
|
web/src/App.jsx
CHANGED
|
@@ -6,38 +6,82 @@ import {
|
|
| 6 |
SelectValue,
|
| 7 |
} from "@/components/ui/select"
|
| 8 |
import { ChevronLeft, Menu } from "lucide-react"
|
| 9 |
-
import { useEffect,
|
|
|
|
|
|
|
| 10 |
|
| 11 |
function App() {
|
| 12 |
|
| 13 |
-
const selectRef = useRef(null);
|
| 14 |
const [subject, setSubject] = useState("");
|
| 15 |
|
| 16 |
const [menuState, setMenuState] = useState(true);
|
| 17 |
const [isLoading, setIsLoading] = useState(false);
|
| 18 |
-
const [questions, setQuestions] = useState(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
const handleSubmit = async (e) => {
|
| 21 |
e.preventDefault();
|
| 22 |
setIsLoading(true);
|
| 23 |
try {
|
| 24 |
-
const res = await fetch("
|
| 25 |
method: "POST",
|
| 26 |
headers: {
|
| 27 |
"Content-Type": "application/json"
|
| 28 |
},
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
});
|
| 33 |
console.log(res)
|
| 34 |
if (res.ok) {
|
| 35 |
const data = await res.json();
|
| 36 |
console.log(data)
|
| 37 |
-
setQuestions(data.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
}
|
| 39 |
} catch (err) {
|
| 40 |
console.error(err);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
}
|
| 42 |
setIsLoading(false);
|
| 43 |
}
|
|
@@ -51,26 +95,25 @@ function App() {
|
|
| 51 |
setMenuState(false);
|
| 52 |
}
|
| 53 |
}
|
| 54 |
-
// console.log(selectRef.current.value)
|
| 55 |
window.addEventListener("resize", handleResize);
|
| 56 |
return () => window.removeEventListener("resize", handleResize);
|
| 57 |
}
|
| 58 |
, []);
|
| 59 |
|
| 60 |
return (
|
| 61 |
-
<div className="font-sans text-white min-h-screen bg-slate-900 flex flex-col relative">
|
| 62 |
<header className="p-10 flex items-center gap-2.5">
|
| 63 |
-
<Menu className="md:hidden" onClick={
|
| 64 |
<h1 className="text-3xl font-bold">
|
| 65 |
-
|
| 66 |
</h1>
|
| 67 |
</header>
|
| 68 |
-
<main className="flex-1 flex">
|
| 69 |
{
|
| 70 |
menuState &&
|
| 71 |
<form
|
| 72 |
onSubmit={handleSubmit}
|
| 73 |
-
className="p-10 flex flex-col gap-2.5 absolute top-0 bg-black/
|
| 74 |
<div className="md:sr-only">
|
| 75 |
<ChevronLeft className="w-10 h-10" onClick={e => setMenuState(prev => !prev)} />
|
| 76 |
</div>
|
|
@@ -95,24 +138,35 @@ function App() {
|
|
| 95 |
</SelectContent>
|
| 96 |
</Select>
|
| 97 |
<button className="h-10 bg-purple-500 rounded px-2.5 py-1 mt-5 hover:brightness-110 transition-all flex items-center justify-center disabled:hover:brightness-75 disabled:brightness-75"
|
| 98 |
-
disabled={isLoading}>
|
| 99 |
{
|
| 100 |
isLoading ?
|
| 101 |
-
<div className="animate-spin h-5 w-5 border-2 border-white border-r-purple-500 rounded-full
|
| 102 |
:
|
| 103 |
"Enviar"
|
| 104 |
}
|
| 105 |
</button>
|
| 106 |
</form>
|
| 107 |
}
|
| 108 |
-
<section className="p-10 flex-1 md:border-l-2 md:border-l-white flex flex-col gap-2.5">
|
| 109 |
<h2 className="text-2xl font-bold">Questões</h2>
|
| 110 |
-
<div className="w-full h-full flex flex-col gap-5
|
| 111 |
{questions ?
|
| 112 |
-
questions.map((question,
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
</div>
|
| 115 |
</section>
|
|
|
|
| 116 |
</main>
|
| 117 |
</div>
|
| 118 |
)
|
|
|
|
| 6 |
SelectValue,
|
| 7 |
} from "@/components/ui/select"
|
| 8 |
import { ChevronLeft, Menu } from "lucide-react"
|
| 9 |
+
import { useEffect, useState } from "react";
|
| 10 |
+
import { ToastContainer, toast } from "react-toastify"
|
| 11 |
+
import 'react-toastify/dist/ReactToastify.css';
|
| 12 |
|
| 13 |
function App() {
|
| 14 |
|
|
|
|
| 15 |
const [subject, setSubject] = useState("");
|
| 16 |
|
| 17 |
const [menuState, setMenuState] = useState(true);
|
| 18 |
const [isLoading, setIsLoading] = useState(false);
|
| 19 |
+
const [questions, setQuestions] = useState([
|
| 20 |
+
{
|
| 21 |
+
question: "Qual a cor do céu?",
|
| 22 |
+
options: ["Azul", "Verde", "Amarelo", "Vermelho"],
|
| 23 |
+
answer: "Azul"
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
question: "Qual a cor da grama?",
|
| 27 |
+
options: ["Azul", "Verde", "Amarelo", "Vermelho"],
|
| 28 |
+
answer: "Verde"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
question: "Qual a cor do sol?",
|
| 32 |
+
options: ["Azul", "Verde", "Amarelo", "Vermelho"],
|
| 33 |
+
answer: "Amarelo"
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
question: "Qual a cor do sangue?",
|
| 37 |
+
options: ["Azul", "Verde", "Amarelo", "Vermelho"],
|
| 38 |
+
answer: "Vermelho"
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
]);
|
| 42 |
|
| 43 |
const handleSubmit = async (e) => {
|
| 44 |
e.preventDefault();
|
| 45 |
setIsLoading(true);
|
| 46 |
try {
|
| 47 |
+
const res = await fetch("/generate_questions", {
|
| 48 |
method: "POST",
|
| 49 |
headers: {
|
| 50 |
"Content-Type": "application/json"
|
| 51 |
},
|
| 52 |
+
body: JSON.stringify({
|
| 53 |
+
subject
|
| 54 |
+
})
|
| 55 |
});
|
| 56 |
console.log(res)
|
| 57 |
if (res.ok) {
|
| 58 |
const data = await res.json();
|
| 59 |
console.log(data)
|
| 60 |
+
setQuestions(data.questions);
|
| 61 |
+
} else {
|
| 62 |
+
toast.error("Erro ao gerar questões", {
|
| 63 |
+
position: "top-right",
|
| 64 |
+
autoClose: 5000,
|
| 65 |
+
hideProgressBar: false,
|
| 66 |
+
closeOnClick: true,
|
| 67 |
+
pauseOnHover: true,
|
| 68 |
+
draggable: true,
|
| 69 |
+
progress: undefined,
|
| 70 |
+
className: "bg-slate-900 text-white font-semibold",
|
| 71 |
+
});
|
| 72 |
}
|
| 73 |
} catch (err) {
|
| 74 |
console.error(err);
|
| 75 |
+
toast.error("Erro ao gerar questões", {
|
| 76 |
+
position: "top-right",
|
| 77 |
+
autoClose: 5000,
|
| 78 |
+
hideProgressBar: false,
|
| 79 |
+
closeOnClick: true,
|
| 80 |
+
pauseOnHover: true,
|
| 81 |
+
draggable: true,
|
| 82 |
+
progress: undefined,
|
| 83 |
+
className: "bg-slate-900 text-white font-semibold",
|
| 84 |
+
});
|
| 85 |
}
|
| 86 |
setIsLoading(false);
|
| 87 |
}
|
|
|
|
| 95 |
setMenuState(false);
|
| 96 |
}
|
| 97 |
}
|
|
|
|
| 98 |
window.addEventListener("resize", handleResize);
|
| 99 |
return () => window.removeEventListener("resize", handleResize);
|
| 100 |
}
|
| 101 |
, []);
|
| 102 |
|
| 103 |
return (
|
| 104 |
+
<div className="font-sans h-screen text-white min-h-screen bg-slate-900 flex flex-col relative overflow-hidden">
|
| 105 |
<header className="p-10 flex items-center gap-2.5">
|
| 106 |
+
<Menu className="md:hidden" onClick={() => setMenuState(prev => !prev)} />
|
| 107 |
<h1 className="text-3xl font-bold">
|
| 108 |
+
Pergunt.<span className="text-transparent bg-clip-text bg-gradient-to-r from-green-300 to bg-blue-500">AÍ</span>
|
| 109 |
</h1>
|
| 110 |
</header>
|
| 111 |
+
<main className="flex-1 flex overflow-hidden">
|
| 112 |
{
|
| 113 |
menuState &&
|
| 114 |
<form
|
| 115 |
onSubmit={handleSubmit}
|
| 116 |
+
className="p-10 flex flex-col gap-2.5 absolute top-0 bg-black/95 h-full z-10 md:static md:h-full md:bg-transparent">
|
| 117 |
<div className="md:sr-only">
|
| 118 |
<ChevronLeft className="w-10 h-10" onClick={e => setMenuState(prev => !prev)} />
|
| 119 |
</div>
|
|
|
|
| 138 |
</SelectContent>
|
| 139 |
</Select>
|
| 140 |
<button className="h-10 bg-purple-500 rounded px-2.5 py-1 mt-5 hover:brightness-110 transition-all flex items-center justify-center disabled:hover:brightness-75 disabled:brightness-75"
|
| 141 |
+
disabled={isLoading || !subject}>
|
| 142 |
{
|
| 143 |
isLoading ?
|
| 144 |
+
<div className="animate-spin h-5 w-5 border-2 border-white border-r-purple-500 rounded-full"></div>
|
| 145 |
:
|
| 146 |
"Enviar"
|
| 147 |
}
|
| 148 |
</button>
|
| 149 |
</form>
|
| 150 |
}
|
| 151 |
+
<section className="p-10 flex-1 md:border-l-2 md:border-l-white flex flex-col gap-2.5 ">
|
| 152 |
<h2 className="text-2xl font-bold">Questões</h2>
|
| 153 |
+
<div className="w-full h-full flex flex-col gap-5 rounded p-2.5 overflow-y-scroll">
|
| 154 |
{questions ?
|
| 155 |
+
questions.map(({ question, options, answer }, index) => (
|
| 156 |
+
<div key={index} className="bg-slate-950 ring-2 ring-white p-2.5 rounded">
|
| 157 |
+
{question}<br /><br />
|
| 158 |
+
{options.map((option, i) => (
|
| 159 |
+
<div key={i}>
|
| 160 |
+
{option}
|
| 161 |
+
</div>
|
| 162 |
+
))}<br />
|
| 163 |
+
Resposta correta: {answer}
|
| 164 |
+
</div>
|
| 165 |
+
))
|
| 166 |
+
: <div className="p-2.5 bg-slate-950 ring-2 ring-white rounded">{"Ainda sem questões"}</div>}
|
| 167 |
</div>
|
| 168 |
</section>
|
| 169 |
+
<ToastContainer />
|
| 170 |
</main>
|
| 171 |
</div>
|
| 172 |
)
|
web/src/components/ui/separator.jsx
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils"
|
| 7 |
+
|
| 8 |
+
const Separator = React.forwardRef((
|
| 9 |
+
{ className, orientation = "horizontal", decorative = true, ...props },
|
| 10 |
+
ref
|
| 11 |
+
) => (
|
| 12 |
+
<SeparatorPrimitive.Root
|
| 13 |
+
ref={ref}
|
| 14 |
+
decorative={decorative}
|
| 15 |
+
orientation={orientation}
|
| 16 |
+
className={cn(
|
| 17 |
+
"shrink-0 bg-border",
|
| 18 |
+
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
| 19 |
+
className
|
| 20 |
+
)}
|
| 21 |
+
{...props} />
|
| 22 |
+
))
|
| 23 |
+
Separator.displayName = SeparatorPrimitive.Root.displayName
|
| 24 |
+
|
| 25 |
+
export { Separator }
|
web/src/index.css
CHANGED
|
@@ -27,3 +27,19 @@
|
|
| 27 |
--radius: 0.5rem;
|
| 28 |
}
|
| 29 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
--radius: 0.5rem;
|
| 28 |
}
|
| 29 |
}
|
| 30 |
+
|
| 31 |
+
::-webkit-scrollbar {
|
| 32 |
+
/* Customize the scrollbar width */
|
| 33 |
+
width: .5rem;
|
| 34 |
+
background-color: transparent;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
::-webkit-scrollbar-track {
|
| 38 |
+
/* Customize the scrollbar track */
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
::-webkit-scrollbar-thumb {
|
| 42 |
+
/* Customize the scrollbar thumb */
|
| 43 |
+
background-color: #C0C0C0;
|
| 44 |
+
border-radius: .5rem;
|
| 45 |
+
}
|