Spaces:
Sleeping
Sleeping
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 |
-
|
32 |
-
|
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, index) =>
|
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 |
+
}
|