Got basics of FE/BE running. BE passes tests
Browse files- .gitignore +67 -0
- Dockerfile +44 -0
- backend/app/__init__.py +1 -0
- backend/app/main.py +38 -0
- backend/tests/test_quiz.py +21 -0
- frontend/jest.config.js +8 -0
- frontend/package.json +48 -0
- frontend/public/index.html +14 -0
- frontend/public/manifest.json +8 -0
- frontend/public/robots.txt +2 -0
- frontend/src/App.tsx +35 -0
- frontend/src/components/DocumentInput.tsx +38 -0
- frontend/src/components/Header.tsx +13 -0
- frontend/src/components/ProblemList.tsx +26 -0
- frontend/src/components/QuizGenerator.tsx +44 -0
- frontend/src/index.js +13 -0
- frontend/src/tests/App.test.tsx +20 -0
- frontend/src/tests/components/DocumentInput.test.tsx +30 -0
- frontend/src/tests/components/QuizGenerator.test.tsx +35 -0
- frontend/tsconfig.json +22 -0
- pyproject.toml +22 -0
- pytest.ini +2 -0
.gitignore
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
build/
|
| 8 |
+
develop-eggs/
|
| 9 |
+
dist/
|
| 10 |
+
downloads/
|
| 11 |
+
eggs/
|
| 12 |
+
.eggs/
|
| 13 |
+
lib/
|
| 14 |
+
lib64/
|
| 15 |
+
parts/
|
| 16 |
+
sdist/
|
| 17 |
+
var/
|
| 18 |
+
wheels/
|
| 19 |
+
*.egg-info/
|
| 20 |
+
.installed.cfg
|
| 21 |
+
*.egg
|
| 22 |
+
.pytest_cache/
|
| 23 |
+
.coverage
|
| 24 |
+
htmlcov/
|
| 25 |
+
.env
|
| 26 |
+
.venv
|
| 27 |
+
env/
|
| 28 |
+
venv/
|
| 29 |
+
ENV/
|
| 30 |
+
.uv/
|
| 31 |
+
|
| 32 |
+
# Node/React
|
| 33 |
+
node_modules/
|
| 34 |
+
coverage/
|
| 35 |
+
build/
|
| 36 |
+
.DS_Store
|
| 37 |
+
.env.local
|
| 38 |
+
.env.development.local
|
| 39 |
+
.env.test.local
|
| 40 |
+
.env.production.local
|
| 41 |
+
npm-debug.log*
|
| 42 |
+
yarn-debug.log*
|
| 43 |
+
yarn-error.log*
|
| 44 |
+
|
| 45 |
+
# IDEs and editors
|
| 46 |
+
.idea/
|
| 47 |
+
.vscode/
|
| 48 |
+
*.swp
|
| 49 |
+
*.swo
|
| 50 |
+
.project
|
| 51 |
+
.classpath
|
| 52 |
+
.settings/
|
| 53 |
+
*.sublime-workspace
|
| 54 |
+
*.sublime-project
|
| 55 |
+
|
| 56 |
+
# OS generated files
|
| 57 |
+
.DS_Store
|
| 58 |
+
.DS_Store?
|
| 59 |
+
._*
|
| 60 |
+
.Spotlight-V100
|
| 61 |
+
.Trashes
|
| 62 |
+
ehthumbs.db
|
| 63 |
+
Thumbs.db
|
| 64 |
+
|
| 65 |
+
# Docker
|
| 66 |
+
.docker/
|
| 67 |
+
*.log
|
Dockerfile
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use Node.js image for building frontend
|
| 2 |
+
FROM node:20-slim AS frontend-builder
|
| 3 |
+
|
| 4 |
+
WORKDIR /app/frontend
|
| 5 |
+
|
| 6 |
+
# Copy package files first for better caching
|
| 7 |
+
COPY frontend/package*.json ./
|
| 8 |
+
RUN npm cache clean --force && \
|
| 9 |
+
npm install --legacy-peer-deps --force
|
| 10 |
+
|
| 11 |
+
# Copy only the necessary frontend files
|
| 12 |
+
COPY frontend/public ./public
|
| 13 |
+
COPY frontend/src ./src
|
| 14 |
+
COPY frontend/tsconfig.json .
|
| 15 |
+
COPY frontend/jest.config.js .
|
| 16 |
+
COPY frontend/.env .
|
| 17 |
+
|
| 18 |
+
# Show more verbose output during build
|
| 19 |
+
RUN npm run build --verbose
|
| 20 |
+
|
| 21 |
+
# Use Python image with uv pre-installed
|
| 22 |
+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
|
| 23 |
+
|
| 24 |
+
WORKDIR /app
|
| 25 |
+
|
| 26 |
+
# Copy backend code
|
| 27 |
+
COPY backend/ backend/
|
| 28 |
+
COPY pyproject.toml .
|
| 29 |
+
|
| 30 |
+
# Install backend dependencies and make pytest available
|
| 31 |
+
RUN uv sync && uv pip install .
|
| 32 |
+
ENV PATH="/root/.local/bin:/root/.uv/venv/bin:${PATH}"
|
| 33 |
+
|
| 34 |
+
# Copy frontend build
|
| 35 |
+
COPY --from=frontend-builder /app/frontend/build /app/frontend/build
|
| 36 |
+
|
| 37 |
+
# Add uv's bin directory to PATH
|
| 38 |
+
ENV PATH="/app/.venv/bin:/root/.local/bin:/root/.uv/venv/bin:${PATH}"
|
| 39 |
+
|
| 40 |
+
# Expose port
|
| 41 |
+
EXPOSE 8000
|
| 42 |
+
|
| 43 |
+
# Run the application
|
| 44 |
+
CMD ["uvicorn", "backend.app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
backend/app/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Empty file to make the directory a Python package
|
backend/app/main.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from pydantic import BaseModel
|
| 4 |
+
import random
|
| 5 |
+
|
| 6 |
+
app = FastAPI()
|
| 7 |
+
|
| 8 |
+
# Add CORS middleware
|
| 9 |
+
app.add_middleware(
|
| 10 |
+
CORSMiddleware,
|
| 11 |
+
allow_origins=["*"], # In production, replace with specific origins
|
| 12 |
+
allow_credentials=True,
|
| 13 |
+
allow_methods=["*"],
|
| 14 |
+
allow_headers=["*"],
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
class UrlInput(BaseModel):
|
| 18 |
+
url: str
|
| 19 |
+
|
| 20 |
+
class UserQuery(BaseModel):
|
| 21 |
+
user_query: str
|
| 22 |
+
|
| 23 |
+
@app.post("/crawl/")
|
| 24 |
+
async def crawl_documentation(input_data: UrlInput):
|
| 25 |
+
print(f"Received url {input_data.url}")
|
| 26 |
+
return {"status": "received"}
|
| 27 |
+
|
| 28 |
+
@app.post("/problems/")
|
| 29 |
+
async def generate_problems(query: UserQuery):
|
| 30 |
+
# For MVP, returning random sample questions
|
| 31 |
+
sample_questions = [
|
| 32 |
+
"What is the main purpose of this framework?",
|
| 33 |
+
"How do you install this tool?",
|
| 34 |
+
"What are the key components?",
|
| 35 |
+
"Explain the basic workflow",
|
| 36 |
+
"What are the best practices?"
|
| 37 |
+
]
|
| 38 |
+
return {"Problems": sample_questions}
|
backend/tests/test_quiz.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi.testclient import TestClient
|
| 2 |
+
from backend.app.main import app
|
| 3 |
+
|
| 4 |
+
client = TestClient(app)
|
| 5 |
+
|
| 6 |
+
def test_crawl_endpoint():
|
| 7 |
+
response = client.post(
|
| 8 |
+
"/crawl/",
|
| 9 |
+
json={"url": "https://example.com"}
|
| 10 |
+
)
|
| 11 |
+
assert response.status_code == 200
|
| 12 |
+
assert response.json() == {"status": "received"}
|
| 13 |
+
|
| 14 |
+
def test_problems_endpoint():
|
| 15 |
+
response = client.post(
|
| 16 |
+
"/problems/",
|
| 17 |
+
json={"user_query": "test query"}
|
| 18 |
+
)
|
| 19 |
+
assert response.status_code == 200
|
| 20 |
+
assert "Problems" in response.json()
|
| 21 |
+
assert len(response.json()["Problems"]) == 5
|
frontend/jest.config.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module.exports = {
|
| 2 |
+
preset: 'ts-jest',
|
| 3 |
+
testEnvironment: 'jsdom',
|
| 4 |
+
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
|
| 5 |
+
moduleNameMapper: {
|
| 6 |
+
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
|
| 7 |
+
},
|
| 8 |
+
};
|
frontend/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "simplify-frontend",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"dependencies": {
|
| 6 |
+
"@emotion/react": "^11.11.3",
|
| 7 |
+
"@emotion/styled": "^11.11.0",
|
| 8 |
+
"@mui/material": "^5.15.10",
|
| 9 |
+
"@testing-library/jest-dom": "^6.4.2",
|
| 10 |
+
"@testing-library/react": "^14.2.1",
|
| 11 |
+
"@testing-library/user-event": "^14.5.2",
|
| 12 |
+
"@types/jest": "^29.5.12",
|
| 13 |
+
"@types/node": "^20.11.19",
|
| 14 |
+
"@types/react": "^18.2.56",
|
| 15 |
+
"@types/react-dom": "^18.2.19",
|
| 16 |
+
"ajv": "^8.12.0",
|
| 17 |
+
"ajv-keywords": "^5.1.0",
|
| 18 |
+
"react": "^18.2.0",
|
| 19 |
+
"react-dom": "^18.2.0",
|
| 20 |
+
"react-scripts": "5.0.1",
|
| 21 |
+
"typescript": "^4.9.5",
|
| 22 |
+
"web-vitals": "^3.5.2"
|
| 23 |
+
},
|
| 24 |
+
"scripts": {
|
| 25 |
+
"start": "react-scripts start",
|
| 26 |
+
"build": "CI=true react-scripts build",
|
| 27 |
+
"test": "react-scripts test",
|
| 28 |
+
"eject": "react-scripts eject"
|
| 29 |
+
},
|
| 30 |
+
"eslintConfig": {
|
| 31 |
+
"extends": [
|
| 32 |
+
"react-app",
|
| 33 |
+
"react-app/jest"
|
| 34 |
+
]
|
| 35 |
+
},
|
| 36 |
+
"browserslist": {
|
| 37 |
+
"production": [
|
| 38 |
+
">0.2%",
|
| 39 |
+
"not dead",
|
| 40 |
+
"not op_mini all"
|
| 41 |
+
],
|
| 42 |
+
"development": [
|
| 43 |
+
"last 1 chrome version",
|
| 44 |
+
"last 1 firefox version",
|
| 45 |
+
"last 1 safari version"
|
| 46 |
+
]
|
| 47 |
+
}
|
| 48 |
+
}
|
frontend/public/index.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
+
<meta name="theme-color" content="#000000" />
|
| 7 |
+
<meta name="description" content="Simplify - Quiz Generator" />
|
| 8 |
+
<title>Simplify</title>
|
| 9 |
+
</head>
|
| 10 |
+
<body>
|
| 11 |
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
| 12 |
+
<div id="root"></div>
|
| 13 |
+
</body>
|
| 14 |
+
</html>
|
frontend/public/manifest.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"short_name": "Simplify",
|
| 3 |
+
"name": "Simplify Quiz Generator",
|
| 4 |
+
"start_url": ".",
|
| 5 |
+
"display": "standalone",
|
| 6 |
+
"theme_color": "#000000",
|
| 7 |
+
"background_color": "#ffffff"
|
| 8 |
+
}
|
frontend/public/robots.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
User-agent: *
|
| 2 |
+
Disallow:
|
frontend/src/App.tsx
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Container, CssBaseline, ThemeProvider, createTheme } from '@mui/material';
|
| 2 |
+
import Header from './components/Header';
|
| 3 |
+
import DocumentInput from './components/DocumentInput';
|
| 4 |
+
import QuizGenerator from './components/QuizGenerator';
|
| 5 |
+
import ProblemList from './components/ProblemList';
|
| 6 |
+
import { useState } from 'react';
|
| 7 |
+
|
| 8 |
+
const theme = createTheme({
|
| 9 |
+
palette: {
|
| 10 |
+
primary: {
|
| 11 |
+
main: '#1976d2',
|
| 12 |
+
},
|
| 13 |
+
background: {
|
| 14 |
+
default: '#f5f5f5',
|
| 15 |
+
},
|
| 16 |
+
},
|
| 17 |
+
});
|
| 18 |
+
|
| 19 |
+
function App() {
|
| 20 |
+
const [problems, setProblems] = useState<string[]>([]);
|
| 21 |
+
|
| 22 |
+
return (
|
| 23 |
+
<ThemeProvider theme={theme}>
|
| 24 |
+
<CssBaseline />
|
| 25 |
+
<Container maxWidth="md" sx={{ py: 4 }}>
|
| 26 |
+
<Header />
|
| 27 |
+
<DocumentInput />
|
| 28 |
+
<QuizGenerator onProblemsGenerated={setProblems} />
|
| 29 |
+
<ProblemList problems={problems} />
|
| 30 |
+
</Container>
|
| 31 |
+
</ThemeProvider>
|
| 32 |
+
);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
export default App;
|
frontend/src/components/DocumentInput.tsx
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { TextField, Button, Box } from '@mui/material';
|
| 2 |
+
import { useState } from 'react';
|
| 3 |
+
|
| 4 |
+
function DocumentInput() {
|
| 5 |
+
const [url, setUrl] = useState('');
|
| 6 |
+
|
| 7 |
+
const handleSubmit = async () => {
|
| 8 |
+
try {
|
| 9 |
+
const response = await fetch('http://localhost:8000/crawl/', {
|
| 10 |
+
method: 'POST',
|
| 11 |
+
headers: {
|
| 12 |
+
'Content-Type': 'application/json',
|
| 13 |
+
},
|
| 14 |
+
body: JSON.stringify({ url }),
|
| 15 |
+
});
|
| 16 |
+
if (!response.ok) throw new Error('Network response was not ok');
|
| 17 |
+
setUrl('');
|
| 18 |
+
} catch (error) {
|
| 19 |
+
console.error('Error:', error);
|
| 20 |
+
}
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
return (
|
| 24 |
+
<Box sx={{ mb: 4, display: 'flex', gap: 2 }}>
|
| 25 |
+
<TextField
|
| 26 |
+
fullWidth
|
| 27 |
+
label="Source Documentation"
|
| 28 |
+
value={url}
|
| 29 |
+
onChange={(e) => setUrl(e.target.value)}
|
| 30 |
+
/>
|
| 31 |
+
<Button variant="contained" onClick={handleSubmit}>
|
| 32 |
+
Pull Source Docs
|
| 33 |
+
</Button>
|
| 34 |
+
</Box>
|
| 35 |
+
);
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
export default DocumentInput;
|
frontend/src/components/Header.tsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Typography, Box } from '@mui/material';
|
| 2 |
+
|
| 3 |
+
function Header() {
|
| 4 |
+
return (
|
| 5 |
+
<Box sx={{ mb: 4 }}>
|
| 6 |
+
<Typography variant="h2" component="h1" align="center">
|
| 7 |
+
Simplify
|
| 8 |
+
</Typography>
|
| 9 |
+
</Box>
|
| 10 |
+
);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
export default Header;
|
frontend/src/components/ProblemList.tsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { List, ListItem, Paper, Typography } from '@mui/material';
|
| 2 |
+
|
| 3 |
+
interface ProblemListProps {
|
| 4 |
+
problems: string[];
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
function ProblemList({ problems }: ProblemListProps) {
|
| 8 |
+
if (problems.length === 0) return null;
|
| 9 |
+
|
| 10 |
+
return (
|
| 11 |
+
<Paper sx={{ p: 2 }}>
|
| 12 |
+
<Typography variant="h6" gutterBottom>
|
| 13 |
+
Generated Problems
|
| 14 |
+
</Typography>
|
| 15 |
+
<List>
|
| 16 |
+
{problems.map((problem, index) => (
|
| 17 |
+
<ListItem key={index}>
|
| 18 |
+
{index + 1}. {problem}
|
| 19 |
+
</ListItem>
|
| 20 |
+
))}
|
| 21 |
+
</List>
|
| 22 |
+
</Paper>
|
| 23 |
+
);
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
export default ProblemList;
|
frontend/src/components/QuizGenerator.tsx
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { TextField, Button, Box } from '@mui/material';
|
| 2 |
+
import { useState } from 'react';
|
| 3 |
+
|
| 4 |
+
interface QuizGeneratorProps {
|
| 5 |
+
onProblemsGenerated: (problems: string[]) => void;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
function QuizGenerator({ onProblemsGenerated }: QuizGeneratorProps) {
|
| 9 |
+
const [query, setQuery] = useState('');
|
| 10 |
+
|
| 11 |
+
const handleGenerate = async () => {
|
| 12 |
+
try {
|
| 13 |
+
const response = await fetch('http://localhost:8000/problems/', {
|
| 14 |
+
method: 'POST',
|
| 15 |
+
headers: {
|
| 16 |
+
'Content-Type': 'application/json',
|
| 17 |
+
},
|
| 18 |
+
body: JSON.stringify({ user_query: query }),
|
| 19 |
+
});
|
| 20 |
+
if (!response.ok) throw new Error('Network response was not ok');
|
| 21 |
+
const data = await response.json();
|
| 22 |
+
onProblemsGenerated(data.Problems);
|
| 23 |
+
setQuery('');
|
| 24 |
+
} catch (error) {
|
| 25 |
+
console.error('Error:', error);
|
| 26 |
+
}
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
return (
|
| 30 |
+
<Box sx={{ mb: 4, display: 'flex', gap: 2 }}>
|
| 31 |
+
<TextField
|
| 32 |
+
fullWidth
|
| 33 |
+
label="Quiz topic?"
|
| 34 |
+
value={query}
|
| 35 |
+
onChange={(e) => setQuery(e.target.value)}
|
| 36 |
+
/>
|
| 37 |
+
<Button variant="contained" onClick={handleGenerate}>
|
| 38 |
+
Generate
|
| 39 |
+
</Button>
|
| 40 |
+
</Box>
|
| 41 |
+
);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
export default QuizGenerator;
|
frontend/src/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import ReactDOM from 'react-dom/client';
|
| 3 |
+
import App from './App';
|
| 4 |
+
|
| 5 |
+
const root = ReactDOM.createRoot(
|
| 6 |
+
document.getElementById('root')
|
| 7 |
+
);
|
| 8 |
+
|
| 9 |
+
root.render(
|
| 10 |
+
<React.StrictMode>
|
| 11 |
+
<App />
|
| 12 |
+
</React.StrictMode>
|
| 13 |
+
);
|
frontend/src/tests/App.test.tsx
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { render, screen } from '@testing-library/react';
|
| 2 |
+
import userEvent from '@testing-library/user-event';
|
| 3 |
+
import App from '../App';
|
| 4 |
+
|
| 5 |
+
describe('App', () => {
|
| 6 |
+
test('renders main components', () => {
|
| 7 |
+
render(<App />);
|
| 8 |
+
|
| 9 |
+
// Check for title
|
| 10 |
+
expect(screen.getByText('Simplify')).toBeInTheDocument();
|
| 11 |
+
|
| 12 |
+
// Check for input fields
|
| 13 |
+
expect(screen.getByLabelText('Source Documentation')).toBeInTheDocument();
|
| 14 |
+
expect(screen.getByLabelText('Quiz topic?')).toBeInTheDocument();
|
| 15 |
+
|
| 16 |
+
// Check for buttons
|
| 17 |
+
expect(screen.getByText('Pull Source Docs')).toBeInTheDocument();
|
| 18 |
+
expect(screen.getByText('Generate')).toBeInTheDocument();
|
| 19 |
+
});
|
| 20 |
+
});
|
frontend/src/tests/components/DocumentInput.test.tsx
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { render, screen, fireEvent } from '@testing-library/react';
|
| 2 |
+
import DocumentInput from '../../components/DocumentInput';
|
| 3 |
+
|
| 4 |
+
describe('DocumentInput', () => {
|
| 5 |
+
beforeEach(() => {
|
| 6 |
+
global.fetch = jest.fn();
|
| 7 |
+
});
|
| 8 |
+
|
| 9 |
+
test('submits URL when button is clicked', async () => {
|
| 10 |
+
const mockFetch = global.fetch as jest.Mock;
|
| 11 |
+
mockFetch.mockResolvedValueOnce({
|
| 12 |
+
ok: true,
|
| 13 |
+
json: async () => ({ status: 'received' }),
|
| 14 |
+
});
|
| 15 |
+
|
| 16 |
+
render(<DocumentInput />);
|
| 17 |
+
|
| 18 |
+
const input = screen.getByLabelText('Source Documentation');
|
| 19 |
+
const button = screen.getByText('Pull Source Docs');
|
| 20 |
+
|
| 21 |
+
await fireEvent.change(input, { target: { value: 'https://example.com' } });
|
| 22 |
+
await fireEvent.click(button);
|
| 23 |
+
|
| 24 |
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:8000/crawl/', {
|
| 25 |
+
method: 'POST',
|
| 26 |
+
headers: { 'Content-Type': 'application/json' },
|
| 27 |
+
body: JSON.stringify({ url: 'https://example.com' }),
|
| 28 |
+
});
|
| 29 |
+
});
|
| 30 |
+
});
|
frontend/src/tests/components/QuizGenerator.test.tsx
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { render, screen, fireEvent } from '@testing-library/react';
|
| 2 |
+
import QuizGenerator from '../../components/QuizGenerator';
|
| 3 |
+
|
| 4 |
+
describe('QuizGenerator', () => {
|
| 5 |
+
const mockOnProblemsGenerated = jest.fn();
|
| 6 |
+
|
| 7 |
+
beforeEach(() => {
|
| 8 |
+
global.fetch = jest.fn();
|
| 9 |
+
mockOnProblemsGenerated.mockClear();
|
| 10 |
+
});
|
| 11 |
+
|
| 12 |
+
test('generates problems when button is clicked', async () => {
|
| 13 |
+
const mockProblems = ['Problem 1', 'Problem 2'];
|
| 14 |
+
const mockFetch = global.fetch as jest.Mock;
|
| 15 |
+
mockFetch.mockResolvedValueOnce({
|
| 16 |
+
ok: true,
|
| 17 |
+
json: async () => ({ Problems: mockProblems }),
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
render(<QuizGenerator onProblemsGenerated={mockOnProblemsGenerated} />);
|
| 21 |
+
|
| 22 |
+
const input = screen.getByLabelText('Quiz topic?');
|
| 23 |
+
const button = screen.getByText('Generate');
|
| 24 |
+
|
| 25 |
+
await fireEvent.change(input, { target: { value: 'React' } });
|
| 26 |
+
await fireEvent.click(button);
|
| 27 |
+
|
| 28 |
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:8000/problems/', {
|
| 29 |
+
method: 'POST',
|
| 30 |
+
headers: { 'Content-Type': 'application/json' },
|
| 31 |
+
body: JSON.stringify({ user_query: 'React' }),
|
| 32 |
+
});
|
| 33 |
+
expect(mockOnProblemsGenerated).toHaveBeenCalledWith(mockProblems);
|
| 34 |
+
});
|
| 35 |
+
});
|
frontend/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "es5",
|
| 4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
| 5 |
+
"allowJs": true,
|
| 6 |
+
"skipLibCheck": true,
|
| 7 |
+
"esModuleInterop": true,
|
| 8 |
+
"allowSyntheticDefaultImports": true,
|
| 9 |
+
"strict": true,
|
| 10 |
+
"forceConsistentCasingInFileNames": true,
|
| 11 |
+
"noFallthroughCasesInSwitch": true,
|
| 12 |
+
"module": "esnext",
|
| 13 |
+
"moduleResolution": "node",
|
| 14 |
+
"resolveJsonModule": true,
|
| 15 |
+
"isolatedModules": true,
|
| 16 |
+
"noEmit": true,
|
| 17 |
+
"jsx": "react-jsx",
|
| 18 |
+
"types": ["jest", "node"]
|
| 19 |
+
},
|
| 20 |
+
"include": ["src/**/*"],
|
| 21 |
+
"exclude": ["node_modules"]
|
| 22 |
+
}
|
pyproject.toml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "simplify"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "LLM System to generate quizzes that simplify the learning process of tools and frameworks"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.12"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"chainlit>=2.0.4",
|
| 9 |
+
"numpy>=2.2.2",
|
| 10 |
+
"openai>=1.59.9",
|
| 11 |
+
"pydantic==2.10.1",
|
| 12 |
+
"pypdf2>=3.0.1",
|
| 13 |
+
"websockets>=14.2",
|
| 14 |
+
"fastapi>=0.110.0",
|
| 15 |
+
"uvicorn>=0.27.1",
|
| 16 |
+
"pytest>=8.0.0",
|
| 17 |
+
"httpx>=0.26.0"
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
[tool.pytest.ini_options]
|
| 21 |
+
testpaths = ["backend/tests"]
|
| 22 |
+
python_files = ["test_*.py"]
|
pytest.ini
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[pytest]
|
| 2 |
+
pythonpath = .
|