Rsr2425 commited on
Commit
58973c7
·
1 Parent(s): 560fd32

Got basics of FE/BE running. BE passes tests

Browse files
.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 = .