AleksanderObuchowski commited on
Commit
e4f1db2
·
0 Parent(s):

Initial commit for Hugging Face Spaces

Browse files
.dockerignore ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Node modules
2
+ node_modules
3
+ */node_modules
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+
8
+ # Build outputs
9
+ frontend/dist
10
+ backend/dist
11
+ *.tgz
12
+
13
+ # Development files
14
+ .env.local
15
+ .env.development.local
16
+ .env.test.local
17
+ .env.production.local
18
+
19
+ # IDE and editor files
20
+ .vscode
21
+ .idea
22
+ *.swp
23
+ *.swo
24
+ *~
25
+
26
+ # OS generated files
27
+ .DS_Store
28
+ .DS_Store?
29
+ ._*
30
+ .Spotlight-V100
31
+ .Trashes
32
+ ehthumbs.db
33
+ Thumbs.db
34
+
35
+ # Git
36
+ .git
37
+ .gitignore
38
+
39
+ # Docker
40
+ Dockerfile*
41
+ docker-compose*
42
+ .dockerignore
43
+
44
+ # Logs
45
+ logs
46
+ *.log
47
+
48
+ # Runtime data
49
+ pids
50
+ *.pid
51
+ *.seed
52
+ *.pid.lock
53
+
54
+ # Coverage directory used by tools like istanbul
55
+ coverage
56
+ *.lcov
57
+
58
+ # Dependency directories
59
+ .npm
60
+ .eslintcache
61
+
62
+ # Optional npm cache directory
63
+ .npm
64
+
65
+ # Optional REPL history
66
+ .node_repl_history
67
+
68
+ # Output of 'npm pack'
69
+ *.tgz
70
+
71
+ # Yarn Integrity file
72
+ .yarn-integrity
73
+
74
+ # dotenv environment variables file
75
+ .env
76
+ .env.test
77
+
78
+ # Temporary folders
79
+ tmp/
80
+ temp/
.gitignore ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Claude Code specific
2
+ CLAUDE.md
3
+
4
+ # Dependencies
5
+ node_modules/
6
+ */node_modules/
7
+ npm-debug.log*
8
+ yarn-debug.log*
9
+ yarn-error.log*
10
+
11
+ # Build outputs
12
+ frontend/dist/
13
+ backend/dist/
14
+ *.tgz
15
+
16
+ # Environment files
17
+ .env
18
+ .env.local
19
+ .env.development.local
20
+ .env.test.local
21
+ .env.production.local
22
+
23
+ # IDE files
24
+ .vscode/
25
+ .idea/
26
+ *.swp
27
+ *.swo
28
+ *~
29
+
30
+ # OS files
31
+ .DS_Store
32
+ .DS_Store?
33
+ ._*
34
+ .Spotlight-V100
35
+ .Trashes
36
+ ehthumbs.db
37
+ Thumbs.db
38
+
39
+ # Logs
40
+ logs
41
+ *.log
42
+
43
+ # Runtime data
44
+ pids
45
+ *.pid
46
+ *.seed
47
+ *.pid.lock
48
+
49
+ # Coverage
50
+ coverage/
51
+ *.lcov
52
+
53
+ # Cache
54
+ .npm
55
+ .eslintcache
56
+ .node_repl_history
57
+
58
+ # Temporary
59
+ tmp/
60
+ temp/
Dockerfile ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multi-stage build for React + Node.js application
2
+ FROM node:18-alpine AS base
3
+
4
+ # Install dumb-init for proper signal handling
5
+ RUN apk add --no-cache dumb-init
6
+
7
+ # Create app directory
8
+ WORKDIR /app
9
+
10
+ # Copy package files
11
+ COPY package*.json ./
12
+ COPY backend/package*.json ./backend/
13
+ COPY frontend/package*.json ./frontend/
14
+
15
+ # Install dependencies
16
+ RUN npm ci --only=production && \
17
+ cd backend && npm ci --only=production && \
18
+ cd ../frontend && npm ci
19
+
20
+ # Frontend build stage
21
+ FROM base AS frontend-build
22
+ WORKDIR /app/frontend
23
+ COPY frontend/ .
24
+ RUN npm run build
25
+
26
+ # Production stage
27
+ FROM node:18-alpine AS production
28
+
29
+ # Install dumb-init
30
+ RUN apk add --no-cache dumb-init
31
+
32
+ # Create user with UID 1000 as required by HF Spaces
33
+ RUN addgroup -g 1000 -S user && \
34
+ adduser -S user -u 1000
35
+
36
+ # Set working directory
37
+ WORKDIR /app
38
+
39
+ # Copy backend dependencies and source
40
+ COPY --from=base --chown=user /app/backend/node_modules ./backend/node_modules
41
+ COPY --chown=user backend/ ./backend/
42
+ COPY --chown=user data/ ./data/
43
+
44
+ # Copy built frontend
45
+ COPY --from=frontend-build --chown=user /app/frontend/dist ./frontend/dist
46
+
47
+ # Switch to user
48
+ USER user
49
+
50
+ # Expose port
51
+ EXPOSE 3001
52
+
53
+ # Health check
54
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
55
+ CMD node -e "require('http').get('http://localhost:3001/api/algorithms', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))"
56
+
57
+ # Start the application
58
+ CMD ["dumb-init", "node", "backend/server.js"]
README.md ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: PubMed AI Explorer
3
+ emoji: 🔬
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ app_port: 3001
8
+ ---
9
+
10
+ # PubMed AI Explorer
11
+
12
+ A web application that helps users explore AI algorithms used in medical research papers from PubMed.
13
+
14
+ ## Features
15
+
16
+ - **Dashboard**: View statistics and trends of AI algorithms used in medical papers
17
+ - **Algorithm Search**: Search for specific medical problems and see which algorithms are most commonly used
18
+ - **Interactive Charts**: Visual representation of algorithm usage and distribution
19
+ - **PubMed Integration**: Direct links to relevant papers on PubMed
20
+ - **Editable Algorithm Database**: JSON-based configuration for easy algorithm management
21
+
22
+ ## Tech Stack
23
+
24
+ - **Frontend**: React + TypeScript + Vite + Tailwind CSS
25
+ - **Backend**: Node.js + Express
26
+ - **Charts**: Recharts
27
+ - **API**: PubMed E-utilities
28
+ - **Icons**: Lucide React
29
+
30
+ ## Installation
31
+
32
+ 1. Install dependencies for all parts:
33
+ ```bash
34
+ npm run install:all
35
+ ```
36
+
37
+ 2. Start the development server:
38
+ ```bash
39
+ npm run dev
40
+ ```
41
+
42
+ This will start both the backend (port 3001) and frontend (port 3000) concurrently.
43
+
44
+ ## Usage
45
+
46
+ 1. **Dashboard**: Visit the homepage to see overall statistics of AI algorithms in medical research
47
+ 2. **Search**: Use the search page to find algorithms used for specific medical problems
48
+ 3. **Algorithm Management**: Edit `/data/algorithms.json` to add, remove, or modify algorithms and their synonyms
49
+
50
+ ## Algorithm Configuration
51
+
52
+ The algorithms are configured in `/data/algorithms.json`. Each algorithm has:
53
+ - `name`: Display name
54
+ - `category`: Either "classical_ml" or "deep_learning"
55
+ - `description`: Brief description of the algorithm
56
+ - `synonyms`: Array of terms used to search PubMed (includes variations, abbreviations, etc.)
57
+
58
+ ## API Endpoints
59
+
60
+ - `GET /api/algorithms` - Get all algorithms
61
+ - `PUT /api/algorithms` - Update algorithms configuration
62
+ - `GET /api/search/dashboard-stats` - Get dashboard statistics
63
+ - `POST /api/search/problem` - Search algorithms for a specific problem
64
+ - `GET /api/search/pubmed-link` - Generate PubMed search URL
65
+ - `GET /api/pubmed/search` - Search PubMed papers
66
+ - `GET /api/pubmed/paper/:pmid` - Get specific paper details
67
+
68
+ ## Development
69
+
70
+ - Backend runs on port 3001
71
+ - Frontend runs on port 3000 with proxy to backend
72
+ - Hot reload enabled for both frontend and backend
backend/package-lock.json ADDED
@@ -0,0 +1,1406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "pubmed-ai-explorer-backend",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "pubmed-ai-explorer-backend",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "axios": "^1.6.2",
12
+ "cors": "^2.8.5",
13
+ "dotenv": "^16.3.1",
14
+ "express": "^4.18.2",
15
+ "node-cache": "^5.1.2",
16
+ "xml2js": "^0.6.2"
17
+ },
18
+ "devDependencies": {
19
+ "nodemon": "^3.0.2"
20
+ }
21
+ },
22
+ "node_modules/accepts": {
23
+ "version": "1.3.8",
24
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
25
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "mime-types": "~2.1.34",
29
+ "negotiator": "0.6.3"
30
+ },
31
+ "engines": {
32
+ "node": ">= 0.6"
33
+ }
34
+ },
35
+ "node_modules/anymatch": {
36
+ "version": "3.1.3",
37
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
38
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
39
+ "dev": true,
40
+ "license": "ISC",
41
+ "dependencies": {
42
+ "normalize-path": "^3.0.0",
43
+ "picomatch": "^2.0.4"
44
+ },
45
+ "engines": {
46
+ "node": ">= 8"
47
+ }
48
+ },
49
+ "node_modules/array-flatten": {
50
+ "version": "1.1.1",
51
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
52
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
53
+ "license": "MIT"
54
+ },
55
+ "node_modules/asynckit": {
56
+ "version": "0.4.0",
57
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
58
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
59
+ "license": "MIT"
60
+ },
61
+ "node_modules/axios": {
62
+ "version": "1.9.0",
63
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
64
+ "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
65
+ "license": "MIT",
66
+ "dependencies": {
67
+ "follow-redirects": "^1.15.6",
68
+ "form-data": "^4.0.0",
69
+ "proxy-from-env": "^1.1.0"
70
+ }
71
+ },
72
+ "node_modules/balanced-match": {
73
+ "version": "1.0.2",
74
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
75
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
76
+ "dev": true,
77
+ "license": "MIT"
78
+ },
79
+ "node_modules/binary-extensions": {
80
+ "version": "2.3.0",
81
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
82
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
83
+ "dev": true,
84
+ "license": "MIT",
85
+ "engines": {
86
+ "node": ">=8"
87
+ },
88
+ "funding": {
89
+ "url": "https://github.com/sponsors/sindresorhus"
90
+ }
91
+ },
92
+ "node_modules/body-parser": {
93
+ "version": "1.20.3",
94
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
95
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
96
+ "license": "MIT",
97
+ "dependencies": {
98
+ "bytes": "3.1.2",
99
+ "content-type": "~1.0.5",
100
+ "debug": "2.6.9",
101
+ "depd": "2.0.0",
102
+ "destroy": "1.2.0",
103
+ "http-errors": "2.0.0",
104
+ "iconv-lite": "0.4.24",
105
+ "on-finished": "2.4.1",
106
+ "qs": "6.13.0",
107
+ "raw-body": "2.5.2",
108
+ "type-is": "~1.6.18",
109
+ "unpipe": "1.0.0"
110
+ },
111
+ "engines": {
112
+ "node": ">= 0.8",
113
+ "npm": "1.2.8000 || >= 1.4.16"
114
+ }
115
+ },
116
+ "node_modules/brace-expansion": {
117
+ "version": "1.1.11",
118
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
119
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
120
+ "dev": true,
121
+ "license": "MIT",
122
+ "dependencies": {
123
+ "balanced-match": "^1.0.0",
124
+ "concat-map": "0.0.1"
125
+ }
126
+ },
127
+ "node_modules/braces": {
128
+ "version": "3.0.3",
129
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
130
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
131
+ "dev": true,
132
+ "license": "MIT",
133
+ "dependencies": {
134
+ "fill-range": "^7.1.1"
135
+ },
136
+ "engines": {
137
+ "node": ">=8"
138
+ }
139
+ },
140
+ "node_modules/bytes": {
141
+ "version": "3.1.2",
142
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
143
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
144
+ "license": "MIT",
145
+ "engines": {
146
+ "node": ">= 0.8"
147
+ }
148
+ },
149
+ "node_modules/call-bind-apply-helpers": {
150
+ "version": "1.0.2",
151
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
152
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
153
+ "license": "MIT",
154
+ "dependencies": {
155
+ "es-errors": "^1.3.0",
156
+ "function-bind": "^1.1.2"
157
+ },
158
+ "engines": {
159
+ "node": ">= 0.4"
160
+ }
161
+ },
162
+ "node_modules/call-bound": {
163
+ "version": "1.0.4",
164
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
165
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
166
+ "license": "MIT",
167
+ "dependencies": {
168
+ "call-bind-apply-helpers": "^1.0.2",
169
+ "get-intrinsic": "^1.3.0"
170
+ },
171
+ "engines": {
172
+ "node": ">= 0.4"
173
+ },
174
+ "funding": {
175
+ "url": "https://github.com/sponsors/ljharb"
176
+ }
177
+ },
178
+ "node_modules/chokidar": {
179
+ "version": "3.6.0",
180
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
181
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
182
+ "dev": true,
183
+ "license": "MIT",
184
+ "dependencies": {
185
+ "anymatch": "~3.1.2",
186
+ "braces": "~3.0.2",
187
+ "glob-parent": "~5.1.2",
188
+ "is-binary-path": "~2.1.0",
189
+ "is-glob": "~4.0.1",
190
+ "normalize-path": "~3.0.0",
191
+ "readdirp": "~3.6.0"
192
+ },
193
+ "engines": {
194
+ "node": ">= 8.10.0"
195
+ },
196
+ "funding": {
197
+ "url": "https://paulmillr.com/funding/"
198
+ },
199
+ "optionalDependencies": {
200
+ "fsevents": "~2.3.2"
201
+ }
202
+ },
203
+ "node_modules/clone": {
204
+ "version": "2.1.2",
205
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
206
+ "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
207
+ "license": "MIT",
208
+ "engines": {
209
+ "node": ">=0.8"
210
+ }
211
+ },
212
+ "node_modules/combined-stream": {
213
+ "version": "1.0.8",
214
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
215
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
216
+ "license": "MIT",
217
+ "dependencies": {
218
+ "delayed-stream": "~1.0.0"
219
+ },
220
+ "engines": {
221
+ "node": ">= 0.8"
222
+ }
223
+ },
224
+ "node_modules/concat-map": {
225
+ "version": "0.0.1",
226
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
227
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
228
+ "dev": true,
229
+ "license": "MIT"
230
+ },
231
+ "node_modules/content-disposition": {
232
+ "version": "0.5.4",
233
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
234
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
235
+ "license": "MIT",
236
+ "dependencies": {
237
+ "safe-buffer": "5.2.1"
238
+ },
239
+ "engines": {
240
+ "node": ">= 0.6"
241
+ }
242
+ },
243
+ "node_modules/content-type": {
244
+ "version": "1.0.5",
245
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
246
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
247
+ "license": "MIT",
248
+ "engines": {
249
+ "node": ">= 0.6"
250
+ }
251
+ },
252
+ "node_modules/cookie": {
253
+ "version": "0.7.1",
254
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
255
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
256
+ "license": "MIT",
257
+ "engines": {
258
+ "node": ">= 0.6"
259
+ }
260
+ },
261
+ "node_modules/cookie-signature": {
262
+ "version": "1.0.6",
263
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
264
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
265
+ "license": "MIT"
266
+ },
267
+ "node_modules/cors": {
268
+ "version": "2.8.5",
269
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
270
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
271
+ "license": "MIT",
272
+ "dependencies": {
273
+ "object-assign": "^4",
274
+ "vary": "^1"
275
+ },
276
+ "engines": {
277
+ "node": ">= 0.10"
278
+ }
279
+ },
280
+ "node_modules/debug": {
281
+ "version": "2.6.9",
282
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
283
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
284
+ "license": "MIT",
285
+ "dependencies": {
286
+ "ms": "2.0.0"
287
+ }
288
+ },
289
+ "node_modules/delayed-stream": {
290
+ "version": "1.0.0",
291
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
292
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
293
+ "license": "MIT",
294
+ "engines": {
295
+ "node": ">=0.4.0"
296
+ }
297
+ },
298
+ "node_modules/depd": {
299
+ "version": "2.0.0",
300
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
301
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
302
+ "license": "MIT",
303
+ "engines": {
304
+ "node": ">= 0.8"
305
+ }
306
+ },
307
+ "node_modules/destroy": {
308
+ "version": "1.2.0",
309
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
310
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
311
+ "license": "MIT",
312
+ "engines": {
313
+ "node": ">= 0.8",
314
+ "npm": "1.2.8000 || >= 1.4.16"
315
+ }
316
+ },
317
+ "node_modules/dotenv": {
318
+ "version": "16.5.0",
319
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
320
+ "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
321
+ "license": "BSD-2-Clause",
322
+ "engines": {
323
+ "node": ">=12"
324
+ },
325
+ "funding": {
326
+ "url": "https://dotenvx.com"
327
+ }
328
+ },
329
+ "node_modules/dunder-proto": {
330
+ "version": "1.0.1",
331
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
332
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
333
+ "license": "MIT",
334
+ "dependencies": {
335
+ "call-bind-apply-helpers": "^1.0.1",
336
+ "es-errors": "^1.3.0",
337
+ "gopd": "^1.2.0"
338
+ },
339
+ "engines": {
340
+ "node": ">= 0.4"
341
+ }
342
+ },
343
+ "node_modules/ee-first": {
344
+ "version": "1.1.1",
345
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
346
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
347
+ "license": "MIT"
348
+ },
349
+ "node_modules/encodeurl": {
350
+ "version": "2.0.0",
351
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
352
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
353
+ "license": "MIT",
354
+ "engines": {
355
+ "node": ">= 0.8"
356
+ }
357
+ },
358
+ "node_modules/es-define-property": {
359
+ "version": "1.0.1",
360
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
361
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
362
+ "license": "MIT",
363
+ "engines": {
364
+ "node": ">= 0.4"
365
+ }
366
+ },
367
+ "node_modules/es-errors": {
368
+ "version": "1.3.0",
369
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
370
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
371
+ "license": "MIT",
372
+ "engines": {
373
+ "node": ">= 0.4"
374
+ }
375
+ },
376
+ "node_modules/es-object-atoms": {
377
+ "version": "1.1.1",
378
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
379
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
380
+ "license": "MIT",
381
+ "dependencies": {
382
+ "es-errors": "^1.3.0"
383
+ },
384
+ "engines": {
385
+ "node": ">= 0.4"
386
+ }
387
+ },
388
+ "node_modules/es-set-tostringtag": {
389
+ "version": "2.1.0",
390
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
391
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
392
+ "license": "MIT",
393
+ "dependencies": {
394
+ "es-errors": "^1.3.0",
395
+ "get-intrinsic": "^1.2.6",
396
+ "has-tostringtag": "^1.0.2",
397
+ "hasown": "^2.0.2"
398
+ },
399
+ "engines": {
400
+ "node": ">= 0.4"
401
+ }
402
+ },
403
+ "node_modules/escape-html": {
404
+ "version": "1.0.3",
405
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
406
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
407
+ "license": "MIT"
408
+ },
409
+ "node_modules/etag": {
410
+ "version": "1.8.1",
411
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
412
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
413
+ "license": "MIT",
414
+ "engines": {
415
+ "node": ">= 0.6"
416
+ }
417
+ },
418
+ "node_modules/express": {
419
+ "version": "4.21.2",
420
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
421
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
422
+ "license": "MIT",
423
+ "dependencies": {
424
+ "accepts": "~1.3.8",
425
+ "array-flatten": "1.1.1",
426
+ "body-parser": "1.20.3",
427
+ "content-disposition": "0.5.4",
428
+ "content-type": "~1.0.4",
429
+ "cookie": "0.7.1",
430
+ "cookie-signature": "1.0.6",
431
+ "debug": "2.6.9",
432
+ "depd": "2.0.0",
433
+ "encodeurl": "~2.0.0",
434
+ "escape-html": "~1.0.3",
435
+ "etag": "~1.8.1",
436
+ "finalhandler": "1.3.1",
437
+ "fresh": "0.5.2",
438
+ "http-errors": "2.0.0",
439
+ "merge-descriptors": "1.0.3",
440
+ "methods": "~1.1.2",
441
+ "on-finished": "2.4.1",
442
+ "parseurl": "~1.3.3",
443
+ "path-to-regexp": "0.1.12",
444
+ "proxy-addr": "~2.0.7",
445
+ "qs": "6.13.0",
446
+ "range-parser": "~1.2.1",
447
+ "safe-buffer": "5.2.1",
448
+ "send": "0.19.0",
449
+ "serve-static": "1.16.2",
450
+ "setprototypeof": "1.2.0",
451
+ "statuses": "2.0.1",
452
+ "type-is": "~1.6.18",
453
+ "utils-merge": "1.0.1",
454
+ "vary": "~1.1.2"
455
+ },
456
+ "engines": {
457
+ "node": ">= 0.10.0"
458
+ },
459
+ "funding": {
460
+ "type": "opencollective",
461
+ "url": "https://opencollective.com/express"
462
+ }
463
+ },
464
+ "node_modules/fill-range": {
465
+ "version": "7.1.1",
466
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
467
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
468
+ "dev": true,
469
+ "license": "MIT",
470
+ "dependencies": {
471
+ "to-regex-range": "^5.0.1"
472
+ },
473
+ "engines": {
474
+ "node": ">=8"
475
+ }
476
+ },
477
+ "node_modules/finalhandler": {
478
+ "version": "1.3.1",
479
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
480
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
481
+ "license": "MIT",
482
+ "dependencies": {
483
+ "debug": "2.6.9",
484
+ "encodeurl": "~2.0.0",
485
+ "escape-html": "~1.0.3",
486
+ "on-finished": "2.4.1",
487
+ "parseurl": "~1.3.3",
488
+ "statuses": "2.0.1",
489
+ "unpipe": "~1.0.0"
490
+ },
491
+ "engines": {
492
+ "node": ">= 0.8"
493
+ }
494
+ },
495
+ "node_modules/follow-redirects": {
496
+ "version": "1.15.9",
497
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
498
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
499
+ "funding": [
500
+ {
501
+ "type": "individual",
502
+ "url": "https://github.com/sponsors/RubenVerborgh"
503
+ }
504
+ ],
505
+ "license": "MIT",
506
+ "engines": {
507
+ "node": ">=4.0"
508
+ },
509
+ "peerDependenciesMeta": {
510
+ "debug": {
511
+ "optional": true
512
+ }
513
+ }
514
+ },
515
+ "node_modules/form-data": {
516
+ "version": "4.0.3",
517
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
518
+ "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
519
+ "license": "MIT",
520
+ "dependencies": {
521
+ "asynckit": "^0.4.0",
522
+ "combined-stream": "^1.0.8",
523
+ "es-set-tostringtag": "^2.1.0",
524
+ "hasown": "^2.0.2",
525
+ "mime-types": "^2.1.12"
526
+ },
527
+ "engines": {
528
+ "node": ">= 6"
529
+ }
530
+ },
531
+ "node_modules/forwarded": {
532
+ "version": "0.2.0",
533
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
534
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
535
+ "license": "MIT",
536
+ "engines": {
537
+ "node": ">= 0.6"
538
+ }
539
+ },
540
+ "node_modules/fresh": {
541
+ "version": "0.5.2",
542
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
543
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
544
+ "license": "MIT",
545
+ "engines": {
546
+ "node": ">= 0.6"
547
+ }
548
+ },
549
+ "node_modules/fsevents": {
550
+ "version": "2.3.3",
551
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
552
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
553
+ "dev": true,
554
+ "hasInstallScript": true,
555
+ "license": "MIT",
556
+ "optional": true,
557
+ "os": [
558
+ "darwin"
559
+ ],
560
+ "engines": {
561
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
562
+ }
563
+ },
564
+ "node_modules/function-bind": {
565
+ "version": "1.1.2",
566
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
567
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
568
+ "license": "MIT",
569
+ "funding": {
570
+ "url": "https://github.com/sponsors/ljharb"
571
+ }
572
+ },
573
+ "node_modules/get-intrinsic": {
574
+ "version": "1.3.0",
575
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
576
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
577
+ "license": "MIT",
578
+ "dependencies": {
579
+ "call-bind-apply-helpers": "^1.0.2",
580
+ "es-define-property": "^1.0.1",
581
+ "es-errors": "^1.3.0",
582
+ "es-object-atoms": "^1.1.1",
583
+ "function-bind": "^1.1.2",
584
+ "get-proto": "^1.0.1",
585
+ "gopd": "^1.2.0",
586
+ "has-symbols": "^1.1.0",
587
+ "hasown": "^2.0.2",
588
+ "math-intrinsics": "^1.1.0"
589
+ },
590
+ "engines": {
591
+ "node": ">= 0.4"
592
+ },
593
+ "funding": {
594
+ "url": "https://github.com/sponsors/ljharb"
595
+ }
596
+ },
597
+ "node_modules/get-proto": {
598
+ "version": "1.0.1",
599
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
600
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
601
+ "license": "MIT",
602
+ "dependencies": {
603
+ "dunder-proto": "^1.0.1",
604
+ "es-object-atoms": "^1.0.0"
605
+ },
606
+ "engines": {
607
+ "node": ">= 0.4"
608
+ }
609
+ },
610
+ "node_modules/glob-parent": {
611
+ "version": "5.1.2",
612
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
613
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
614
+ "dev": true,
615
+ "license": "ISC",
616
+ "dependencies": {
617
+ "is-glob": "^4.0.1"
618
+ },
619
+ "engines": {
620
+ "node": ">= 6"
621
+ }
622
+ },
623
+ "node_modules/gopd": {
624
+ "version": "1.2.0",
625
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
626
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
627
+ "license": "MIT",
628
+ "engines": {
629
+ "node": ">= 0.4"
630
+ },
631
+ "funding": {
632
+ "url": "https://github.com/sponsors/ljharb"
633
+ }
634
+ },
635
+ "node_modules/has-flag": {
636
+ "version": "3.0.0",
637
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
638
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
639
+ "dev": true,
640
+ "license": "MIT",
641
+ "engines": {
642
+ "node": ">=4"
643
+ }
644
+ },
645
+ "node_modules/has-symbols": {
646
+ "version": "1.1.0",
647
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
648
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
649
+ "license": "MIT",
650
+ "engines": {
651
+ "node": ">= 0.4"
652
+ },
653
+ "funding": {
654
+ "url": "https://github.com/sponsors/ljharb"
655
+ }
656
+ },
657
+ "node_modules/has-tostringtag": {
658
+ "version": "1.0.2",
659
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
660
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
661
+ "license": "MIT",
662
+ "dependencies": {
663
+ "has-symbols": "^1.0.3"
664
+ },
665
+ "engines": {
666
+ "node": ">= 0.4"
667
+ },
668
+ "funding": {
669
+ "url": "https://github.com/sponsors/ljharb"
670
+ }
671
+ },
672
+ "node_modules/hasown": {
673
+ "version": "2.0.2",
674
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
675
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
676
+ "license": "MIT",
677
+ "dependencies": {
678
+ "function-bind": "^1.1.2"
679
+ },
680
+ "engines": {
681
+ "node": ">= 0.4"
682
+ }
683
+ },
684
+ "node_modules/http-errors": {
685
+ "version": "2.0.0",
686
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
687
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
688
+ "license": "MIT",
689
+ "dependencies": {
690
+ "depd": "2.0.0",
691
+ "inherits": "2.0.4",
692
+ "setprototypeof": "1.2.0",
693
+ "statuses": "2.0.1",
694
+ "toidentifier": "1.0.1"
695
+ },
696
+ "engines": {
697
+ "node": ">= 0.8"
698
+ }
699
+ },
700
+ "node_modules/iconv-lite": {
701
+ "version": "0.4.24",
702
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
703
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
704
+ "license": "MIT",
705
+ "dependencies": {
706
+ "safer-buffer": ">= 2.1.2 < 3"
707
+ },
708
+ "engines": {
709
+ "node": ">=0.10.0"
710
+ }
711
+ },
712
+ "node_modules/ignore-by-default": {
713
+ "version": "1.0.1",
714
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
715
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
716
+ "dev": true,
717
+ "license": "ISC"
718
+ },
719
+ "node_modules/inherits": {
720
+ "version": "2.0.4",
721
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
722
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
723
+ "license": "ISC"
724
+ },
725
+ "node_modules/ipaddr.js": {
726
+ "version": "1.9.1",
727
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
728
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
729
+ "license": "MIT",
730
+ "engines": {
731
+ "node": ">= 0.10"
732
+ }
733
+ },
734
+ "node_modules/is-binary-path": {
735
+ "version": "2.1.0",
736
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
737
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
738
+ "dev": true,
739
+ "license": "MIT",
740
+ "dependencies": {
741
+ "binary-extensions": "^2.0.0"
742
+ },
743
+ "engines": {
744
+ "node": ">=8"
745
+ }
746
+ },
747
+ "node_modules/is-extglob": {
748
+ "version": "2.1.1",
749
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
750
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
751
+ "dev": true,
752
+ "license": "MIT",
753
+ "engines": {
754
+ "node": ">=0.10.0"
755
+ }
756
+ },
757
+ "node_modules/is-glob": {
758
+ "version": "4.0.3",
759
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
760
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
761
+ "dev": true,
762
+ "license": "MIT",
763
+ "dependencies": {
764
+ "is-extglob": "^2.1.1"
765
+ },
766
+ "engines": {
767
+ "node": ">=0.10.0"
768
+ }
769
+ },
770
+ "node_modules/is-number": {
771
+ "version": "7.0.0",
772
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
773
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
774
+ "dev": true,
775
+ "license": "MIT",
776
+ "engines": {
777
+ "node": ">=0.12.0"
778
+ }
779
+ },
780
+ "node_modules/math-intrinsics": {
781
+ "version": "1.1.0",
782
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
783
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
784
+ "license": "MIT",
785
+ "engines": {
786
+ "node": ">= 0.4"
787
+ }
788
+ },
789
+ "node_modules/media-typer": {
790
+ "version": "0.3.0",
791
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
792
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
793
+ "license": "MIT",
794
+ "engines": {
795
+ "node": ">= 0.6"
796
+ }
797
+ },
798
+ "node_modules/merge-descriptors": {
799
+ "version": "1.0.3",
800
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
801
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
802
+ "license": "MIT",
803
+ "funding": {
804
+ "url": "https://github.com/sponsors/sindresorhus"
805
+ }
806
+ },
807
+ "node_modules/methods": {
808
+ "version": "1.1.2",
809
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
810
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
811
+ "license": "MIT",
812
+ "engines": {
813
+ "node": ">= 0.6"
814
+ }
815
+ },
816
+ "node_modules/mime": {
817
+ "version": "1.6.0",
818
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
819
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
820
+ "license": "MIT",
821
+ "bin": {
822
+ "mime": "cli.js"
823
+ },
824
+ "engines": {
825
+ "node": ">=4"
826
+ }
827
+ },
828
+ "node_modules/mime-db": {
829
+ "version": "1.52.0",
830
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
831
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
832
+ "license": "MIT",
833
+ "engines": {
834
+ "node": ">= 0.6"
835
+ }
836
+ },
837
+ "node_modules/mime-types": {
838
+ "version": "2.1.35",
839
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
840
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
841
+ "license": "MIT",
842
+ "dependencies": {
843
+ "mime-db": "1.52.0"
844
+ },
845
+ "engines": {
846
+ "node": ">= 0.6"
847
+ }
848
+ },
849
+ "node_modules/minimatch": {
850
+ "version": "3.1.2",
851
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
852
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
853
+ "dev": true,
854
+ "license": "ISC",
855
+ "dependencies": {
856
+ "brace-expansion": "^1.1.7"
857
+ },
858
+ "engines": {
859
+ "node": "*"
860
+ }
861
+ },
862
+ "node_modules/ms": {
863
+ "version": "2.0.0",
864
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
865
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
866
+ "license": "MIT"
867
+ },
868
+ "node_modules/negotiator": {
869
+ "version": "0.6.3",
870
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
871
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
872
+ "license": "MIT",
873
+ "engines": {
874
+ "node": ">= 0.6"
875
+ }
876
+ },
877
+ "node_modules/node-cache": {
878
+ "version": "5.1.2",
879
+ "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
880
+ "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
881
+ "license": "MIT",
882
+ "dependencies": {
883
+ "clone": "2.x"
884
+ },
885
+ "engines": {
886
+ "node": ">= 8.0.0"
887
+ }
888
+ },
889
+ "node_modules/nodemon": {
890
+ "version": "3.1.10",
891
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
892
+ "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
893
+ "dev": true,
894
+ "license": "MIT",
895
+ "dependencies": {
896
+ "chokidar": "^3.5.2",
897
+ "debug": "^4",
898
+ "ignore-by-default": "^1.0.1",
899
+ "minimatch": "^3.1.2",
900
+ "pstree.remy": "^1.1.8",
901
+ "semver": "^7.5.3",
902
+ "simple-update-notifier": "^2.0.0",
903
+ "supports-color": "^5.5.0",
904
+ "touch": "^3.1.0",
905
+ "undefsafe": "^2.0.5"
906
+ },
907
+ "bin": {
908
+ "nodemon": "bin/nodemon.js"
909
+ },
910
+ "engines": {
911
+ "node": ">=10"
912
+ },
913
+ "funding": {
914
+ "type": "opencollective",
915
+ "url": "https://opencollective.com/nodemon"
916
+ }
917
+ },
918
+ "node_modules/nodemon/node_modules/debug": {
919
+ "version": "4.4.1",
920
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
921
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
922
+ "dev": true,
923
+ "license": "MIT",
924
+ "dependencies": {
925
+ "ms": "^2.1.3"
926
+ },
927
+ "engines": {
928
+ "node": ">=6.0"
929
+ },
930
+ "peerDependenciesMeta": {
931
+ "supports-color": {
932
+ "optional": true
933
+ }
934
+ }
935
+ },
936
+ "node_modules/nodemon/node_modules/ms": {
937
+ "version": "2.1.3",
938
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
939
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
940
+ "dev": true,
941
+ "license": "MIT"
942
+ },
943
+ "node_modules/normalize-path": {
944
+ "version": "3.0.0",
945
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
946
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
947
+ "dev": true,
948
+ "license": "MIT",
949
+ "engines": {
950
+ "node": ">=0.10.0"
951
+ }
952
+ },
953
+ "node_modules/object-assign": {
954
+ "version": "4.1.1",
955
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
956
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
957
+ "license": "MIT",
958
+ "engines": {
959
+ "node": ">=0.10.0"
960
+ }
961
+ },
962
+ "node_modules/object-inspect": {
963
+ "version": "1.13.4",
964
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
965
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
966
+ "license": "MIT",
967
+ "engines": {
968
+ "node": ">= 0.4"
969
+ },
970
+ "funding": {
971
+ "url": "https://github.com/sponsors/ljharb"
972
+ }
973
+ },
974
+ "node_modules/on-finished": {
975
+ "version": "2.4.1",
976
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
977
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
978
+ "license": "MIT",
979
+ "dependencies": {
980
+ "ee-first": "1.1.1"
981
+ },
982
+ "engines": {
983
+ "node": ">= 0.8"
984
+ }
985
+ },
986
+ "node_modules/parseurl": {
987
+ "version": "1.3.3",
988
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
989
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
990
+ "license": "MIT",
991
+ "engines": {
992
+ "node": ">= 0.8"
993
+ }
994
+ },
995
+ "node_modules/path-to-regexp": {
996
+ "version": "0.1.12",
997
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
998
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
999
+ "license": "MIT"
1000
+ },
1001
+ "node_modules/picomatch": {
1002
+ "version": "2.3.1",
1003
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1004
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1005
+ "dev": true,
1006
+ "license": "MIT",
1007
+ "engines": {
1008
+ "node": ">=8.6"
1009
+ },
1010
+ "funding": {
1011
+ "url": "https://github.com/sponsors/jonschlinkert"
1012
+ }
1013
+ },
1014
+ "node_modules/proxy-addr": {
1015
+ "version": "2.0.7",
1016
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1017
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1018
+ "license": "MIT",
1019
+ "dependencies": {
1020
+ "forwarded": "0.2.0",
1021
+ "ipaddr.js": "1.9.1"
1022
+ },
1023
+ "engines": {
1024
+ "node": ">= 0.10"
1025
+ }
1026
+ },
1027
+ "node_modules/proxy-from-env": {
1028
+ "version": "1.1.0",
1029
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
1030
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
1031
+ "license": "MIT"
1032
+ },
1033
+ "node_modules/pstree.remy": {
1034
+ "version": "1.1.8",
1035
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
1036
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
1037
+ "dev": true,
1038
+ "license": "MIT"
1039
+ },
1040
+ "node_modules/qs": {
1041
+ "version": "6.13.0",
1042
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
1043
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
1044
+ "license": "BSD-3-Clause",
1045
+ "dependencies": {
1046
+ "side-channel": "^1.0.6"
1047
+ },
1048
+ "engines": {
1049
+ "node": ">=0.6"
1050
+ },
1051
+ "funding": {
1052
+ "url": "https://github.com/sponsors/ljharb"
1053
+ }
1054
+ },
1055
+ "node_modules/range-parser": {
1056
+ "version": "1.2.1",
1057
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1058
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1059
+ "license": "MIT",
1060
+ "engines": {
1061
+ "node": ">= 0.6"
1062
+ }
1063
+ },
1064
+ "node_modules/raw-body": {
1065
+ "version": "2.5.2",
1066
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
1067
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
1068
+ "license": "MIT",
1069
+ "dependencies": {
1070
+ "bytes": "3.1.2",
1071
+ "http-errors": "2.0.0",
1072
+ "iconv-lite": "0.4.24",
1073
+ "unpipe": "1.0.0"
1074
+ },
1075
+ "engines": {
1076
+ "node": ">= 0.8"
1077
+ }
1078
+ },
1079
+ "node_modules/readdirp": {
1080
+ "version": "3.6.0",
1081
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1082
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1083
+ "dev": true,
1084
+ "license": "MIT",
1085
+ "dependencies": {
1086
+ "picomatch": "^2.2.1"
1087
+ },
1088
+ "engines": {
1089
+ "node": ">=8.10.0"
1090
+ }
1091
+ },
1092
+ "node_modules/safe-buffer": {
1093
+ "version": "5.2.1",
1094
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1095
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1096
+ "funding": [
1097
+ {
1098
+ "type": "github",
1099
+ "url": "https://github.com/sponsors/feross"
1100
+ },
1101
+ {
1102
+ "type": "patreon",
1103
+ "url": "https://www.patreon.com/feross"
1104
+ },
1105
+ {
1106
+ "type": "consulting",
1107
+ "url": "https://feross.org/support"
1108
+ }
1109
+ ],
1110
+ "license": "MIT"
1111
+ },
1112
+ "node_modules/safer-buffer": {
1113
+ "version": "2.1.2",
1114
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1115
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1116
+ "license": "MIT"
1117
+ },
1118
+ "node_modules/sax": {
1119
+ "version": "1.4.1",
1120
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
1121
+ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
1122
+ "license": "ISC"
1123
+ },
1124
+ "node_modules/semver": {
1125
+ "version": "7.7.2",
1126
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
1127
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
1128
+ "dev": true,
1129
+ "license": "ISC",
1130
+ "bin": {
1131
+ "semver": "bin/semver.js"
1132
+ },
1133
+ "engines": {
1134
+ "node": ">=10"
1135
+ }
1136
+ },
1137
+ "node_modules/send": {
1138
+ "version": "0.19.0",
1139
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
1140
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
1141
+ "license": "MIT",
1142
+ "dependencies": {
1143
+ "debug": "2.6.9",
1144
+ "depd": "2.0.0",
1145
+ "destroy": "1.2.0",
1146
+ "encodeurl": "~1.0.2",
1147
+ "escape-html": "~1.0.3",
1148
+ "etag": "~1.8.1",
1149
+ "fresh": "0.5.2",
1150
+ "http-errors": "2.0.0",
1151
+ "mime": "1.6.0",
1152
+ "ms": "2.1.3",
1153
+ "on-finished": "2.4.1",
1154
+ "range-parser": "~1.2.1",
1155
+ "statuses": "2.0.1"
1156
+ },
1157
+ "engines": {
1158
+ "node": ">= 0.8.0"
1159
+ }
1160
+ },
1161
+ "node_modules/send/node_modules/encodeurl": {
1162
+ "version": "1.0.2",
1163
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
1164
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
1165
+ "license": "MIT",
1166
+ "engines": {
1167
+ "node": ">= 0.8"
1168
+ }
1169
+ },
1170
+ "node_modules/send/node_modules/ms": {
1171
+ "version": "2.1.3",
1172
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1173
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1174
+ "license": "MIT"
1175
+ },
1176
+ "node_modules/serve-static": {
1177
+ "version": "1.16.2",
1178
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
1179
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
1180
+ "license": "MIT",
1181
+ "dependencies": {
1182
+ "encodeurl": "~2.0.0",
1183
+ "escape-html": "~1.0.3",
1184
+ "parseurl": "~1.3.3",
1185
+ "send": "0.19.0"
1186
+ },
1187
+ "engines": {
1188
+ "node": ">= 0.8.0"
1189
+ }
1190
+ },
1191
+ "node_modules/setprototypeof": {
1192
+ "version": "1.2.0",
1193
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1194
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1195
+ "license": "ISC"
1196
+ },
1197
+ "node_modules/side-channel": {
1198
+ "version": "1.1.0",
1199
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
1200
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
1201
+ "license": "MIT",
1202
+ "dependencies": {
1203
+ "es-errors": "^1.3.0",
1204
+ "object-inspect": "^1.13.3",
1205
+ "side-channel-list": "^1.0.0",
1206
+ "side-channel-map": "^1.0.1",
1207
+ "side-channel-weakmap": "^1.0.2"
1208
+ },
1209
+ "engines": {
1210
+ "node": ">= 0.4"
1211
+ },
1212
+ "funding": {
1213
+ "url": "https://github.com/sponsors/ljharb"
1214
+ }
1215
+ },
1216
+ "node_modules/side-channel-list": {
1217
+ "version": "1.0.0",
1218
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
1219
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
1220
+ "license": "MIT",
1221
+ "dependencies": {
1222
+ "es-errors": "^1.3.0",
1223
+ "object-inspect": "^1.13.3"
1224
+ },
1225
+ "engines": {
1226
+ "node": ">= 0.4"
1227
+ },
1228
+ "funding": {
1229
+ "url": "https://github.com/sponsors/ljharb"
1230
+ }
1231
+ },
1232
+ "node_modules/side-channel-map": {
1233
+ "version": "1.0.1",
1234
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1235
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1236
+ "license": "MIT",
1237
+ "dependencies": {
1238
+ "call-bound": "^1.0.2",
1239
+ "es-errors": "^1.3.0",
1240
+ "get-intrinsic": "^1.2.5",
1241
+ "object-inspect": "^1.13.3"
1242
+ },
1243
+ "engines": {
1244
+ "node": ">= 0.4"
1245
+ },
1246
+ "funding": {
1247
+ "url": "https://github.com/sponsors/ljharb"
1248
+ }
1249
+ },
1250
+ "node_modules/side-channel-weakmap": {
1251
+ "version": "1.0.2",
1252
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1253
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1254
+ "license": "MIT",
1255
+ "dependencies": {
1256
+ "call-bound": "^1.0.2",
1257
+ "es-errors": "^1.3.0",
1258
+ "get-intrinsic": "^1.2.5",
1259
+ "object-inspect": "^1.13.3",
1260
+ "side-channel-map": "^1.0.1"
1261
+ },
1262
+ "engines": {
1263
+ "node": ">= 0.4"
1264
+ },
1265
+ "funding": {
1266
+ "url": "https://github.com/sponsors/ljharb"
1267
+ }
1268
+ },
1269
+ "node_modules/simple-update-notifier": {
1270
+ "version": "2.0.0",
1271
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
1272
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
1273
+ "dev": true,
1274
+ "license": "MIT",
1275
+ "dependencies": {
1276
+ "semver": "^7.5.3"
1277
+ },
1278
+ "engines": {
1279
+ "node": ">=10"
1280
+ }
1281
+ },
1282
+ "node_modules/statuses": {
1283
+ "version": "2.0.1",
1284
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1285
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1286
+ "license": "MIT",
1287
+ "engines": {
1288
+ "node": ">= 0.8"
1289
+ }
1290
+ },
1291
+ "node_modules/supports-color": {
1292
+ "version": "5.5.0",
1293
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1294
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1295
+ "dev": true,
1296
+ "license": "MIT",
1297
+ "dependencies": {
1298
+ "has-flag": "^3.0.0"
1299
+ },
1300
+ "engines": {
1301
+ "node": ">=4"
1302
+ }
1303
+ },
1304
+ "node_modules/to-regex-range": {
1305
+ "version": "5.0.1",
1306
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1307
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1308
+ "dev": true,
1309
+ "license": "MIT",
1310
+ "dependencies": {
1311
+ "is-number": "^7.0.0"
1312
+ },
1313
+ "engines": {
1314
+ "node": ">=8.0"
1315
+ }
1316
+ },
1317
+ "node_modules/toidentifier": {
1318
+ "version": "1.0.1",
1319
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1320
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1321
+ "license": "MIT",
1322
+ "engines": {
1323
+ "node": ">=0.6"
1324
+ }
1325
+ },
1326
+ "node_modules/touch": {
1327
+ "version": "3.1.1",
1328
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
1329
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
1330
+ "dev": true,
1331
+ "license": "ISC",
1332
+ "bin": {
1333
+ "nodetouch": "bin/nodetouch.js"
1334
+ }
1335
+ },
1336
+ "node_modules/type-is": {
1337
+ "version": "1.6.18",
1338
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1339
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1340
+ "license": "MIT",
1341
+ "dependencies": {
1342
+ "media-typer": "0.3.0",
1343
+ "mime-types": "~2.1.24"
1344
+ },
1345
+ "engines": {
1346
+ "node": ">= 0.6"
1347
+ }
1348
+ },
1349
+ "node_modules/undefsafe": {
1350
+ "version": "2.0.5",
1351
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
1352
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
1353
+ "dev": true,
1354
+ "license": "MIT"
1355
+ },
1356
+ "node_modules/unpipe": {
1357
+ "version": "1.0.0",
1358
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1359
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1360
+ "license": "MIT",
1361
+ "engines": {
1362
+ "node": ">= 0.8"
1363
+ }
1364
+ },
1365
+ "node_modules/utils-merge": {
1366
+ "version": "1.0.1",
1367
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1368
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1369
+ "license": "MIT",
1370
+ "engines": {
1371
+ "node": ">= 0.4.0"
1372
+ }
1373
+ },
1374
+ "node_modules/vary": {
1375
+ "version": "1.1.2",
1376
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1377
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1378
+ "license": "MIT",
1379
+ "engines": {
1380
+ "node": ">= 0.8"
1381
+ }
1382
+ },
1383
+ "node_modules/xml2js": {
1384
+ "version": "0.6.2",
1385
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
1386
+ "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
1387
+ "license": "MIT",
1388
+ "dependencies": {
1389
+ "sax": ">=0.6.0",
1390
+ "xmlbuilder": "~11.0.0"
1391
+ },
1392
+ "engines": {
1393
+ "node": ">=4.0.0"
1394
+ }
1395
+ },
1396
+ "node_modules/xmlbuilder": {
1397
+ "version": "11.0.1",
1398
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
1399
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
1400
+ "license": "MIT",
1401
+ "engines": {
1402
+ "node": ">=4.0"
1403
+ }
1404
+ }
1405
+ }
1406
+ }
backend/package.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "pubmed-ai-explorer-backend",
3
+ "version": "1.0.0",
4
+ "description": "Backend API for PubMed AI Explorer",
5
+ "main": "server.js",
6
+ "scripts": {
7
+ "dev": "nodemon server.js",
8
+ "start": "node server.js"
9
+ },
10
+ "dependencies": {
11
+ "express": "^4.18.2",
12
+ "cors": "^2.8.5",
13
+ "axios": "^1.6.2",
14
+ "node-cache": "^5.1.2",
15
+ "xml2js": "^0.6.2",
16
+ "dotenv": "^16.3.1"
17
+ },
18
+ "devDependencies": {
19
+ "nodemon": "^3.0.2"
20
+ }
21
+ }
backend/routes/algorithms.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const router = express.Router();
5
+
6
+ const ALGORITHMS_PATH = path.join(__dirname, '../../data/algorithms.json');
7
+
8
+ router.get('/', (req, res) => {
9
+ try {
10
+ const data = fs.readFileSync(ALGORITHMS_PATH, 'utf8');
11
+ const algorithms = JSON.parse(data);
12
+ res.json(algorithms);
13
+ } catch (error) {
14
+ res.status(500).json({ error: 'Failed to load algorithms' });
15
+ }
16
+ });
17
+
18
+ router.put('/', (req, res) => {
19
+ try {
20
+ const updatedAlgorithms = req.body;
21
+ fs.writeFileSync(ALGORITHMS_PATH, JSON.stringify(updatedAlgorithms, null, 2));
22
+ res.json({ message: 'Algorithms updated successfully' });
23
+ } catch (error) {
24
+ res.status(500).json({ error: 'Failed to update algorithms' });
25
+ }
26
+ });
27
+
28
+ router.get('/categories', (req, res) => {
29
+ try {
30
+ const data = fs.readFileSync(ALGORITHMS_PATH, 'utf8');
31
+ const algorithms = JSON.parse(data);
32
+
33
+ const categories = {};
34
+ Object.values(algorithms.algorithms).forEach(algo => {
35
+ if (!categories[algo.category]) {
36
+ categories[algo.category] = [];
37
+ }
38
+ categories[algo.category].push(algo.name);
39
+ });
40
+
41
+ res.json(categories);
42
+ } catch (error) {
43
+ res.status(500).json({ error: 'Failed to get categories' });
44
+ }
45
+ });
46
+
47
+ module.exports = router;
backend/routes/pubmed.js ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const axios = require('axios');
3
+ const xml2js = require('xml2js');
4
+ const NodeCache = require('node-cache');
5
+ const router = express.Router();
6
+
7
+ const cache = new NodeCache({ stdTTL: 3600 });
8
+
9
+ const PUBMED_BASE_URL = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils';
10
+
11
+ async function searchPubMed(query, maxResults = 100) {
12
+ try {
13
+ const cacheKey = `search_${query}_${maxResults}`;
14
+ const cached = cache.get(cacheKey);
15
+ if (cached) return cached;
16
+
17
+ // Add filters to exclude review papers, meta-analyses, and systematic reviews
18
+ const filteredQuery = `(${query}) NOT Review[Publication Type] NOT Meta-Analysis[Publication Type] NOT Systematic Review[Publication Type]`;
19
+
20
+ const searchUrl = `${PUBMED_BASE_URL}/esearch.fcgi?db=pubmed&term=${encodeURIComponent(filteredQuery)}&retmax=${maxResults}&retmode=json`;
21
+ const response = await axios.get(searchUrl);
22
+
23
+ const result = {
24
+ count: parseInt(response.data.esearchresult.count),
25
+ ids: response.data.esearchresult.idlist || []
26
+ };
27
+
28
+ cache.set(cacheKey, result);
29
+ return result;
30
+ } catch (error) {
31
+ throw new Error(`PubMed search failed: ${error.message}`);
32
+ }
33
+ }
34
+
35
+ async function fetchPaperDetails(ids) {
36
+ try {
37
+ if (!ids.length) return [];
38
+
39
+ const cacheKey = `details_${ids.join(',')}`;
40
+ const cached = cache.get(cacheKey);
41
+ if (cached) return cached;
42
+
43
+ const fetchUrl = `${PUBMED_BASE_URL}/efetch.fcgi?db=pubmed&id=${ids.join(',')}&retmode=xml`;
44
+
45
+ // Add timeout and retry logic for PubMed API
46
+ const response = await axios.get(fetchUrl, {
47
+ timeout: 10000,
48
+ headers: {
49
+ 'User-Agent': 'PubMedAIExplorer/1.0'
50
+ }
51
+ });
52
+
53
+ const parser = new xml2js.Parser();
54
+ const result = await parser.parseStringPromise(response.data);
55
+
56
+ const papers = [];
57
+ const articles = result.PubmedArticleSet?.PubmedArticle || [];
58
+
59
+ articles.forEach(article => {
60
+ const medlineCitation = article.MedlineCitation?.[0];
61
+ if (medlineCitation) {
62
+ const articleData = medlineCitation.Article?.[0];
63
+ if (articleData) {
64
+ papers.push({
65
+ pmid: medlineCitation.PMID?.[0]?._,
66
+ title: articleData.ArticleTitle?.[0] || 'No title',
67
+ abstract: articleData.Abstract?.AbstractText?.[0]?._ || articleData.Abstract?.AbstractText?.[0] || '',
68
+ authors: articleData.AuthorList?.Author?.map(author => {
69
+ const lastName = author.LastName?.[0] || '';
70
+ const foreName = author.ForeName?.[0] || '';
71
+ return `${foreName} ${lastName}`.trim();
72
+ }) || [],
73
+ journal: articleData.Journal?.Title?.[0] || '',
74
+ pubDate: medlineCitation.DateCompleted?.[0]?.Year?.[0] ||
75
+ medlineCitation.DateRevised?.[0]?.Year?.[0] || ''
76
+ });
77
+ }
78
+ }
79
+ });
80
+
81
+ cache.set(cacheKey, papers);
82
+ return papers;
83
+ } catch (error) {
84
+ throw new Error(`Failed to fetch paper details: ${error.message}`);
85
+ }
86
+ }
87
+
88
+ router.get('/search', async (req, res) => {
89
+ try {
90
+ const { query, maxResults = 20 } = req.query;
91
+ if (!query) {
92
+ return res.status(400).json({ error: 'Query parameter is required' });
93
+ }
94
+
95
+ const searchResult = await searchPubMed(query, maxResults);
96
+ const papers = await fetchPaperDetails(searchResult.ids.slice(0, 10));
97
+
98
+ res.json({
99
+ totalCount: searchResult.count,
100
+ papers: papers
101
+ });
102
+ } catch (error) {
103
+ res.status(500).json({ error: error.message });
104
+ }
105
+ });
106
+
107
+ router.get('/paper/:pmid', async (req, res) => {
108
+ try {
109
+ const { pmid } = req.params;
110
+ console.log(`Fetching paper details for PMID: ${pmid}`);
111
+
112
+ const papers = await fetchPaperDetails([pmid]);
113
+ console.log(`Found ${papers.length} papers for PMID: ${pmid}`);
114
+
115
+ if (papers.length === 0) {
116
+ console.log(`No paper found for PMID: ${pmid}`);
117
+ return res.status(404).json({ error: 'Paper not found' });
118
+ }
119
+
120
+ console.log(`Returning paper:`, papers[0].title);
121
+ res.json(papers[0]);
122
+ } catch (error) {
123
+ const { pmid } = req.params;
124
+ console.error(`Error fetching paper ${pmid}:`, error.message);
125
+ res.status(500).json({ error: error.message });
126
+ }
127
+ });
128
+
129
+ module.exports = router;
backend/routes/search.js ADDED
@@ -0,0 +1,527 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const axios = require('axios');
5
+ const NodeCache = require('node-cache');
6
+ const router = express.Router();
7
+
8
+ const cache = new NodeCache({ stdTTL: 3600 });
9
+
10
+ const ALGORITHMS_PATH = path.join(__dirname, '../../data/algorithms.json');
11
+ const TIMELINE_CACHE_PATH = path.join(__dirname, '../../data/timeline-cache.json');
12
+ const PUBMED_BASE_URL = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils';
13
+
14
+ function loadAlgorithms() {
15
+ const data = fs.readFileSync(ALGORITHMS_PATH, 'utf8');
16
+ return JSON.parse(data);
17
+ }
18
+
19
+ function loadTimelineCache() {
20
+ try {
21
+ if (fs.existsSync(TIMELINE_CACHE_PATH)) {
22
+ const data = fs.readFileSync(TIMELINE_CACHE_PATH, 'utf8');
23
+ return JSON.parse(data);
24
+ }
25
+ } catch (error) {
26
+ console.warn('Error loading timeline cache:', error.message);
27
+ }
28
+ return {};
29
+ }
30
+
31
+ function saveTimelineCache(cache) {
32
+ try {
33
+ fs.writeFileSync(TIMELINE_CACHE_PATH, JSON.stringify(cache, null, 2));
34
+ } catch (error) {
35
+ console.error('Error saving timeline cache:', error.message);
36
+ }
37
+ }
38
+
39
+ function getCacheKey(algorithmKey, year) {
40
+ return `${algorithmKey}-${year}`;
41
+ }
42
+
43
+ function isCurrentYear(year) {
44
+ return year === new Date().getFullYear();
45
+ }
46
+
47
+ async function searchAlgorithmUsage(problem, algorithmKey, algorithmData) {
48
+ try {
49
+ const synonymQueries = algorithmData.synonyms.map(synonym =>
50
+ `("${problem}" AND "${synonym}")`
51
+ ).join(' OR ');
52
+
53
+ // Add filters to exclude review papers, meta-analyses, and systematic reviews
54
+ const filteredQuery = `(${synonymQueries}) NOT Review[Publication Type] NOT Meta-Analysis[Publication Type] NOT Systematic Review[Publication Type]`;
55
+
56
+ const searchUrl = `${PUBMED_BASE_URL}/esearch.fcgi?db=pubmed&term=${encodeURIComponent(filteredQuery)}&retmode=json`;
57
+
58
+ // Debug logging for CNN specifically
59
+ if (algorithmKey === 'cnn') {
60
+ console.log(`CNN Search for "${problem}":`);
61
+ console.log(`Query: ${filteredQuery}`);
62
+ console.log(`URL: ${searchUrl}`);
63
+ }
64
+
65
+ const response = await axios.get(searchUrl);
66
+ const count = parseInt(response.data.esearchresult.count) || 0;
67
+
68
+ // Debug logging for CNN results
69
+ if (algorithmKey === 'cnn') {
70
+ console.log(`CNN Results: ${count} papers found`);
71
+ console.log(`Sample IDs:`, response.data.esearchresult.idlist?.slice(0, 3));
72
+ }
73
+
74
+ return {
75
+ algorithm: algorithmKey,
76
+ name: algorithmData.name,
77
+ category: algorithmData.category,
78
+ description: algorithmData.description,
79
+ count: count,
80
+ sampleIds: response.data.esearchresult.idlist?.slice(0, 3) || []
81
+ };
82
+ } catch (error) {
83
+ if (algorithmKey === 'cnn') {
84
+ console.error(`CNN Search Error:`, error.message);
85
+ }
86
+ return {
87
+ algorithm: algorithmKey,
88
+ name: algorithmData.name,
89
+ category: algorithmData.category,
90
+ description: algorithmData.description,
91
+ count: 0,
92
+ sampleIds: []
93
+ };
94
+ }
95
+ }
96
+
97
+ router.post('/problem', async (req, res) => {
98
+ try {
99
+ const { problem } = req.body;
100
+ if (!problem) {
101
+ return res.status(400).json({ error: 'Problem parameter is required' });
102
+ }
103
+
104
+ const algorithms = loadAlgorithms();
105
+ const results = [];
106
+
107
+ for (const [key, algo] of Object.entries(algorithms.algorithms)) {
108
+ const result = await searchAlgorithmUsage(problem, key, algo);
109
+ results.push(result);
110
+ }
111
+
112
+ results.sort((a, b) => b.count - a.count);
113
+
114
+ res.json({
115
+ problem,
116
+ totalAlgorithms: results.length,
117
+ results: results.filter(r => r.count > 0),
118
+ allResults: results
119
+ });
120
+ } catch (error) {
121
+ res.status(500).json({ error: error.message });
122
+ }
123
+ });
124
+
125
+ async function delay(ms) {
126
+ return new Promise(resolve => setTimeout(resolve, ms));
127
+ }
128
+
129
+ async function fetchAlgorithmCount(key, algo) {
130
+ const cacheKey = `dashboard-${key}`;
131
+ const cached = cache.get(cacheKey);
132
+ if (cached !== undefined) {
133
+ console.log(`${algo.name}: ${cached} results (cached)`);
134
+ return cached;
135
+ }
136
+
137
+ const generalQuery = algo.synonyms.map(s => `"${s}"`).join(' OR ');
138
+
139
+ // Add filters to exclude review papers, meta-analyses, and systematic reviews
140
+ const filteredQuery = `(${generalQuery}) NOT Review[Publication Type] NOT Meta-Analysis[Publication Type] NOT Systematic Review[Publication Type]`;
141
+
142
+ try {
143
+ const searchUrl = `${PUBMED_BASE_URL}/esearch.fcgi?db=pubmed&term=${encodeURIComponent(filteredQuery)}&retmode=json`;
144
+ const response = await axios.get(searchUrl, { timeout: 15000 });
145
+ const count = parseInt(response.data.esearchresult.count) || 0;
146
+
147
+ console.log(`${algo.name}: ${count} results for query: ${filteredQuery}`);
148
+ cache.set(cacheKey, count);
149
+ return count;
150
+ } catch (error) {
151
+ console.error(`Error fetching data for ${algo.name}:`, error.message);
152
+ return 0;
153
+ }
154
+ }
155
+
156
+ router.get('/dashboard-stats', async (req, res) => {
157
+ try {
158
+ const algorithms = loadAlgorithms();
159
+ const stats = {
160
+ classical_ml: [],
161
+ deep_learning: [],
162
+ llms: []
163
+ };
164
+
165
+ // Process algorithms sequentially to avoid rate limiting
166
+ for (const [key, algo] of Object.entries(algorithms.algorithms)) {
167
+ const count = await fetchAlgorithmCount(key, algo);
168
+
169
+ stats[algo.category].push({
170
+ algorithm: key,
171
+ name: algo.name,
172
+ count: count
173
+ });
174
+
175
+ // Add delay between requests to respect rate limits
176
+ await delay(200);
177
+ }
178
+
179
+ stats.classical_ml.sort((a, b) => b.count - a.count);
180
+ stats.deep_learning.sort((a, b) => b.count - a.count);
181
+ stats.llms.sort((a, b) => b.count - a.count);
182
+
183
+ res.json(stats);
184
+ } catch (error) {
185
+ res.status(500).json({ error: error.message });
186
+ }
187
+ });
188
+
189
+ router.get('/pubmed-link', (req, res) => {
190
+ const { problem, algorithm } = req.query;
191
+ if (!problem || !algorithm) {
192
+ return res.status(400).json({ error: 'Both problem and algorithm parameters are required' });
193
+ }
194
+
195
+ const algorithms = loadAlgorithms();
196
+ const algoData = algorithms.algorithms[algorithm];
197
+
198
+ if (!algoData) {
199
+ return res.status(404).json({ error: 'Algorithm not found' });
200
+ }
201
+
202
+ const synonymQueries = algoData.synonyms.map(synonym =>
203
+ `("${problem}" AND "${synonym}")`
204
+ ).join(' OR ');
205
+
206
+ // Add filters to exclude review papers for PubMed links too
207
+ const filteredQuery = `(${synonymQueries}) NOT Review[Publication Type] NOT Meta-Analysis[Publication Type] NOT Systematic Review[Publication Type]`;
208
+
209
+ const pubmedUrl = `https://pubmed.ncbi.nlm.nih.gov/?term=${encodeURIComponent(filteredQuery)}`;
210
+
211
+ res.json({ url: pubmedUrl });
212
+ });
213
+
214
+ async function fetchAlgorithmCountByYear(key, algo, year, diskCache, retryCount = 0) {
215
+ const diskCacheKey = getCacheKey(key, year);
216
+
217
+ // Check disk cache first for past years (which never change)
218
+ if (!isCurrentYear(year) && diskCache[diskCacheKey] !== undefined) {
219
+ console.log(`Using disk cache for ${algo.name} (${year}): ${diskCache[diskCacheKey]}`);
220
+ return diskCache[diskCacheKey];
221
+ }
222
+
223
+ // Check memory cache for current year
224
+ const memoryCacheKey = `timeline-${key}-${year}`;
225
+ const memCached = cache.get(memoryCacheKey);
226
+ if (isCurrentYear(year) && memCached !== undefined) {
227
+ return memCached;
228
+ }
229
+
230
+ const generalQuery = algo.synonyms.map(s => `"${s}"`).join(' OR ');
231
+ const yearFilter = `"${year}"[Date - Publication]`;
232
+ const filteredQuery = `(${generalQuery}) AND ${yearFilter} NOT Review[Publication Type] NOT Meta-Analysis[Publication Type] NOT Systematic Review[Publication Type]`;
233
+
234
+ try {
235
+ const searchUrl = `${PUBMED_BASE_URL}/esearch.fcgi?db=pubmed&term=${encodeURIComponent(filteredQuery)}&retmode=json`;
236
+ const response = await axios.get(searchUrl, { timeout: 15000 });
237
+ const count = parseInt(response.data.esearchresult.count) || 0;
238
+
239
+ // Save to appropriate cache
240
+ if (isCurrentYear(year)) {
241
+ // Current year: save to memory cache (expires)
242
+ cache.set(memoryCacheKey, count);
243
+ } else {
244
+ // Past years: save to disk cache (permanent)
245
+ diskCache[diskCacheKey] = count;
246
+ }
247
+
248
+ console.log(`Fetched ${algo.name} (${year}): ${count} papers`);
249
+ return count;
250
+ } catch (error) {
251
+ if (error.response?.status === 429 && retryCount < 3) {
252
+ const backoffTime = Math.pow(2, retryCount) * 1000; // 1s, 2s, 4s
253
+ console.log(`Rate limited for ${algo.name} (${year}), retrying in ${backoffTime}ms (attempt ${retryCount + 1})`);
254
+ await delay(backoffTime);
255
+ return fetchAlgorithmCountByYear(key, algo, year, diskCache, retryCount + 1);
256
+ }
257
+
258
+ console.error(`Error fetching timeline data for ${algo.name} (${year}):`, error.message);
259
+ return 0;
260
+ }
261
+ }
262
+
263
+ router.get('/timeline-stream', async (req, res) => {
264
+ const { startYear = 2015, endYear = 2024 } = req.query;
265
+ const start = parseInt(startYear);
266
+ const end = parseInt(endYear);
267
+
268
+ if (start > end || start < 2010 || end > 2024) {
269
+ return res.status(400).json({ error: 'Invalid year range' });
270
+ }
271
+
272
+ // Set up Server-Sent Events
273
+ res.writeHead(200, {
274
+ 'Content-Type': 'text/event-stream',
275
+ 'Cache-Control': 'no-cache',
276
+ 'Connection': 'keep-alive',
277
+ 'Access-Control-Allow-Origin': '*',
278
+ 'Access-Control-Allow-Headers': 'Cache-Control'
279
+ });
280
+
281
+ const sendProgress = (data) => {
282
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
283
+ };
284
+
285
+ try {
286
+ const algorithms = loadAlgorithms();
287
+ const diskCache = loadTimelineCache();
288
+ let cacheUpdated = false;
289
+
290
+ const years = [];
291
+ for (let year = start; year <= end; year++) {
292
+ years.push(year);
293
+ }
294
+
295
+ const timelineData = [];
296
+ const algorithmsData = [];
297
+
298
+ // Initialize timeline structure
299
+ for (const year of years) {
300
+ timelineData.push({ year });
301
+ }
302
+
303
+ // Count total operations
304
+ const totalAlgorithms = Object.keys(algorithms.algorithms).length;
305
+ const totalYears = years.length;
306
+ const totalOperations = totalAlgorithms * totalYears;
307
+
308
+ let completedOperations = 0;
309
+ let cachedResults = 0;
310
+ let fetchedResults = 0;
311
+
312
+ // Count cached vs fetched upfront
313
+ for (const [key, algo] of Object.entries(algorithms.algorithms)) {
314
+ for (const year of years) {
315
+ const diskCacheKey = getCacheKey(key, year);
316
+ if (!isCurrentYear(year) && diskCache[diskCacheKey] !== undefined) {
317
+ cachedResults++;
318
+ } else {
319
+ fetchedResults++;
320
+ }
321
+ }
322
+ }
323
+
324
+ sendProgress({
325
+ type: 'init',
326
+ totalOperations,
327
+ cachedResults,
328
+ fetchedResults,
329
+ message: 'Starting timeline data collection...'
330
+ });
331
+
332
+ // Process each algorithm
333
+ for (const [key, algo] of Object.entries(algorithms.algorithms)) {
334
+ const algorithmTimeline = {
335
+ algorithm: key,
336
+ name: algo.name,
337
+ category: algo.category,
338
+ data: []
339
+ };
340
+
341
+ sendProgress({
342
+ type: 'algorithm_start',
343
+ algorithm: algo.name,
344
+ progress: Math.round((completedOperations / totalOperations) * 100),
345
+ completed: completedOperations,
346
+ total: totalOperations
347
+ });
348
+
349
+ // Get data for each year
350
+ for (const year of years) {
351
+ const count = await fetchAlgorithmCountByYear(key, algo, year, diskCache);
352
+ algorithmTimeline.data.push({ year, count });
353
+
354
+ // Add to timeline data structure
355
+ const yearIndex = timelineData.findIndex(item => item.year === year);
356
+ if (yearIndex !== -1) {
357
+ timelineData[yearIndex][key] = count;
358
+ }
359
+
360
+ completedOperations++;
361
+
362
+ sendProgress({
363
+ type: 'year_complete',
364
+ algorithm: algo.name,
365
+ year,
366
+ count,
367
+ progress: Math.round((completedOperations / totalOperations) * 100),
368
+ completed: completedOperations,
369
+ total: totalOperations
370
+ });
371
+
372
+ // Check if we made an API call and need to save cache
373
+ const diskCacheKey = getCacheKey(key, year);
374
+ if (!isCurrentYear(year) && diskCache[diskCacheKey] === count) {
375
+ cacheUpdated = true;
376
+ }
377
+
378
+ // Add delay only if we made an actual API call
379
+ if (isCurrentYear(year) || diskCache[diskCacheKey] === undefined) {
380
+ await delay(500);
381
+ }
382
+ }
383
+
384
+ algorithmsData.push(algorithmTimeline);
385
+
386
+ sendProgress({
387
+ type: 'algorithm_complete',
388
+ algorithm: algo.name,
389
+ progress: Math.round((completedOperations / totalOperations) * 100),
390
+ completed: completedOperations,
391
+ total: totalOperations
392
+ });
393
+ }
394
+
395
+ // Save cache if updated
396
+ if (cacheUpdated) {
397
+ saveTimelineCache(diskCache);
398
+ sendProgress({
399
+ type: 'cache_saved',
400
+ message: 'Timeline cache updated and saved to disk'
401
+ });
402
+ }
403
+
404
+ // Send final results
405
+ sendProgress({
406
+ type: 'complete',
407
+ timelineData,
408
+ algorithms: algorithmsData,
409
+ yearRange: { start, end },
410
+ cacheStats: {
411
+ cached: cachedResults,
412
+ fetched: fetchedResults
413
+ }
414
+ });
415
+
416
+ res.end();
417
+ } catch (error) {
418
+ sendProgress({
419
+ type: 'error',
420
+ error: error.message
421
+ });
422
+ res.end();
423
+ }
424
+ });
425
+
426
+ router.get('/timeline', async (req, res) => {
427
+ try {
428
+ const { startYear = 2015, endYear = 2024 } = req.query;
429
+ const start = parseInt(startYear);
430
+ const end = parseInt(endYear);
431
+
432
+ if (start > end || start < 2010 || end > 2024) {
433
+ return res.status(400).json({ error: 'Invalid year range' });
434
+ }
435
+
436
+ const algorithms = loadAlgorithms();
437
+ const diskCache = loadTimelineCache();
438
+ let cacheUpdated = false;
439
+
440
+ const years = [];
441
+ for (let year = start; year <= end; year++) {
442
+ years.push(year);
443
+ }
444
+
445
+ const timelineData = [];
446
+ const algorithmsData = [];
447
+
448
+ // Initialize timeline structure
449
+ for (const year of years) {
450
+ timelineData.push({ year });
451
+ }
452
+
453
+ // Count how many API calls we'll need to make
454
+ let totalApiCalls = 0;
455
+ let cachedResults = 0;
456
+
457
+ for (const [key, algo] of Object.entries(algorithms.algorithms)) {
458
+ for (const year of years) {
459
+ const diskCacheKey = getCacheKey(key, year);
460
+ if (!isCurrentYear(year) && diskCache[diskCacheKey] !== undefined) {
461
+ cachedResults++;
462
+ } else {
463
+ totalApiCalls++;
464
+ }
465
+ }
466
+ }
467
+
468
+ console.log(`Timeline request: ${cachedResults} cached results, ${totalApiCalls} API calls needed`);
469
+
470
+ // Process each algorithm
471
+ for (const [key, algo] of Object.entries(algorithms.algorithms)) {
472
+ const algorithmTimeline = {
473
+ algorithm: key,
474
+ name: algo.name,
475
+ category: algo.category,
476
+ data: []
477
+ };
478
+
479
+ // Get data for each year
480
+ for (const year of years) {
481
+ const count = await fetchAlgorithmCountByYear(key, algo, year, diskCache);
482
+ algorithmTimeline.data.push({ year, count });
483
+
484
+ // Add to timeline data structure
485
+ const yearIndex = timelineData.findIndex(item => item.year === year);
486
+ if (yearIndex !== -1) {
487
+ timelineData[yearIndex][key] = count;
488
+ }
489
+
490
+ // Check if we made an API call (not cached) and need to save cache
491
+ const diskCacheKey = getCacheKey(key, year);
492
+ if (!isCurrentYear(year) && diskCache[diskCacheKey] === count) {
493
+ cacheUpdated = true;
494
+ }
495
+
496
+ // Add delay only if we made an actual API call
497
+ if (isCurrentYear(year) || diskCache[diskCacheKey] === undefined) {
498
+ await delay(500);
499
+ }
500
+ }
501
+
502
+ algorithmsData.push(algorithmTimeline);
503
+ console.log(`Completed timeline data for ${algo.name}`);
504
+ }
505
+
506
+ // Save updated cache to disk if needed
507
+ if (cacheUpdated) {
508
+ saveTimelineCache(diskCache);
509
+ console.log('Timeline cache updated and saved to disk');
510
+ }
511
+
512
+ res.json({
513
+ timelineData,
514
+ algorithms: algorithmsData,
515
+ yearRange: { start, end },
516
+ cacheStats: {
517
+ cached: cachedResults,
518
+ fetched: totalApiCalls
519
+ }
520
+ });
521
+ } catch (error) {
522
+ console.error('Timeline API error:', error);
523
+ res.status(500).json({ error: error.message });
524
+ }
525
+ });
526
+
527
+ module.exports = router;
backend/server.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const cors = require('cors');
3
+ const path = require('path');
4
+ require('dotenv').config();
5
+
6
+ const app = express();
7
+ const PORT = process.env.PORT || 3001;
8
+
9
+ app.use(cors());
10
+ app.use(express.json());
11
+
12
+ const algorithmsRoute = require('./routes/algorithms');
13
+ const pubmedRoute = require('./routes/pubmed');
14
+ const searchRoute = require('./routes/search');
15
+
16
+ app.use('/api/algorithms', algorithmsRoute);
17
+ app.use('/api/pubmed', pubmedRoute);
18
+ app.use('/api/search', searchRoute);
19
+
20
+ app.get('/api/health', (req, res) => {
21
+ res.json({ status: 'OK', message: 'PubMed AI Explorer API is running' });
22
+ });
23
+
24
+ app.listen(PORT, () => {
25
+ console.log(`Server running on port ${PORT}`);
26
+ });
data/algorithms.json ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algorithms": {
3
+ "svm": {
4
+ "name": "Support Vector Machine",
5
+ "category": "classical_ml",
6
+ "description": "A supervised learning algorithm that finds optimal hyperplanes for classification and regression tasks",
7
+ "synonyms": ["support vector machine", "SVM", "support vector classifier", "support vector regression", "SVR"]
8
+ },
9
+ "decision_tree": {
10
+ "name": "Decision Tree",
11
+ "category": "classical_ml",
12
+ "description": "A tree-like model that makes decisions by splitting data based on feature values",
13
+ "synonyms": ["decision tree", "decision trees", "DT", "CART", "classification tree", "regression tree"]
14
+ },
15
+ "random_forest": {
16
+ "name": "Random Forest",
17
+ "category": "classical_ml",
18
+ "description": "An ensemble method that combines multiple decision trees for improved accuracy",
19
+ "synonyms": ["random forest", "RF", "random forests", "forest classifier"]
20
+ },
21
+ "xgboost": {
22
+ "name": "XGBoost",
23
+ "category": "classical_ml",
24
+ "description": "Extreme Gradient Boosting - an optimized gradient boosting framework",
25
+ "synonyms": ["xgboost", "XGBoost", "extreme gradient boosting", "XGB"]
26
+ },
27
+ "logistic_regression": {
28
+ "name": "Logistic Regression",
29
+ "category": "classical_ml",
30
+ "description": "A linear model for binary and multiclass classification problems",
31
+ "synonyms": ["logistic regression", "logit", "logistic model", "LR"]
32
+ },
33
+ "naive_bayes": {
34
+ "name": "Naive Bayes",
35
+ "category": "classical_ml",
36
+ "description": "A probabilistic classifier based on Bayes' theorem with independence assumptions",
37
+ "synonyms": ["naive bayes", "Naive Bayes", "NB", "Bayes classifier"]
38
+ },
39
+ "knn": {
40
+ "name": "K-Nearest Neighbors",
41
+ "category": "classical_ml",
42
+ "description": "A non-parametric method that classifies data points based on the class of their nearest neighbors",
43
+ "synonyms": ["k-nearest neighbors", "KNN", "k-NN", "nearest neighbor", "k nearest neighbour"]
44
+ },
45
+ "kmeans": {
46
+ "name": "K-Means Clustering",
47
+ "category": "classical_ml",
48
+ "description": "An unsupervised clustering algorithm that partitions data into k clusters",
49
+ "synonyms": ["k-means", "K-means", "kmeans", "k-means clustering", "k means"]
50
+ },
51
+ "gradient_boosting": {
52
+ "name": "Gradient Boosting",
53
+ "category": "classical_ml",
54
+ "description": "An ensemble method that builds models sequentially to correct errors of previous models",
55
+ "synonyms": ["gradient boosting", "GB", "GBM", "gradient boosted trees", "gradient boosting machine"]
56
+ },
57
+ "ada_boost": {
58
+ "name": "AdaBoost",
59
+ "category": "classical_ml",
60
+ "description": "Adaptive Boosting algorithm that combines weak learners into a strong classifier",
61
+ "synonyms": ["AdaBoost", "ada boost", "adaptive boosting", "adaboost"]
62
+ },
63
+ "pca": {
64
+ "name": "Principal Component Analysis",
65
+ "category": "classical_ml",
66
+ "description": "A dimensionality reduction technique that finds principal components of data variance",
67
+ "synonyms": ["PCA", "principal component analysis", "principal components"]
68
+ },
69
+ "linear_regression": {
70
+ "name": "Linear Regression",
71
+ "category": "classical_ml",
72
+ "description": "A linear approach to modeling the relationship between variables",
73
+ "synonyms": ["linear regression", "ordinary least squares", "OLS", "linear model"]
74
+ },
75
+ "cnn": {
76
+ "name": "Convolutional Neural Network",
77
+ "category": "deep_learning",
78
+ "description": "Deep learning architecture specialized for processing grid-like data such as images",
79
+ "synonyms": ["convolutional neural network", "CNN", "ConvNet", "convolutional network", "deep convolutional", "conv neural network", "convolution neural network"]
80
+ },
81
+ "lstm": {
82
+ "name": "Long Short-Term Memory",
83
+ "category": "deep_learning",
84
+ "description": "A type of recurrent neural network capable of learning long-term dependencies",
85
+ "synonyms": ["LSTM", "long short-term memory", "long short term memory", "LSTM network"]
86
+ },
87
+ "transformer": {
88
+ "name": "Transformer",
89
+ "category": "deep_learning",
90
+ "description": "Attention-based neural network architecture for sequence-to-sequence tasks",
91
+ "synonyms": ["transformer", "transformers", "transformer model", "transformer architecture", "self-attention"]
92
+ },
93
+ "resnet": {
94
+ "name": "ResNet",
95
+ "category": "deep_learning",
96
+ "description": "Residual Neural Network - a deep CNN architecture with skip connections",
97
+ "synonyms": ["ResNet", "resnet", "residual network", "residual neural network"]
98
+ },
99
+ "unet": {
100
+ "name": "U-Net",
101
+ "category": "deep_learning",
102
+ "description": "A CNN architecture for biomedical image segmentation with encoder-decoder structure",
103
+ "synonyms": ["U-Net", "UNet", "u-net", "unet"]
104
+ },
105
+ "gan": {
106
+ "name": "Generative Adversarial Network",
107
+ "category": "deep_learning",
108
+ "description": "A framework where two neural networks compete to generate realistic data",
109
+ "synonyms": ["GAN", "generative adversarial network", "generative adversarial networks", "GANs"]
110
+ },
111
+ "autoencoder": {
112
+ "name": "Autoencoder",
113
+ "category": "deep_learning",
114
+ "description": "Neural networks that learn efficient representations by encoding and decoding data",
115
+ "synonyms": ["autoencoder", "auto-encoder", "autoencoders", "variational autoencoder", "VAE"]
116
+ },
117
+ "vgg": {
118
+ "name": "VGG",
119
+ "category": "deep_learning",
120
+ "description": "Very Deep Convolutional Networks - a CNN architecture with small convolution filters",
121
+ "synonyms": ["VGG", "VGG-16", "VGG-19", "VGGNet"]
122
+ },
123
+ "inception": {
124
+ "name": "Inception",
125
+ "category": "deep_learning",
126
+ "description": "CNN architecture that uses inception modules for efficient computation",
127
+ "synonyms": ["Inception", "GoogLeNet", "Inception-v3", "Inception-v4", "inception network"]
128
+ },
129
+ "rnn": {
130
+ "name": "Recurrent Neural Network",
131
+ "category": "deep_learning",
132
+ "description": "Neural networks with memory that can process sequences of data",
133
+ "synonyms": ["RNN", "recurrent neural network", "recurrent network", "RNNs"]
134
+ },
135
+ "gru": {
136
+ "name": "Gated Recurrent Unit",
137
+ "category": "deep_learning",
138
+ "description": "Simplified variant of LSTM with fewer parameters and faster training",
139
+ "synonyms": ["GRU", "gated recurrent unit", "gated recurrent units", "GRUs"]
140
+ },
141
+ "yolo": {
142
+ "name": "YOLO",
143
+ "category": "deep_learning",
144
+ "description": "You Only Look Once - real-time object detection algorithm",
145
+ "synonyms": ["YOLO", "you only look once", "YOLOv3", "YOLOv4", "YOLOv5"]
146
+ },
147
+ "capsnet": {
148
+ "name": "Capsule Network",
149
+ "category": "deep_learning",
150
+ "description": "Neural network architecture that uses capsules to better model hierarchical relationships",
151
+ "synonyms": ["CapsNet", "capsule network", "capsule networks", "dynamic routing"]
152
+ },
153
+ "attention": {
154
+ "name": "Attention Mechanism",
155
+ "category": "deep_learning",
156
+ "description": "Mechanism that allows models to focus on relevant parts of input sequences",
157
+ "synonyms": ["attention mechanism", "attention", "multi-head attention", "scaled dot-product attention"]
158
+ },
159
+ "gpt": {
160
+ "name": "GPT",
161
+ "category": "llms",
162
+ "description": "Generative Pre-trained Transformer - OpenAI's large language model family",
163
+ "synonyms": ["GPT", "gpt", "generative pre-trained transformer", "ChatGPT", "GPT-3", "GPT-4", "GPT-4o", "OpenAI GPT"]
164
+ },
165
+ "claude": {
166
+ "name": "Claude",
167
+ "category": "llms",
168
+ "description": "Anthropic's AI assistant and large language model family",
169
+ "synonyms": ["Claude", "claude", "Anthropic Claude", "Claude-3", "Claude Sonnet", "Claude Haiku", "Claude Opus"]
170
+ },
171
+ "bert": {
172
+ "name": "BERT",
173
+ "category": "llms",
174
+ "description": "Bidirectional Encoder Representations from Transformers - Google's pre-trained language model",
175
+ "synonyms": ["BERT", "bert", "bidirectional encoder representations", "BERT model", "Google BERT"]
176
+ },
177
+ "gemini": {
178
+ "name": "Gemini",
179
+ "category": "llms",
180
+ "description": "Google's multimodal large language model family",
181
+ "synonyms": ["Gemini", "gemini", "Google Gemini", "Gemini Pro", "Gemini Ultra", "Gemini Nano"]
182
+ },
183
+ "llama": {
184
+ "name": "LLaMA",
185
+ "category": "llms",
186
+ "description": "Large Language Model Meta AI - Meta's open-source language model family",
187
+ "synonyms": ["LLaMA", "llama", "Llama", "Meta LLaMA", "Llama-2", "Llama 2", "Llama 3", "Code Llama"]
188
+ },
189
+ "qwen": {
190
+ "name": "Qwen",
191
+ "category": "llms",
192
+ "description": "Alibaba's large language model series with multilingual capabilities",
193
+ "synonyms": ["Qwen", "qwen", "Alibaba Qwen", "Qwen-7B", "Qwen-14B", "Qwen-72B", "Tongyi Qianwen"]
194
+ },
195
+ "deepseek": {
196
+ "name": "DeepSeek",
197
+ "category": "llms",
198
+ "description": "DeepSeek's large language model optimized for code and reasoning",
199
+ "synonyms": ["DeepSeek", "deepseek", "DeepSeek Coder", "DeepSeek LLM", "DeepSeek-V2"]
200
+ },
201
+ "mistral": {
202
+ "name": "Mistral",
203
+ "category": "llms",
204
+ "description": "Mistral AI's efficient and powerful open-source language models",
205
+ "synonyms": ["Mistral", "mistral", "Mistral 7B", "Mixtral", "Mistral AI", "Mixtral 8x7B"]
206
+ },
207
+ "palm": {
208
+ "name": "PaLM",
209
+ "category": "llms",
210
+ "description": "Pathways Language Model - Google's large-scale language model",
211
+ "synonyms": ["PaLM", "palm", "Pathways Language Model", "PaLM-2", "Google PaLM"]
212
+ },
213
+ "t5": {
214
+ "name": "T5",
215
+ "category": "llms",
216
+ "description": "Text-to-Text Transfer Transformer - Google's unified text processing model",
217
+ "synonyms": ["T5", "t5", "text-to-text transfer transformer", "Google T5"]
218
+ },
219
+ "roberta": {
220
+ "name": "RoBERTa",
221
+ "category": "llms",
222
+ "description": "Robustly Optimized BERT Pretraining Approach - Meta's improved BERT variant",
223
+ "synonyms": ["RoBERTa", "roberta", "robustly optimized BERT", "Meta RoBERTa"]
224
+ },
225
+ "phi": {
226
+ "name": "Phi",
227
+ "category": "llms",
228
+ "description": "Microsoft's small language model series optimized for efficiency",
229
+ "synonyms": ["Phi", "phi", "Microsoft Phi", "Phi-3", "Phi-2", "Phi-1"]
230
+ },
231
+ "falcon": {
232
+ "name": "Falcon",
233
+ "category": "llms",
234
+ "description": "Technology Innovation Institute's open-source large language model",
235
+ "synonyms": ["Falcon", "falcon", "Falcon-7B", "Falcon-40B", "Falcon-180B", "TII Falcon"]
236
+ }
237
+ }
238
+ }
data/timeline-cache.json ADDED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "svm-2015": 2191,
3
+ "svm-2016": 2220,
4
+ "svm-2017": 2496,
5
+ "svm-2018": 2800,
6
+ "svm-2019": 3138,
7
+ "svm-2020": 3552,
8
+ "svm-2021": 4402,
9
+ "svm-2022": 5312,
10
+ "svm-2023": 5015,
11
+ "svm-2024": 5569,
12
+ "decision_tree-2015": 2690,
13
+ "decision_tree-2016": 2822,
14
+ "decision_tree-2017": 3089,
15
+ "decision_tree-2018": 3521,
16
+ "decision_tree-2019": 3884,
17
+ "decision_tree-2020": 4730,
18
+ "decision_tree-2021": 5816,
19
+ "decision_tree-2022": 6297,
20
+ "decision_tree-2023": 6106,
21
+ "decision_tree-2024": 6727,
22
+ "random_forest-2015": 2927,
23
+ "random_forest-2016": 3092,
24
+ "random_forest-2017": 3413,
25
+ "random_forest-2018": 4008,
26
+ "random_forest-2019": 4797,
27
+ "random_forest-2020": 6107,
28
+ "random_forest-2021": 8016,
29
+ "random_forest-2022": 9246,
30
+ "random_forest-2023": 9684,
31
+ "random_forest-2024": 11618,
32
+ "xgboost-2015": 1,
33
+ "xgboost-2016": 3,
34
+ "xgboost-2017": 16,
35
+ "xgboost-2018": 66,
36
+ "xgboost-2019": 180,
37
+ "xgboost-2020": 445,
38
+ "xgboost-2021": 1000,
39
+ "xgboost-2022": 1660,
40
+ "xgboost-2023": 2104,
41
+ "xgboost-2024": 3269,
42
+ "logistic_regression-2015": 24898,
43
+ "logistic_regression-2016": 26330,
44
+ "logistic_regression-2017": 28299,
45
+ "logistic_regression-2018": 30524,
46
+ "logistic_regression-2019": 34055,
47
+ "logistic_regression-2020": 41392,
48
+ "logistic_regression-2021": 50426,
49
+ "logistic_regression-2022": 53795,
50
+ "logistic_regression-2023": 50528,
51
+ "logistic_regression-2024": 54933,
52
+ "naive_bayes-2015": 1473,
53
+ "naive_bayes-2016": 1641,
54
+ "naive_bayes-2017": 1790,
55
+ "naive_bayes-2018": 2062,
56
+ "naive_bayes-2019": 2255,
57
+ "naive_bayes-2020": 2857,
58
+ "naive_bayes-2021": 3505,
59
+ "naive_bayes-2022": 3738,
60
+ "naive_bayes-2023": 4030,
61
+ "naive_bayes-2024": 4923,
62
+ "knn-2015": 574,
63
+ "knn-2016": 604,
64
+ "knn-2017": 595,
65
+ "knn-2018": 809,
66
+ "knn-2019": 901,
67
+ "knn-2020": 1027,
68
+ "knn-2021": 1507,
69
+ "knn-2022": 1829,
70
+ "knn-2023": 1875,
71
+ "knn-2024": 2144,
72
+ "kmeans-2015": 345,
73
+ "kmeans-2016": 353,
74
+ "kmeans-2017": 424,
75
+ "kmeans-2018": 490,
76
+ "kmeans-2019": 584,
77
+ "kmeans-2020": 730,
78
+ "kmeans-2021": 1016,
79
+ "kmeans-2022": 1255,
80
+ "kmeans-2023": 1272,
81
+ "kmeans-2024": 1373,
82
+ "gradient_boosting-2015": 2774,
83
+ "gradient_boosting-2016": 2787,
84
+ "gradient_boosting-2017": 3104,
85
+ "gradient_boosting-2018": 3688,
86
+ "gradient_boosting-2019": 4101,
87
+ "gradient_boosting-2020": 4975,
88
+ "gradient_boosting-2021": 6060,
89
+ "gradient_boosting-2022": 6674,
90
+ "gradient_boosting-2023": 6827,
91
+ "gradient_boosting-2024": 7834,
92
+ "ada_boost-2015": 67,
93
+ "ada_boost-2016": 76,
94
+ "ada_boost-2017": 74,
95
+ "ada_boost-2018": 82,
96
+ "ada_boost-2019": 117,
97
+ "ada_boost-2020": 161,
98
+ "ada_boost-2021": 271,
99
+ "ada_boost-2022": 363,
100
+ "ada_boost-2023": 370,
101
+ "ada_boost-2024": 499,
102
+ "pca-2015": 6571,
103
+ "pca-2016": 6845,
104
+ "pca-2017": 7096,
105
+ "pca-2018": 7622,
106
+ "pca-2019": 8342,
107
+ "pca-2020": 9232,
108
+ "pca-2021": 10167,
109
+ "pca-2022": 10370,
110
+ "pca-2023": 9496,
111
+ "pca-2024": 9824,
112
+ "linear_regression-2015": 9508,
113
+ "linear_regression-2016": 10092,
114
+ "linear_regression-2017": 10640,
115
+ "linear_regression-2018": 11826,
116
+ "linear_regression-2019": 13019,
117
+ "linear_regression-2020": 15565,
118
+ "linear_regression-2021": 18227,
119
+ "linear_regression-2022": 19244,
120
+ "linear_regression-2023": 18409,
121
+ "linear_regression-2024": 19206,
122
+ "cnn-2015": 116,
123
+ "cnn-2016": 273,
124
+ "cnn-2017": 719,
125
+ "cnn-2018": 1676,
126
+ "cnn-2019": 2912,
127
+ "cnn-2020": 4256,
128
+ "cnn-2021": 6357,
129
+ "cnn-2022": 8084,
130
+ "cnn-2023": 7326,
131
+ "cnn-2024": 7451,
132
+ "lstm-2015": 21,
133
+ "lstm-2016": 40,
134
+ "lstm-2017": 117,
135
+ "lstm-2018": 272,
136
+ "lstm-2019": 513,
137
+ "lstm-2020": 773,
138
+ "lstm-2021": 1334,
139
+ "lstm-2022": 1969,
140
+ "lstm-2023": 1826,
141
+ "lstm-2024": 2059,
142
+ "transformer-2015": 111,
143
+ "transformer-2016": 108,
144
+ "transformer-2017": 102,
145
+ "transformer-2018": 138,
146
+ "transformer-2019": 184,
147
+ "transformer-2020": 354,
148
+ "transformer-2021": 683,
149
+ "transformer-2022": 1698,
150
+ "transformer-2023": 3208,
151
+ "transformer-2024": 4961,
152
+ "resnet-2015": 2,
153
+ "resnet-2016": 4,
154
+ "resnet-2017": 23,
155
+ "resnet-2018": 65,
156
+ "resnet-2019": 228,
157
+ "resnet-2020": 382,
158
+ "resnet-2021": 752,
159
+ "resnet-2022": 1102,
160
+ "resnet-2023": 1131,
161
+ "resnet-2024": 1205,
162
+ "unet-2015": 3,
163
+ "unet-2016": 2,
164
+ "unet-2017": 14,
165
+ "unet-2018": 108,
166
+ "unet-2019": 350,
167
+ "unet-2020": 670,
168
+ "unet-2021": 1103,
169
+ "unet-2022": 1552,
170
+ "unet-2023": 1698,
171
+ "unet-2024": 1818,
172
+ "gan-2015": 3093,
173
+ "gan-2016": 3349,
174
+ "gan-2017": 3312,
175
+ "gan-2018": 3863,
176
+ "gan-2019": 4344,
177
+ "gan-2020": 5126,
178
+ "gan-2021": 5986,
179
+ "gan-2022": 6676,
180
+ "gan-2023": 6611,
181
+ "gan-2024": 6944,
182
+ "autoencoder-2015": 44,
183
+ "autoencoder-2016": 102,
184
+ "autoencoder-2017": 162,
185
+ "autoencoder-2018": 300,
186
+ "autoencoder-2019": 397,
187
+ "autoencoder-2020": 563,
188
+ "autoencoder-2021": 821,
189
+ "autoencoder-2022": 1075,
190
+ "autoencoder-2023": 1181,
191
+ "autoencoder-2024": 1358,
192
+ "vgg-2015": 4,
193
+ "vgg-2016": 10,
194
+ "vgg-2017": 25,
195
+ "vgg-2018": 43,
196
+ "vgg-2019": 107,
197
+ "vgg-2020": 142,
198
+ "vgg-2021": 293,
199
+ "vgg-2022": 365,
200
+ "vgg-2023": 322,
201
+ "vgg-2024": 287,
202
+ "inception-2015": 551,
203
+ "inception-2016": 550,
204
+ "inception-2017": 696,
205
+ "inception-2018": 924,
206
+ "inception-2019": 1189,
207
+ "inception-2020": 1757,
208
+ "inception-2021": 1892,
209
+ "inception-2022": 1995,
210
+ "inception-2023": 1931,
211
+ "inception-2024": 2033,
212
+ "rnn-2015": 105,
213
+ "rnn-2016": 116,
214
+ "rnn-2017": 166,
215
+ "rnn-2018": 325,
216
+ "rnn-2019": 462,
217
+ "rnn-2020": 503,
218
+ "rnn-2021": 747,
219
+ "rnn-2022": 854,
220
+ "rnn-2023": 767,
221
+ "rnn-2024": 734,
222
+ "gru-2015": 307,
223
+ "gru-2016": 230,
224
+ "gru-2017": 89,
225
+ "gru-2018": 107,
226
+ "gru-2019": 138,
227
+ "gru-2020": 169,
228
+ "gru-2021": 297,
229
+ "gru-2022": 438,
230
+ "gru-2023": 447,
231
+ "gru-2024": 560,
232
+ "yolo-2015": 10,
233
+ "yolo-2016": 7,
234
+ "yolo-2017": 16,
235
+ "yolo-2018": 25,
236
+ "yolo-2019": 40,
237
+ "yolo-2020": 108,
238
+ "yolo-2021": 246,
239
+ "yolo-2022": 588,
240
+ "yolo-2023": 774,
241
+ "yolo-2024": 886,
242
+ "capsnet-2015": 8,
243
+ "capsnet-2016": 4,
244
+ "capsnet-2017": 5,
245
+ "capsnet-2018": 9,
246
+ "capsnet-2019": 22,
247
+ "capsnet-2020": 42,
248
+ "capsnet-2021": 72,
249
+ "capsnet-2022": 103,
250
+ "capsnet-2023": 82,
251
+ "capsnet-2024": 73,
252
+ "attention-2015": 22456,
253
+ "attention-2016": 23429,
254
+ "attention-2017": 24706,
255
+ "attention-2018": 26810,
256
+ "attention-2019": 29325,
257
+ "attention-2020": 35137,
258
+ "attention-2021": 40508,
259
+ "attention-2022": 44763,
260
+ "attention-2023": 44196,
261
+ "attention-2024": 46082,
262
+ "gpt-2015": 110,
263
+ "gpt-2016": 93,
264
+ "gpt-2017": 96,
265
+ "gpt-2018": 96,
266
+ "gpt-2019": 97,
267
+ "gpt-2020": 118,
268
+ "gpt-2021": 151,
269
+ "gpt-2022": 167,
270
+ "gpt-2023": 2314,
271
+ "gpt-2024": 4243,
272
+ "claude-2015": 1555,
273
+ "claude-2016": 1864,
274
+ "claude-2017": 2127,
275
+ "claude-2018": 2282,
276
+ "claude-2019": 2379,
277
+ "claude-2020": 2694,
278
+ "claude-2021": 3013,
279
+ "claude-2022": 2895,
280
+ "claude-2023": 2760,
281
+ "claude-2024": 2979,
282
+ "bert-2015": 147,
283
+ "bert-2016": 152,
284
+ "bert-2017": 129,
285
+ "bert-2018": 142,
286
+ "bert-2019": 141,
287
+ "bert-2020": 227,
288
+ "bert-2021": 391,
289
+ "bert-2022": 588,
290
+ "bert-2023": 577,
291
+ "bert-2024": 706,
292
+ "gemini-2015": 158,
293
+ "gemini-2016": 181,
294
+ "gemini-2017": 190,
295
+ "gemini-2018": 175,
296
+ "gemini-2019": 189,
297
+ "gemini-2020": 233,
298
+ "gemini-2021": 250,
299
+ "gemini-2022": 228,
300
+ "gemini-2023": 219,
301
+ "gemini-2024": 491,
302
+ "llama-2015": 50,
303
+ "llama-2016": 32,
304
+ "llama-2017": 50,
305
+ "llama-2018": 57,
306
+ "llama-2019": 46,
307
+ "llama-2020": 62,
308
+ "llama-2021": 72,
309
+ "llama-2022": 60,
310
+ "llama-2023": 66,
311
+ "llama-2024": 224,
312
+ "qwen-2015": 0,
313
+ "qwen-2016": 0,
314
+ "qwen-2017": 0,
315
+ "qwen-2018": 3,
316
+ "qwen-2019": 1,
317
+ "qwen-2020": 1,
318
+ "qwen-2021": 2,
319
+ "qwen-2022": 2,
320
+ "qwen-2023": 1,
321
+ "qwen-2024": 11,
322
+ "deepseek-2015": 0,
323
+ "deepseek-2016": 0,
324
+ "deepseek-2017": 0,
325
+ "deepseek-2018": 0,
326
+ "deepseek-2019": 0,
327
+ "deepseek-2020": 0,
328
+ "deepseek-2021": 0,
329
+ "deepseek-2022": 0,
330
+ "deepseek-2023": 0,
331
+ "deepseek-2024": 1,
332
+ "mistral-2015": 11,
333
+ "mistral-2016": 14,
334
+ "mistral-2017": 14,
335
+ "mistral-2018": 8,
336
+ "mistral-2019": 22,
337
+ "mistral-2020": 33,
338
+ "mistral-2021": 36,
339
+ "mistral-2022": 25,
340
+ "mistral-2023": 25,
341
+ "mistral-2024": 73,
342
+ "palm-2015": 1078,
343
+ "palm-2016": 1126,
344
+ "palm-2017": 1298,
345
+ "palm-2018": 1297,
346
+ "palm-2019": 1443,
347
+ "palm-2020": 1685,
348
+ "palm-2021": 1834,
349
+ "palm-2022": 1752,
350
+ "palm-2023": 1708,
351
+ "palm-2024": 1778,
352
+ "t5-2015": 554,
353
+ "t5-2016": 492,
354
+ "t5-2017": 601,
355
+ "t5-2018": 670,
356
+ "t5-2019": 699,
357
+ "t5-2020": 762,
358
+ "t5-2021": 850,
359
+ "t5-2022": 848,
360
+ "t5-2023": 890,
361
+ "t5-2024": 947,
362
+ "roberta-2015": 144,
363
+ "roberta-2016": 136,
364
+ "roberta-2017": 140,
365
+ "roberta-2018": 162,
366
+ "roberta-2019": 227,
367
+ "roberta-2020": 188,
368
+ "roberta-2021": 176,
369
+ "roberta-2022": 224,
370
+ "roberta-2023": 267,
371
+ "roberta-2024": 306,
372
+ "phi-2015": 758,
373
+ "phi-2016": 644,
374
+ "phi-2017": 812,
375
+ "phi-2018": 874,
376
+ "phi-2019": 937,
377
+ "phi-2020": 1028,
378
+ "phi-2021": 1094,
379
+ "phi-2022": 1167,
380
+ "phi-2023": 1172,
381
+ "phi-2024": 1230,
382
+ "falcon-2015": 117,
383
+ "falcon-2016": 122,
384
+ "falcon-2017": 116,
385
+ "falcon-2018": 135,
386
+ "falcon-2019": 154,
387
+ "falcon-2020": 178,
388
+ "falcon-2021": 215,
389
+ "falcon-2022": 181,
390
+ "falcon-2023": 184,
391
+ "falcon-2024": 182
392
+ }
frontend/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>PubMed AI Explorer</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
frontend/package.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "pubmed-ai-explorer-frontend",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "axios": "^1.6.2",
7
+ "lucide-react": "^0.294.0",
8
+ "react": "^18.2.0",
9
+ "react-dom": "^18.2.0",
10
+ "react-markdown": "^10.1.0",
11
+ "react-router-dom": "^6.8.1",
12
+ "recharts": "^2.8.0"
13
+ },
14
+ "scripts": {
15
+ "dev": "vite",
16
+ "build": "vite build",
17
+ "preview": "vite preview"
18
+ },
19
+ "devDependencies": {
20
+ "@types/react": "^18.2.43",
21
+ "@types/react-dom": "^18.2.17",
22
+ "@vitejs/plugin-react": "^4.2.1",
23
+ "autoprefixer": "^10.4.16",
24
+ "postcss": "^8.4.32",
25
+ "tailwindcss": "^3.3.6",
26
+ "typescript": "^5.2.2",
27
+ "vite": "^5.0.8"
28
+ }
29
+ }
frontend/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
frontend/src/App.tsx ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
3
+ import Navbar from './components/Navbar';
4
+ import Dashboard from './pages/Dashboard';
5
+ import Search from './pages/Search';
6
+ import Timeline from './pages/Timeline';
7
+ import Roadmap from './pages/Roadmap';
8
+ import AlgorithmDetail from './pages/AlgorithmDetail';
9
+
10
+ function App() {
11
+ return (
12
+ <Router>
13
+ <div className="min-h-screen bg-gray-50 flex flex-col">
14
+ <Navbar />
15
+ <main className="container mx-auto px-4 py-8 flex-grow">
16
+ <Routes>
17
+ <Route path="/" element={<Dashboard />} />
18
+ <Route path="/search" element={<Search />} />
19
+ <Route path="/timeline" element={<Timeline />} />
20
+ <Route path="/roadmap" element={<Roadmap />} />
21
+ <Route path="/algorithm/:algorithmId" element={<AlgorithmDetail />} />
22
+ </Routes>
23
+ </main>
24
+ <footer className="bg-white border-t border-gray-200 py-4">
25
+ <div className="container mx-auto px-4 text-center text-sm text-gray-600">
26
+ made by{' '}
27
+ <a
28
+ href="https://www.linkedin.com/in/aleksander-obuchowski/"
29
+ target="_blank"
30
+ rel="noopener noreferrer"
31
+ className="text-blue-600 hover:text-blue-800 underline"
32
+ >
33
+ Aleksander Obuchowski
34
+ </a>
35
+ {' '}@{' '}
36
+ <a
37
+ href="https://thelion.ai"
38
+ target="_blank"
39
+ rel="noopener noreferrer"
40
+ className="text-blue-600 hover:text-blue-800 underline"
41
+ >
42
+ thelion.ai
43
+ </a>
44
+ </div>
45
+ </footer>
46
+ </div>
47
+ </Router>
48
+ );
49
+ }
50
+
51
+ export default App;
frontend/src/components/AlgorithmCard.tsx ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { ExternalLink, FileText, Loader2, Brain, Cpu, MessageSquare } from 'lucide-react';
3
+ import { Link } from 'react-router-dom';
4
+ import api from '../services/api';
5
+
6
+ interface Algorithm {
7
+ algorithm: string;
8
+ name: string;
9
+ category: string;
10
+ description: string;
11
+ count: number;
12
+ sampleIds: string[];
13
+ }
14
+
15
+ interface Paper {
16
+ pmid: string;
17
+ title: string;
18
+ authors: string[];
19
+ journal: string;
20
+ pubDate: string;
21
+ }
22
+
23
+ interface AlgorithmCardProps {
24
+ algorithm: Algorithm;
25
+ problem: string;
26
+ onSeeMorePapers: (algorithm: string) => void;
27
+ }
28
+
29
+ const AlgorithmCard: React.FC<AlgorithmCardProps> = ({ algorithm, problem, onSeeMorePapers }) => {
30
+ const [samplePapers, setSamplePapers] = useState<Paper[]>([]);
31
+ const [loadingPapers, setLoadingPapers] = useState(false);
32
+
33
+ useEffect(() => {
34
+ // Temporarily disabled to avoid PubMed API rate limiting issues
35
+ // if (algorithm.sampleIds.length > 0) {
36
+ // fetchSamplePapers();
37
+ // }
38
+ }, [algorithm.sampleIds]);
39
+
40
+ const fetchSamplePapers = async () => {
41
+ try {
42
+ setLoadingPapers(true);
43
+ console.log(`[${algorithm.name}] Fetching sample papers for IDs:`, algorithm.sampleIds);
44
+
45
+ const papers = [];
46
+ // Fetch papers sequentially with delays to avoid rate limiting
47
+ for (let i = 0; i < algorithm.sampleIds.length && i < 2; i++) {
48
+ try {
49
+ console.log(`[${algorithm.name}] Fetching paper ${i + 1}/${Math.min(algorithm.sampleIds.length, 2)}: ${algorithm.sampleIds[i]}`);
50
+
51
+ if (i > 0) {
52
+ // Add delay between requests to avoid rate limiting
53
+ await new Promise(resolve => setTimeout(resolve, 500));
54
+ }
55
+
56
+ const response = await api.get(`/pubmed/paper/${algorithm.sampleIds[i]}`);
57
+ console.log(`[${algorithm.name}] Response for ${algorithm.sampleIds[i]}:`, response.status, response.data);
58
+
59
+ if (response.data && response.data.title) {
60
+ papers.push(response.data);
61
+ console.log(`[${algorithm.name}] Successfully added paper: ${response.data.title}`);
62
+ } else {
63
+ console.log(`[${algorithm.name}] Invalid paper data for ${algorithm.sampleIds[i]}:`, response.data);
64
+ }
65
+ } catch (error) {
66
+ console.error(`[${algorithm.name}] Failed to fetch paper ${algorithm.sampleIds[i]}:`, {
67
+ status: error.response?.status,
68
+ statusText: error.response?.statusText,
69
+ data: error.response?.data,
70
+ message: error.message
71
+ });
72
+ // Continue with next paper if one fails
73
+ }
74
+ }
75
+
76
+ console.log(`[${algorithm.name}] Final fetched papers:`, papers);
77
+ setSamplePapers(papers);
78
+ } catch (error) {
79
+ console.error(`[${algorithm.name}] Failed to fetch sample papers:`, error);
80
+ } finally {
81
+ setLoadingPapers(false);
82
+ }
83
+ };
84
+
85
+ const getCategoryIcon = (category: string) => {
86
+ switch (category) {
87
+ case 'deep_learning':
88
+ return <Cpu className="h-5 w-5 text-purple-600" />;
89
+ case 'llms':
90
+ return <MessageSquare className="h-5 w-5 text-green-600" />;
91
+ default:
92
+ return <Brain className="h-5 w-5 text-blue-600" />;
93
+ }
94
+ };
95
+
96
+ const getCategoryColor = (category: string) => {
97
+ switch (category) {
98
+ case 'deep_learning':
99
+ return 'bg-purple-100 text-purple-800';
100
+ case 'llms':
101
+ return 'bg-green-100 text-green-800';
102
+ default:
103
+ return 'bg-blue-100 text-blue-800';
104
+ }
105
+ };
106
+
107
+ return (
108
+ <div className="bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 overflow-hidden h-full flex flex-col">
109
+ <div className="p-6 flex-grow">
110
+ <div className="flex items-start justify-between mb-4">
111
+ <div className="flex items-center space-x-3">
112
+ {getCategoryIcon(algorithm.category)}
113
+ <Link
114
+ to={`/algorithm/${algorithm.algorithm}`}
115
+ className="text-lg font-semibold text-gray-900 leading-tight hover:text-blue-600 transition-colors"
116
+ >
117
+ {algorithm.name}
118
+ </Link>
119
+ </div>
120
+ <span className={`px-3 py-1 rounded-full text-xs font-semibold ${getCategoryColor(algorithm.category)} whitespace-nowrap ml-2`}>
121
+ {algorithm.category === 'deep_learning' ? 'Deep Learning' : algorithm.category === 'llms' ? 'LLMs' : 'Classical ML'}
122
+ </span>
123
+ </div>
124
+
125
+ <p className="text-gray-600 text-sm mb-4">{algorithm.description}</p>
126
+
127
+ <div className="mb-4">
128
+ <div className="text-center bg-gray-50 rounded-lg p-4">
129
+ <div className="text-3xl font-bold text-gray-900">{algorithm.count.toLocaleString()}</div>
130
+ <div className="text-sm text-gray-500 font-medium">Papers Found</div>
131
+ </div>
132
+ </div>
133
+
134
+ {algorithm.sampleIds.length > 0 && (
135
+ <div className="mb-4">
136
+ <h4 className="text-sm font-medium text-gray-900 mb-2">Sample Papers:</h4>
137
+ {loadingPapers ? (
138
+ <div className="flex items-center justify-center py-4">
139
+ <Loader2 className="h-4 w-4 animate-spin text-gray-400" />
140
+ <span className="ml-2 text-sm text-gray-500">Loading papers...</span>
141
+ </div>
142
+ ) : samplePapers.length > 0 ? (
143
+ <div className="space-y-3">
144
+ {samplePapers.slice(0, 2).map((paper) => (
145
+ <div key={paper.pmid} className="p-3 bg-gray-50 rounded-lg border border-gray-100">
146
+ <p className="font-medium text-gray-900 text-sm line-clamp-2 mb-1">{paper.title}</p>
147
+ <p className="text-gray-500 text-xs">
148
+ {paper.journal} {paper.pubDate && `(${paper.pubDate})`}
149
+ </p>
150
+ </div>
151
+ ))}
152
+ </div>
153
+ ) : (
154
+ <div className="p-3 bg-blue-50 rounded-lg border border-blue-100">
155
+ <p className="text-xs text-blue-600 text-center">
156
+ {algorithm.count} papers found - Click "See More Papers on PubMed" to view
157
+ </p>
158
+ </div>
159
+ )}
160
+ </div>
161
+ )}
162
+
163
+ </div>
164
+ <div className="p-6 pt-0 mt-auto">
165
+ <button
166
+ onClick={() => onSeeMorePapers(algorithm.algorithm)}
167
+ className="w-full bg-blue-600 text-white py-3 px-4 rounded-lg hover:bg-blue-700 transition-colors flex items-center justify-center text-sm font-medium shadow-sm"
168
+ >
169
+ <ExternalLink className="h-4 w-4 mr-2" />
170
+ See More Papers on PubMed
171
+ </button>
172
+ </div>
173
+ </div>
174
+ );
175
+ };
176
+
177
+ export default AlgorithmCard;
frontend/src/components/Navbar.tsx ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Link, useLocation } from 'react-router-dom';
3
+ import { BarChart3, Search, Calendar, Map } from 'lucide-react';
4
+
5
+ const Navbar: React.FC = () => {
6
+ const location = useLocation();
7
+
8
+ return (
9
+ <nav className="bg-white shadow-lg">
10
+ <div className="container mx-auto px-4">
11
+ <div className="flex justify-between h-16">
12
+ <div className="flex items-center">
13
+ <Link to="/" className="flex items-center space-x-2">
14
+ <BarChart3 className="h-8 w-8 text-blue-600" />
15
+ <span className="text-xl font-bold text-gray-900">PubMed AI Explorer</span>
16
+ </Link>
17
+ </div>
18
+
19
+ <div className="flex items-center space-x-4">
20
+ <Link
21
+ to="/"
22
+ className={`flex items-center space-x-1 px-3 py-2 rounded-md text-sm font-medium ${
23
+ location.pathname === '/'
24
+ ? 'bg-blue-100 text-blue-700'
25
+ : 'text-gray-500 hover:text-gray-700'
26
+ }`}
27
+ >
28
+ <BarChart3 className="h-4 w-4" />
29
+ <span>Dashboard</span>
30
+ </Link>
31
+
32
+ <Link
33
+ to="/search"
34
+ className={`flex items-center space-x-1 px-3 py-2 rounded-md text-sm font-medium ${
35
+ location.pathname === '/search'
36
+ ? 'bg-blue-100 text-blue-700'
37
+ : 'text-gray-500 hover:text-gray-700'
38
+ }`}
39
+ >
40
+ <Search className="h-4 w-4" />
41
+ <span>Search</span>
42
+ </Link>
43
+
44
+ <Link
45
+ to="/timeline"
46
+ className={`flex items-center space-x-1 px-3 py-2 rounded-md text-sm font-medium ${
47
+ location.pathname === '/timeline'
48
+ ? 'bg-blue-100 text-blue-700'
49
+ : 'text-gray-500 hover:text-gray-700'
50
+ }`}
51
+ >
52
+ <Calendar className="h-4 w-4" />
53
+ <span>Timeline</span>
54
+ </Link>
55
+
56
+ <Link
57
+ to="/roadmap"
58
+ className={`flex items-center space-x-1 px-3 py-2 rounded-md text-sm font-medium ${
59
+ location.pathname === '/roadmap'
60
+ ? 'bg-blue-100 text-blue-700'
61
+ : 'text-gray-500 hover:text-gray-700'
62
+ }`}
63
+ >
64
+ <Map className="h-4 w-4" />
65
+ <span>Roadmap</span>
66
+ </Link>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </nav>
71
+ );
72
+ };
73
+
74
+ export default Navbar;
frontend/src/docs/cnn.md ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Convolutional Neural Networks (CNNs) are a sophisticated machine learning technique that has found extensive applications in medical image analysis, disease diagnosis, and scientific research. For medical professionals, understanding the basics of how CNNs work can be crucial for effectively utilizing AI-based diagnostic tools. In this article, we will explain how convolutional neural networks operate and why they are so useful in medicine.
2
+
3
+ ### Basics of Convolutional Neural Networks
4
+
5
+ #### 1. Architecture of CNN
6
+
7
+ Convolutional Neural Networks consist of several layers, each serving a specific function:
8
+
9
+ - **Convolutional Layer:** This is the core of a CNN. This layer applies a set of filters (or kernels) to the input image to extract features such as edges, textures, and patterns. Each filter moves across the image, performing a convolution operation, which involves multiplying pixel values by the filter weights and summing the results.
10
+
11
+ - **ReLU Layer (Rectified Linear Unit):** After the convolution operation, a ReLU activation function is applied, introducing non-linearity to the model. ReLU replaces all negative values with zeros, which speeds up the learning process and improves the network's performance.
12
+
13
+ - **Pooling Layer:** This layer reduces the dimensionality of the data while retaining the most important information. The most commonly used type of pooling is max pooling, which selects the maximum value from a specific area, reducing the image size and the number of parameters, which translates to lower computational resource usage.
14
+
15
+ - **Fully Connected Layer:** At the end of the network, there are fully connected layers that combine all the extracted features and make the final classification. These layers work similarly to traditional neural networks, where each neuron is connected to every neuron in the previous layer.
16
+
17
+ #### 2. Learning Process
18
+
19
+ The learning process of a CNN involves adjusting the weights of the filters to minimize the error between the network's predictions and the actual labels of the training data. This is achieved using the backpropagation algorithm and various optimizers such as Stochastic Gradient Descent (SGD) or Adam.
20
+
21
+ ### Applications in Medicine
22
+
23
+ #### 1. Diagnostic Imaging
24
+
25
+ One of the most important applications of CNNs in medicine is the analysis of medical images such as X-rays, computed tomography (CT) scans, and magnetic resonance imaging (MRI). These networks can detect abnormalities such as tumors, fractures, or pathological changes with high accuracy, often matching or surpassing human capabilities.
26
+
27
+ #### 2. Image Segmentation
28
+
29
+ CNNs are also used for segmenting medical images, dividing the image into different regions corresponding to various tissues or anatomical structures. This is particularly useful in treatment planning and monitoring disease progression.
30
+
31
+ #### 3. Disease Classification
32
+
33
+ Convolutional Neural Networks can classify medical images into different disease categories. For example, they can distinguish healthy tissue from diseased tissue, identify various stages of disease, or classify types of tumors.
34
+
35
+ ### Benefits of Using CNNs in Medicine
36
+
37
+ - **Improved Diagnostic Accuracy:** CNNs can detect subtle patterns and features that may be difficult for the human eye to notice.
38
+ - **Speed and Efficiency:** Automating the analysis of medical images with CNNs can significantly speed up the diagnostic process, allowing doctors to focus on other aspects of patient care.
39
+ - **Reduction of Errors:** Decreasing the risk of diagnostic errors by supporting doctors' decisions with artificial intelligence.
40
+
41
+ ### Challenges and Limitations
42
+
43
+ Despite many advantages, the application of CNNs in medicine also comes with certain challenges:
44
+
45
+ - **Need for Large Datasets:** CNNs require large amounts of training data, which must be appropriately labeled by experts. In medicine, obtaining such data can be difficult and expensive.
46
+ - **Interpretability of Models:** CNNs are often seen as "black boxes," meaning their decisions can be difficult for doctors to interpret.
47
+ - **Ethical and Legal Issues:** The use of artificial intelligence in medicine raises questions about responsibility for diagnostic errors, patient data protection, and the ethics of using algorithms in the decision-making process.
48
+
49
+ ### Conclusion
50
+
51
+ Convolutional Neural Networks are a powerful tool that can significantly aid medical professionals, improving the accuracy and speed of diagnostics. Despite the challenges associated with their implementation, the potential of CNNs in medicine is vast, and it is worth investing in further research and development of this technology. For doctors, understanding the basics of how CNNs work can be key to effectively using AI-based tools and better serving their patients.
frontend/src/docs/linear_regression.md ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Linear Regression
2
+
3
+ **Category:** Classical Machine Learning
4
+
5
+ ## Overview
6
+
7
+ Linear regression establishes a mathematical relationship between predictor variables and a continuous outcome variable. The algorithm identifies the optimal linear combination of input features that best predicts the target variable by minimizing the sum of squared residuals between predicted and observed values.
8
+
9
+ ## Mathematical Foundation
10
+
11
+ ### Linear Model Equation
12
+ ```
13
+ y = β₀ + β₁x₁ + β₂x₂ + ... + βₚxₚ + ε
14
+ ```
15
+
16
+ Where:
17
+ - **y** = dependent variable (outcome)
18
+ - **β₀** = intercept (baseline value when all predictors = 0)
19
+ - **β₁, β₂, ..., βₚ** = regression coefficients
20
+ - **x₁, x₂, ..., xₚ** = independent variables (predictors)
21
+ - **ε** = error term (residual)
22
+
23
+ ### Ordinary Least Squares (OLS)
24
+
25
+ The method minimizes the sum of squared residuals:
26
+ ```
27
+ minimize: Σ(yᵢ - ŷᵢ)²
28
+ ```
29
+
30
+ This optimization finds coefficient values that provide the best linear fit to the observed data.
31
+
32
+ ## Medical Applications
33
+
34
+ ### Clinical Research
35
+ - **Dose-response modeling** for pharmaceutical studies
36
+ - **Biomarker relationship analysis** in clinical trials
37
+ - **Treatment outcome prediction** based on patient characteristics
38
+ - **Healthcare cost prediction** models
39
+
40
+ ### Diagnostic Applications
41
+ - Predicting laboratory values based on patient demographics
42
+ - Modeling disease progression rates
43
+ - Risk factor quantification for preventive medicine
44
+
45
+ ### Epidemiological Studies
46
+ - Population health trend analysis
47
+ - Environmental factor impact assessment
48
+ - Disease prevalence modeling
49
+
50
+ ## Key Assumptions
51
+
52
+ ### 1. Linearity
53
+ The relationship between independent and dependent variables must be linear. Non-linear relationships require transformation or alternative methods.
54
+
55
+ ### 2. Independence
56
+ Observations must be independent of each other. Violations occur in:
57
+ - Repeated measures data
58
+ - Clustered sampling
59
+ - Time series data
60
+
61
+ ### 3. Homoscedasticity
62
+ Residual variance should be constant across all fitted values. Heteroscedasticity can be detected through:
63
+ - Residual plots
64
+ - Breusch-Pagan test
65
+ - White test
66
+
67
+ ### 4. Normality of Residuals
68
+ Residuals should follow a normal distribution, particularly important for:
69
+ - Confidence intervals
70
+ - Hypothesis testing
71
+ - Prediction intervals
72
+
73
+ ## Clinical Considerations
74
+
75
+ ### Multicollinearity
76
+ **Problem:** Highly correlated predictors can make coefficient interpretation unreliable.
77
+
78
+ **Solutions:**
79
+ - Variance Inflation Factor (VIF) assessment
80
+ - Principal component analysis
81
+ - Ridge or LASSO regression
82
+
83
+ ### Outliers and Influential Points
84
+ **Detection Methods:**
85
+ - Cook's distance
86
+ - Leverage values
87
+ - Studentized residuals
88
+
89
+ **Impact:** Single outliers can significantly alter regression coefficients and predictions.
90
+
91
+ ### Sample Size Requirements
92
+ **General Rule:** Minimum 10-15 observations per predictor variable.
93
+
94
+ **Considerations:**
95
+ - Effect size expectations
96
+ - Desired statistical power
97
+ - Expected R² value
98
+
99
+ ### Model Validation
100
+ - **Cross-validation** for generalizability assessment
101
+ - **Hold-out validation** sets
102
+ - **Bootstrap methods** for confidence intervals
103
+
104
+ ## Performance Metrics
105
+
106
+ ### R² (Coefficient of Determination)
107
+ - **Range:** 0 to 1
108
+ - **Interpretation:** Proportion of variance explained by the model
109
+ - **Medical Context:** Higher values indicate better predictive capability
110
+
111
+ ### Root Mean Square Error (RMSE)
112
+ - **Units:** Same as dependent variable
113
+ - **Interpretation:** Average prediction error magnitude
114
+ - **Clinical Relevance:** Should be within acceptable clinical tolerance
115
+
116
+ ### F-statistic
117
+ - **Purpose:** Tests overall model significance
118
+ - **Interpretation:** Higher values with low p-values indicate significant predictive ability
119
+ - **Threshold:** p < 0.05 for statistical significance
120
+
121
+ ### Adjusted R²
122
+ - **Advantage:** Penalizes for additional predictors
123
+ - **Use Case:** Model comparison with different numbers of variables
124
+
125
+ ## Limitations in Medical Context
126
+
127
+ ### Extrapolation Risks
128
+ Predictions outside the observed data range may be unreliable, particularly concerning for:
129
+ - Extreme dosages
130
+ - Unusual patient populations
131
+ - Novel treatment combinations
132
+
133
+ ### Assumption Violations
134
+ Many medical datasets violate linear regression assumptions:
135
+ - Non-linear dose-response relationships
136
+ - Heteroscedastic error patterns
137
+ - Non-normal distributions
138
+
139
+ ### Interpretation Challenges
140
+ - Correlation vs. causation distinctions
141
+ - Confounding variable effects
142
+ - Clinical vs. statistical significance
143
+
144
+ ## Best Practices for Medical Applications
145
+
146
+ ### Model Development
147
+ 1. Conduct thorough exploratory data analysis
148
+ 2. Test all assumptions before model fitting
149
+ 3. Consider clinical knowledge in variable selection
150
+ 4. Validate findings with independent datasets
151
+
152
+ ### Reporting Standards
153
+ 1. Report confidence intervals alongside point estimates
154
+ 2. Discuss clinical significance of coefficients
155
+ 3. Acknowledge model limitations
156
+ 4. Provide goodness-of-fit statistics
157
+
158
+ ### Clinical Implementation
159
+ 1. Validate in target population
160
+ 2. Consider integration with clinical workflow
161
+ 3. Monitor performance over time
162
+ 4. Update models as new data becomes available
frontend/src/index.css ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
frontend/src/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App.tsx'
4
+ import './index.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ )
frontend/src/pages/AlgorithmDetail.tsx ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useParams, useNavigate } from 'react-router-dom';
3
+ import { ArrowLeft, Brain, Cpu, Loader2 } from 'lucide-react';
4
+ import ReactMarkdown from 'react-markdown';
5
+
6
+ // Import markdown content
7
+ import linearRegressionMd from '../docs/linear_regression.md?raw';
8
+ import cnnMd from '../docs/cnn.md?raw';
9
+
10
+ const AlgorithmDetail: React.FC = () => {
11
+ const { algorithmId } = useParams<{ algorithmId: string }>();
12
+ const navigate = useNavigate();
13
+ const [markdownContent, setMarkdownContent] = useState<string>('');
14
+ const [loading, setLoading] = useState(true);
15
+ const [error, setError] = useState<string | null>(null);
16
+
17
+ useEffect(() => {
18
+ loadMarkdownContent();
19
+ }, [algorithmId]);
20
+
21
+ const loadMarkdownContent = async () => {
22
+ try {
23
+ setLoading(true);
24
+ setError(null);
25
+
26
+ let content = '';
27
+ switch (algorithmId) {
28
+ case 'linear_regression':
29
+ content = linearRegressionMd;
30
+ break;
31
+ case 'cnn':
32
+ content = cnnMd;
33
+ break;
34
+ default:
35
+ setError('Algorithm documentation not found');
36
+ setLoading(false);
37
+ return;
38
+ }
39
+
40
+ setMarkdownContent(content);
41
+ } catch (err) {
42
+ setError('Failed to load algorithm documentation');
43
+ } finally {
44
+ setLoading(false);
45
+ }
46
+ };
47
+
48
+ const getAlgorithmInfo = () => {
49
+ switch (algorithmId) {
50
+ case 'linear_regression':
51
+ return {
52
+ name: 'Linear Regression',
53
+ category: 'Classical ML',
54
+ icon: <Brain className="h-8 w-8 text-blue-600" />,
55
+ categoryColor: 'bg-blue-100 text-blue-800'
56
+ };
57
+ case 'cnn':
58
+ return {
59
+ name: 'Convolutional Neural Network',
60
+ category: 'Deep Learning',
61
+ icon: <Cpu className="h-8 w-8 text-purple-600" />,
62
+ categoryColor: 'bg-purple-100 text-purple-800'
63
+ };
64
+ default:
65
+ return {
66
+ name: 'Unknown Algorithm',
67
+ category: 'Unknown',
68
+ icon: <Brain className="h-8 w-8 text-gray-600" />,
69
+ categoryColor: 'bg-gray-100 text-gray-800'
70
+ };
71
+ }
72
+ };
73
+
74
+ const algorithmInfo = getAlgorithmInfo();
75
+
76
+ if (loading) {
77
+ return (
78
+ <div className="max-w-4xl mx-auto">
79
+ <button
80
+ onClick={() => navigate(-1)}
81
+ className="mb-6 flex items-center text-blue-600 hover:text-blue-800 transition-colors"
82
+ >
83
+ <ArrowLeft className="h-4 w-4 mr-2" />
84
+ Back
85
+ </button>
86
+ <div className="flex justify-center items-center h-64">
87
+ <Loader2 className="h-8 w-8 animate-spin text-blue-600" />
88
+ <span className="ml-2 text-gray-600">Loading documentation...</span>
89
+ </div>
90
+ </div>
91
+ );
92
+ }
93
+
94
+ if (error) {
95
+ return (
96
+ <div className="max-w-4xl mx-auto">
97
+ <button
98
+ onClick={() => navigate(-1)}
99
+ className="mb-6 flex items-center text-blue-600 hover:text-blue-800 transition-colors"
100
+ >
101
+ <ArrowLeft className="h-4 w-4 mr-2" />
102
+ Back
103
+ </button>
104
+ <div className="bg-white rounded-lg shadow-md p-8 text-center">
105
+ <h1 className="text-2xl font-bold text-gray-900 mb-4">Documentation Not Available</h1>
106
+ <p className="text-gray-600 mb-4">{error}</p>
107
+ <button
108
+ onClick={loadMarkdownContent}
109
+ className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
110
+ >
111
+ Try Again
112
+ </button>
113
+ </div>
114
+ </div>
115
+ );
116
+ }
117
+
118
+ return (
119
+ <div className="max-w-4xl mx-auto">
120
+ <button
121
+ onClick={() => navigate(-1)}
122
+ className="mb-6 flex items-center text-blue-600 hover:text-blue-800 transition-colors"
123
+ >
124
+ <ArrowLeft className="h-4 w-4 mr-2" />
125
+ Back
126
+ </button>
127
+
128
+ {/* Algorithm Header */}
129
+ <div className="bg-white rounded-lg shadow-md p-6 mb-6">
130
+ <div className="flex items-center justify-between">
131
+ <div className="flex items-center">
132
+ {algorithmInfo.icon}
133
+ <div className="ml-4">
134
+ <h1 className="text-2xl font-bold text-gray-900">{algorithmInfo.name}</h1>
135
+ </div>
136
+ </div>
137
+ <span className={`px-3 py-1 rounded-full text-sm font-semibold ${algorithmInfo.categoryColor}`}>
138
+ {algorithmInfo.category}
139
+ </span>
140
+ </div>
141
+ </div>
142
+
143
+ {/* Markdown Content */}
144
+ <div className="bg-white rounded-lg shadow-md p-8">
145
+ <div className="prose prose-lg max-w-none">
146
+ <ReactMarkdown
147
+ components={{
148
+ h1: ({ children }) => (
149
+ <h1 className="text-3xl font-bold text-gray-900 mb-6 border-b-2 border-gray-200 pb-2">
150
+ {children}
151
+ </h1>
152
+ ),
153
+ h2: ({ children }) => (
154
+ <h2 className="text-2xl font-semibold text-gray-900 mt-8 mb-4 border-b border-gray-200 pb-2">
155
+ {children}
156
+ </h2>
157
+ ),
158
+ h3: ({ children }) => (
159
+ <h3 className="text-xl font-semibold text-gray-900 mt-6 mb-3">
160
+ {children}
161
+ </h3>
162
+ ),
163
+ h4: ({ children }) => (
164
+ <h4 className="text-lg font-semibold text-gray-900 mt-4 mb-2">
165
+ {children}
166
+ </h4>
167
+ ),
168
+ p: ({ children }) => (
169
+ <p className="text-gray-700 leading-relaxed mb-4">
170
+ {children}
171
+ </p>
172
+ ),
173
+ ul: ({ children }) => (
174
+ <ul className="list-disc list-outside text-gray-700 mb-4 space-y-2 ml-6">
175
+ {children}
176
+ </ul>
177
+ ),
178
+ ol: ({ children }) => (
179
+ <ol className="list-decimal list-outside text-gray-700 mb-4 space-y-2 ml-6">
180
+ {children}
181
+ </ol>
182
+ ),
183
+ li: ({ children }) => (
184
+ <li className="pl-2">
185
+ {children}
186
+ </li>
187
+ ),
188
+ code: ({ children, className }) => {
189
+ const isBlock = className?.includes('language-');
190
+ if (isBlock) {
191
+ return (
192
+ <pre className="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-4 overflow-x-auto">
193
+ <code className="text-sm font-mono text-gray-800">
194
+ {children}
195
+ </code>
196
+ </pre>
197
+ );
198
+ }
199
+ return (
200
+ <code className="bg-gray-100 px-2 py-1 rounded text-sm font-mono text-gray-800">
201
+ {children}
202
+ </code>
203
+ );
204
+ },
205
+ blockquote: ({ children }) => (
206
+ <blockquote className="border-l-4 border-blue-500 pl-4 italic text-gray-600 mb-4">
207
+ {children}
208
+ </blockquote>
209
+ ),
210
+ strong: ({ children }) => (
211
+ <strong className="font-semibold text-gray-900">
212
+ {children}
213
+ </strong>
214
+ ),
215
+ em: ({ children }) => (
216
+ <em className="italic text-gray-700">
217
+ {children}
218
+ </em>
219
+ ),
220
+ table: ({ children }) => (
221
+ <div className="overflow-x-auto mb-4">
222
+ <table className="min-w-full divide-y divide-gray-200">
223
+ {children}
224
+ </table>
225
+ </div>
226
+ ),
227
+ thead: ({ children }) => (
228
+ <thead className="bg-gray-50">
229
+ {children}
230
+ </thead>
231
+ ),
232
+ tbody: ({ children }) => (
233
+ <tbody className="bg-white divide-y divide-gray-200">
234
+ {children}
235
+ </tbody>
236
+ ),
237
+ tr: ({ children }) => (
238
+ <tr>
239
+ {children}
240
+ </tr>
241
+ ),
242
+ th: ({ children }) => (
243
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
244
+ {children}
245
+ </th>
246
+ ),
247
+ td: ({ children }) => (
248
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
249
+ {children}
250
+ </td>
251
+ )
252
+ }}
253
+ >
254
+ {markdownContent}
255
+ </ReactMarkdown>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ );
260
+ };
261
+
262
+ export default AlgorithmDetail;
frontend/src/pages/Dashboard.tsx ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts';
3
+ import { Loader2, TrendingUp, Brain, Cpu, MessageSquare } from 'lucide-react';
4
+ import { Link } from 'react-router-dom';
5
+ import api from '../services/api';
6
+
7
+ interface AlgorithmStat {
8
+ algorithm: string;
9
+ name: string;
10
+ count: number;
11
+ }
12
+
13
+ interface DashboardStats {
14
+ classical_ml: AlgorithmStat[];
15
+ deep_learning: AlgorithmStat[];
16
+ llms: AlgorithmStat[];
17
+ }
18
+
19
+ const Dashboard: React.FC = () => {
20
+ const [stats, setStats] = useState<DashboardStats | null>(null);
21
+ const [loading, setLoading] = useState(true);
22
+ const [error, setError] = useState<string | null>(null);
23
+
24
+ useEffect(() => {
25
+ fetchDashboardStats();
26
+ }, []);
27
+
28
+ const fetchDashboardStats = async () => {
29
+ try {
30
+ setLoading(true);
31
+ const response = await api.get('/search/dashboard-stats');
32
+ setStats(response.data);
33
+ } catch (err) {
34
+ setError('Failed to load dashboard statistics');
35
+ } finally {
36
+ setLoading(false);
37
+ }
38
+ };
39
+
40
+ if (loading) {
41
+ return (
42
+ <div className="flex justify-center items-center h-64">
43
+ <Loader2 className="h-8 w-8 animate-spin text-blue-600" />
44
+ <span className="ml-2 text-gray-600">Loading dashboard...</span>
45
+ </div>
46
+ );
47
+ }
48
+
49
+ if (error) {
50
+ return (
51
+ <div className="text-center text-red-600 p-8">
52
+ <p>{error}</p>
53
+ <button
54
+ onClick={fetchDashboardStats}
55
+ className="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
56
+ >
57
+ Retry
58
+ </button>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ if (!stats) return null;
64
+
65
+ const combinedData = [
66
+ ...stats.classical_ml.map(item => ({ ...item, category: 'Classical ML' })),
67
+ ...stats.deep_learning.map(item => ({ ...item, category: 'Deep Learning' })),
68
+ ...stats.llms.map(item => ({ ...item, category: 'LLMs' }))
69
+ ].sort((a, b) => b.count - a.count).slice(0, 12);
70
+
71
+ const categoryTotals = [
72
+ {
73
+ name: 'Classical ML',
74
+ value: stats.classical_ml.reduce((sum, item) => sum + item.count, 0),
75
+ color: '#3B82F6'
76
+ },
77
+ {
78
+ name: 'Deep Learning',
79
+ value: stats.deep_learning.reduce((sum, item) => sum + item.count, 0),
80
+ color: '#8B5CF6'
81
+ },
82
+ {
83
+ name: 'LLMs',
84
+ value: stats.llms.reduce((sum, item) => sum + item.count, 0),
85
+ color: '#10B981'
86
+ }
87
+ ];
88
+
89
+ const totalPapers = categoryTotals.reduce((sum, cat) => sum + cat.value, 0);
90
+
91
+ return (
92
+ <div className="space-y-8">
93
+ <div className="text-center">
94
+ <h1 className="text-4xl font-bold text-gray-900 mb-2">
95
+ AI Algorithms in Medical Research
96
+ </h1>
97
+ <p className="text-xl text-gray-600">
98
+ Explore the landscape of AI algorithms used in PubMed medical papers
99
+ </p>
100
+ </div>
101
+
102
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
103
+ <div className="bg-white p-6 rounded-lg shadow-lg">
104
+ <div className="flex items-center">
105
+ <TrendingUp className="h-8 w-8 text-blue-600" />
106
+ <div className="ml-4">
107
+ <p className="text-sm font-medium text-gray-600">Total Papers</p>
108
+ <p className="text-2xl font-bold text-gray-900">{totalPapers.toLocaleString()}</p>
109
+ </div>
110
+ </div>
111
+ </div>
112
+
113
+ <div className="bg-white p-6 rounded-lg shadow-lg">
114
+ <div className="flex items-center">
115
+ <Brain className="h-8 w-8 text-blue-600" />
116
+ <div className="ml-4">
117
+ <p className="text-sm font-medium text-gray-600">Classical ML Papers</p>
118
+ <p className="text-2xl font-bold text-gray-900">{categoryTotals[0].value.toLocaleString()}</p>
119
+ </div>
120
+ </div>
121
+ </div>
122
+
123
+ <div className="bg-white p-6 rounded-lg shadow-lg">
124
+ <div className="flex items-center">
125
+ <Cpu className="h-8 w-8 text-purple-600" />
126
+ <div className="ml-4">
127
+ <p className="text-sm font-medium text-gray-600">Deep Learning Papers</p>
128
+ <p className="text-2xl font-bold text-gray-900">{categoryTotals[1].value.toLocaleString()}</p>
129
+ </div>
130
+ </div>
131
+ </div>
132
+
133
+ <div className="bg-white p-6 rounded-lg shadow-lg">
134
+ <div className="flex items-center">
135
+ <MessageSquare className="h-8 w-8 text-green-600" />
136
+ <div className="ml-4">
137
+ <p className="text-sm font-medium text-gray-600">LLMs Papers</p>
138
+ <p className="text-2xl font-bold text-gray-900">{categoryTotals[2].value.toLocaleString()}</p>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ </div>
143
+
144
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
145
+ <div className="bg-white p-6 rounded-lg shadow-lg">
146
+ <h2 className="text-xl font-bold text-gray-900 mb-4">Algorithm Distribution</h2>
147
+ <ResponsiveContainer width="100%" height={300}>
148
+ <PieChart>
149
+ <Pie
150
+ data={categoryTotals}
151
+ cx="50%"
152
+ cy="50%"
153
+ labelLine={false}
154
+ label={({ name, percent }) => `${name} ${(percent * 100).toFixed(1)}%`}
155
+ outerRadius={80}
156
+ fill="#8884d8"
157
+ dataKey="value"
158
+ >
159
+ {categoryTotals.map((entry, index) => (
160
+ <Cell key={`cell-${index}`} fill={entry.color} />
161
+ ))}
162
+ </Pie>
163
+ <Tooltip />
164
+ </PieChart>
165
+ </ResponsiveContainer>
166
+ </div>
167
+
168
+ <div className="bg-white p-6 rounded-lg shadow-lg">
169
+ <h2 className="text-xl font-bold text-gray-900 mb-4">Top Algorithms by Usage</h2>
170
+ <ResponsiveContainer width="100%" height={300}>
171
+ <BarChart data={combinedData}>
172
+ <CartesianGrid strokeDasharray="3 3" />
173
+ <XAxis
174
+ dataKey="name"
175
+ angle={-45}
176
+ textAnchor="end"
177
+ height={80}
178
+ fontSize={12}
179
+ />
180
+ <YAxis />
181
+ <Tooltip />
182
+ <Legend />
183
+ <Bar
184
+ dataKey="count"
185
+ fill="#3B82F6"
186
+ name="Paper Count"
187
+ />
188
+ </BarChart>
189
+ </ResponsiveContainer>
190
+ </div>
191
+ </div>
192
+
193
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
194
+ <div className="bg-white p-6 rounded-lg shadow-lg">
195
+ <h2 className="text-xl font-bold text-gray-900 mb-4">Classical ML Algorithms</h2>
196
+ <div className="space-y-3">
197
+ {stats.classical_ml.slice(0, 8).map((algo, index) => (
198
+ <div key={algo.algorithm} className="flex justify-between items-center p-3 bg-gray-50 rounded">
199
+ <div>
200
+ <Link
201
+ to={`/algorithm/${algo.algorithm}`}
202
+ className="font-medium text-gray-900 hover:text-blue-600 transition-colors"
203
+ >
204
+ {algo.name}
205
+ </Link>
206
+ </div>
207
+ <span className="text-blue-600 font-bold">{algo.count.toLocaleString()}</span>
208
+ </div>
209
+ ))}
210
+ </div>
211
+ </div>
212
+
213
+ <div className="bg-white p-6 rounded-lg shadow-lg">
214
+ <h2 className="text-xl font-bold text-gray-900 mb-4">Deep Learning Algorithms</h2>
215
+ <div className="space-y-3">
216
+ {stats.deep_learning.slice(0, 8).map((algo, index) => (
217
+ <div key={algo.algorithm} className="flex justify-between items-center p-3 bg-gray-50 rounded">
218
+ <div>
219
+ <Link
220
+ to={`/algorithm/${algo.algorithm}`}
221
+ className="font-medium text-gray-900 hover:text-purple-600 transition-colors"
222
+ >
223
+ {algo.name}
224
+ </Link>
225
+ </div>
226
+ <span className="text-purple-600 font-bold">{algo.count.toLocaleString()}</span>
227
+ </div>
228
+ ))}
229
+ </div>
230
+ </div>
231
+
232
+ <div className="bg-white p-6 rounded-lg shadow-lg">
233
+ <h2 className="text-xl font-bold text-gray-900 mb-4">Large Language Models</h2>
234
+ <div className="space-y-3">
235
+ {stats.llms.slice(0, 8).map((algo, index) => (
236
+ <div key={algo.algorithm} className="flex justify-between items-center p-3 bg-gray-50 rounded">
237
+ <div>
238
+ <Link
239
+ to={`/algorithm/${algo.algorithm}`}
240
+ className="font-medium text-gray-900 hover:text-green-600 transition-colors"
241
+ >
242
+ {algo.name}
243
+ </Link>
244
+ </div>
245
+ <span className="text-green-600 font-bold">{algo.count.toLocaleString()}</span>
246
+ </div>
247
+ ))}
248
+ </div>
249
+ </div>
250
+ </div>
251
+ </div>
252
+ );
253
+ };
254
+
255
+ export default Dashboard;
frontend/src/pages/Roadmap.tsx ADDED
@@ -0,0 +1,865 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { BookOpen, Code, Database, Brain, Stethoscope, Shield, Wrench, Users, TrendingUp, Award, Target, Crown, ExternalLink, Clock, Star, Users2, Globe, Video, FileText, Laptop } from 'lucide-react';
3
+
4
+ interface Course {
5
+ title: string;
6
+ platform: string;
7
+ url: string;
8
+ duration: string;
9
+ level: 'Beginner' | 'Intermediate' | 'Advanced';
10
+ rating?: number;
11
+ }
12
+
13
+ interface Book {
14
+ title: string;
15
+ author: string;
16
+ url: string;
17
+ description: string;
18
+ }
19
+
20
+ interface PhaseProps {
21
+ phaseNumber: number;
22
+ title: string;
23
+ description: string;
24
+ items: {
25
+ title: string;
26
+ objective: string;
27
+ icon: React.ReactNode;
28
+ courses: Course[];
29
+ books?: Book[];
30
+ topics: string[];
31
+ practicalProjects?: string[];
32
+ estimatedTime: string;
33
+ }[];
34
+ icon: React.ReactNode;
35
+ color: string;
36
+ isLast?: boolean;
37
+ }
38
+
39
+ const Phase: React.FC<PhaseProps> = ({ phaseNumber, title, description, items, icon, color, isLast }) => {
40
+ return (
41
+ <div className="relative">
42
+ {/* Timeline line */}
43
+ {!isLast && (
44
+ <div className="absolute left-8 top-20 w-0.5 h-full bg-gray-300 z-0"></div>
45
+ )}
46
+
47
+ <div className="relative z-10 flex items-start mb-16">
48
+ {/* Phase circle indicator */}
49
+ <div className={`flex-shrink-0 w-16 h-16 rounded-full ${color} flex items-center justify-center mr-8 shadow-lg`}>
50
+ <div className="text-white font-bold text-lg">{phaseNumber}</div>
51
+ </div>
52
+
53
+ {/* Phase content */}
54
+ <div className="flex-grow">
55
+ <div className="mb-6">
56
+ <h2 className="text-2xl font-bold text-gray-900 mb-2">{title}</h2>
57
+ <p className="text-gray-600 text-lg">{description}</p>
58
+ </div>
59
+
60
+ <div className="space-y-8">
61
+ {items.map((item, index) => (
62
+ <div key={index} className="bg-gradient-to-br from-white to-gray-50 rounded-xl shadow-lg p-8 border border-gray-200 hover:shadow-2xl hover:scale-[1.02] transition-all duration-300 ml-8 group">
63
+ {/* Header with icon and title */}
64
+ <div className="flex items-center mb-6">
65
+ <div className="bg-gradient-to-br from-blue-500 to-purple-600 p-3 rounded-lg mr-4 group-hover:scale-110 transition-transform duration-300">
66
+ {item.icon}
67
+ </div>
68
+ <div className="flex-grow">
69
+ <h3 className="text-xl font-bold text-gray-900 mb-1">{item.title}</h3>
70
+ <div className="flex items-center text-sm text-gray-500">
71
+ <Clock className="h-4 w-4 mr-1" />
72
+ <span>Estimated time: {item.estimatedTime}</span>
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ {/* Objective */}
78
+ <div className="mb-6 p-4 bg-blue-50 rounded-lg border-l-4 border-blue-500">
79
+ <h4 className="text-sm font-semibold text-blue-900 mb-2 flex items-center">
80
+ <Target className="h-4 w-4 mr-2" />
81
+ Objective
82
+ </h4>
83
+ <p className="text-sm text-blue-800">{item.objective}</p>
84
+ </div>
85
+
86
+ {/* Courses */}
87
+ <div className="mb-6">
88
+ <h4 className="text-sm font-semibold text-gray-700 mb-3 flex items-center">
89
+ <Video className="h-4 w-4 mr-2" />
90
+ Recommended Courses
91
+ </h4>
92
+ <div className="grid gap-3 sm:grid-cols-2">
93
+ {item.courses.map((course, courseIndex) => (
94
+ <a
95
+ key={courseIndex}
96
+ href={course.url}
97
+ target="_blank"
98
+ rel="noopener noreferrer"
99
+ className="block p-4 bg-white rounded-lg border border-gray-200 hover:border-blue-300 hover:shadow-md transition-all duration-200 group/course"
100
+ >
101
+ <div className="flex items-start justify-between mb-2">
102
+ <h5 className="font-medium text-gray-900 text-sm group-hover/course:text-blue-600 transition-colors">{course.title}</h5>
103
+ <ExternalLink className="h-3 w-3 text-gray-400 group-hover/course:text-blue-500 flex-shrink-0 ml-2" />
104
+ </div>
105
+ <div className="flex items-center justify-between text-xs text-gray-500">
106
+ <span className="bg-gray-100 px-2 py-1 rounded">{course.platform}</span>
107
+ <div className="flex items-center space-x-2">
108
+ <span className={`px-2 py-1 rounded text-xs font-medium ${
109
+ course.level === 'Beginner' ? 'bg-green-100 text-green-700' :
110
+ course.level === 'Intermediate' ? 'bg-yellow-100 text-yellow-700' :
111
+ 'bg-red-100 text-red-700'
112
+ }`}>
113
+ {course.level}
114
+ </span>
115
+ <span>{course.duration}</span>
116
+ {course.rating && (
117
+ <div className="flex items-center">
118
+ <Star className="h-3 w-3 text-yellow-400 fill-current" />
119
+ <span className="ml-1">{course.rating}</span>
120
+ </div>
121
+ )}
122
+ </div>
123
+ </div>
124
+ </a>
125
+ ))}
126
+ </div>
127
+ </div>
128
+
129
+ {/* Books */}
130
+ {item.books && item.books.length > 0 && (
131
+ <div className="mb-6">
132
+ <h4 className="text-sm font-semibold text-gray-700 mb-3 flex items-center">
133
+ <BookOpen className="h-4 w-4 mr-2" />
134
+ Essential Reading
135
+ </h4>
136
+ <div className="space-y-3">
137
+ {item.books.map((book, bookIndex) => (
138
+ <a
139
+ key={bookIndex}
140
+ href={book.url}
141
+ target="_blank"
142
+ rel="noopener noreferrer"
143
+ className="block p-4 bg-orange-50 rounded-lg border border-orange-200 hover:border-orange-300 hover:shadow-md transition-all duration-200 group/book"
144
+ >
145
+ <div className="flex items-start justify-between mb-2">
146
+ <div>
147
+ <h5 className="font-medium text-gray-900 text-sm group-hover/book:text-orange-600 transition-colors">{book.title}</h5>
148
+ <p className="text-xs text-gray-600">by {book.author}</p>
149
+ </div>
150
+ <ExternalLink className="h-3 w-3 text-gray-400 group-hover/book:text-orange-500 flex-shrink-0 ml-2" />
151
+ </div>
152
+ <p className="text-xs text-gray-600">{book.description}</p>
153
+ </a>
154
+ ))}
155
+ </div>
156
+ </div>
157
+ )}
158
+
159
+ {/* Practical Projects */}
160
+ {item.practicalProjects && item.practicalProjects.length > 0 && (
161
+ <div className="mb-6">
162
+ <h4 className="text-sm font-semibold text-gray-700 mb-3 flex items-center">
163
+ <Laptop className="h-4 w-4 mr-2" />
164
+ Hands-on Projects
165
+ </h4>
166
+ <div className="bg-green-50 rounded-lg p-4">
167
+ <ul className="space-y-2">
168
+ {item.practicalProjects.map((project, projectIndex) => (
169
+ <li key={projectIndex} className="flex items-start text-sm text-green-800">
170
+ <div className="w-2 h-2 bg-green-500 rounded-full mt-2 mr-3 flex-shrink-0"></div>
171
+ {project}
172
+ </li>
173
+ ))}
174
+ </ul>
175
+ </div>
176
+ </div>
177
+ )}
178
+
179
+ {/* Topics */}
180
+ <div>
181
+ <h4 className="text-sm font-semibold text-gray-700 mb-3 flex items-center">
182
+ <FileText className="h-4 w-4 mr-2" />
183
+ Key Topics to Master
184
+ </h4>
185
+ <div className="flex flex-wrap gap-2">
186
+ {item.topics.map((topic, topicIndex) => (
187
+ <span
188
+ key={topicIndex}
189
+ className="px-3 py-1 bg-purple-100 text-purple-700 rounded-full text-xs font-medium hover:bg-purple-200 transition-colors"
190
+ >
191
+ {topic}
192
+ </span>
193
+ ))}
194
+ </div>
195
+ </div>
196
+ </div>
197
+ ))}
198
+ </div>
199
+ </div>
200
+ </div>
201
+ </div>
202
+ );
203
+ };
204
+
205
+ const Roadmap: React.FC = () => {
206
+ const phases = [
207
+ {
208
+ phaseNumber: 1,
209
+ title: "Foundational Knowledge",
210
+ description: "Build essential understanding of AI concepts and programming skills",
211
+ icon: <BookOpen className="h-8 w-8 text-white" />,
212
+ color: "bg-blue-500",
213
+ items: [
214
+ {
215
+ title: "Introduction to AI",
216
+ objective: "Understand the basics of AI, its history, and key concepts.",
217
+ icon: <Brain className="h-6 w-6 text-white" />,
218
+ estimatedTime: "4-6 weeks",
219
+ courses: [
220
+ {
221
+ title: "AI For Everyone",
222
+ platform: "Coursera",
223
+ url: "https://www.coursera.org/learn/ai-for-everyone",
224
+ duration: "4 weeks",
225
+ level: "Beginner",
226
+ rating: 4.8
227
+ },
228
+ {
229
+ title: "Introduction to Artificial Intelligence",
230
+ platform: "edX MIT",
231
+ url: "https://www.edx.org/course/introduction-to-artificial-intelligence-ai",
232
+ duration: "5 weeks",
233
+ level: "Beginner",
234
+ rating: 4.6
235
+ },
236
+ {
237
+ title: "AI Fundamentals",
238
+ platform: "IBM Cognitive Class",
239
+ url: "https://cognitiveclass.ai/courses/artificial-intelligence-fundamentals",
240
+ duration: "3 weeks",
241
+ level: "Beginner"
242
+ }
243
+ ],
244
+ books: [
245
+ {
246
+ title: "Artificial Intelligence: A Guide for Thinking Humans",
247
+ author: "Melanie Mitchell",
248
+ url: "https://www.amazon.com/Artificial-Intelligence-Guide-Thinking-Humans/dp/0374257833",
249
+ description: "An accessible introduction to AI concepts without technical jargon"
250
+ },
251
+ {
252
+ title: "Human Compatible: Artificial Intelligence and the Problem of Control",
253
+ author: "Stuart Russell",
254
+ url: "https://www.amazon.com/Human-Compatible-Artificial-Intelligence-Problem/dp/0525558616",
255
+ description: "Explores the future of AI and its implications for humanity"
256
+ }
257
+ ],
258
+ topics: ["AI vs. ML vs. Deep Learning", "History of AI", "Types of AI", "AI Ethics", "Current Applications", "Future Trends"]
259
+ },
260
+ {
261
+ title: "Basic Programming Skills",
262
+ objective: "Gain proficiency in Python, the most commonly used programming language in AI.",
263
+ icon: <Code className="h-6 w-6 text-white" />,
264
+ estimatedTime: "8-12 weeks",
265
+ courses: [
266
+ {
267
+ title: "Python for Everybody Specialization",
268
+ platform: "Coursera",
269
+ url: "https://www.coursera.org/specializations/python",
270
+ duration: "8 months",
271
+ level: "Beginner",
272
+ rating: 4.8
273
+ },
274
+ {
275
+ title: "Learn Python 3",
276
+ platform: "Codecademy",
277
+ url: "https://www.codecademy.com/learn/learn-python-3",
278
+ duration: "25 hours",
279
+ level: "Beginner",
280
+ rating: 4.7
281
+ },
282
+ {
283
+ title: "Python Programming MOOC",
284
+ platform: "University of Helsinki",
285
+ url: "https://programming-23.mooc.fi/",
286
+ duration: "14 weeks",
287
+ level: "Beginner"
288
+ },
289
+ {
290
+ title: "CS50's Introduction to Programming with Python",
291
+ platform: "Harvard edX",
292
+ url: "https://www.edx.org/course/cs50s-introduction-to-programming-with-python",
293
+ duration: "10 weeks",
294
+ level: "Beginner",
295
+ rating: 4.9
296
+ }
297
+ ],
298
+ books: [
299
+ {
300
+ title: "Python Crash Course",
301
+ author: "Eric Matthes",
302
+ url: "https://www.amazon.com/Python-Crash-Course-Hands-Project-Based/dp/1593279280",
303
+ description: "A hands-on, project-based introduction to programming"
304
+ }
305
+ ],
306
+ practicalProjects: [
307
+ "Build a simple calculator application",
308
+ "Create a weather data scraper using APIs",
309
+ "Develop a basic web scraper with BeautifulSoup",
310
+ "Make a simple data visualization with matplotlib"
311
+ ],
312
+ topics: ["Python Syntax", "Data Structures", "Functions & Classes", "NumPy", "Pandas", "File Handling", "Error Handling", "Libraries & Modules"]
313
+ },
314
+ {
315
+ title: "Data Literacy",
316
+ objective: "Learn about data types, collection, preprocessing, and analysis.",
317
+ icon: <Database className="h-6 w-6 text-white" />,
318
+ estimatedTime: "6-8 weeks",
319
+ courses: [
320
+ {
321
+ title: "Data Science Fundamentals",
322
+ platform: "DataCamp",
323
+ url: "https://www.datacamp.com/tracks/data-scientist-with-python",
324
+ duration: "87 hours",
325
+ level: "Beginner",
326
+ rating: 4.6
327
+ },
328
+ {
329
+ title: "Introduction to Data Science in Python",
330
+ platform: "Coursera (University of Michigan)",
331
+ url: "https://www.coursera.org/learn/python-data-analysis",
332
+ duration: "4 weeks",
333
+ level: "Intermediate",
334
+ rating: 4.5
335
+ },
336
+ {
337
+ title: "Data Analysis with Python",
338
+ platform: "freeCodeCamp",
339
+ url: "https://www.freecodecamp.org/learn/data-analysis-with-python/",
340
+ duration: "300 hours",
341
+ level: "Intermediate"
342
+ }
343
+ ],
344
+ books: [
345
+ {
346
+ title: "Python for Data Analysis",
347
+ author: "Wes McKinney",
348
+ url: "https://www.amazon.com/Python-Data-Analysis-Wrangling-IPython/dp/1491957662",
349
+ description: "Essential guide to data manipulation and analysis with pandas"
350
+ }
351
+ ],
352
+ practicalProjects: [
353
+ "Analyze a real dataset from Kaggle",
354
+ "Create comprehensive data visualizations",
355
+ "Build an interactive dashboard with Streamlit",
356
+ "Perform exploratory data analysis on healthcare data"
357
+ ],
358
+ topics: ["Data Types", "Data Cleaning", "Exploratory Data Analysis", "Statistical Analysis", "Matplotlib", "Seaborn", "Plotly", "Data Ethics"]
359
+ }
360
+ ]
361
+ },
362
+ {
363
+ phaseNumber: 2,
364
+ title: "Core AI Concepts",
365
+ description: "Master fundamental machine learning and deep learning techniques",
366
+ icon: <Brain className="h-8 w-8 text-white" />,
367
+ color: "bg-purple-500",
368
+ items: [
369
+ {
370
+ title: "Machine Learning Basics",
371
+ objective: "Study the fundamentals of machine learning algorithms and techniques.",
372
+ icon: <TrendingUp className="h-6 w-6 text-white" />,
373
+ estimatedTime: "10-12 weeks",
374
+ courses: [
375
+ {
376
+ title: "Machine Learning Course",
377
+ platform: "Coursera (Stanford)",
378
+ url: "https://www.coursera.org/learn/machine-learning",
379
+ duration: "11 weeks",
380
+ level: "Intermediate",
381
+ rating: 4.9
382
+ },
383
+ {
384
+ title: "Scikit-Learn Course",
385
+ platform: "DataCamp",
386
+ url: "https://www.datacamp.com/courses/supervised-learning-with-scikit-learn",
387
+ duration: "4 hours",
388
+ level: "Intermediate",
389
+ rating: 4.7
390
+ },
391
+ {
392
+ title: "Machine Learning A-Z",
393
+ platform: "Udemy",
394
+ url: "https://www.udemy.com/course/machinelearning/",
395
+ duration: "44 hours",
396
+ level: "Beginner",
397
+ rating: 4.5
398
+ },
399
+ {
400
+ title: "Introduction to Machine Learning",
401
+ platform: "MIT OpenCourseWare",
402
+ url: "https://ocw.mit.edu/courses/6-0002-introduction-to-computational-thinking-and-data-science-fall-2016/",
403
+ duration: "12 weeks",
404
+ level: "Intermediate"
405
+ }
406
+ ],
407
+ books: [
408
+ {
409
+ title: "Hands-On Machine Learning",
410
+ author: "Aurélien Géron",
411
+ url: "https://www.amazon.com/Hands-Machine-Learning-Scikit-Learn-TensorFlow/dp/1492032646",
412
+ description: "Practical approach to ML with Python, scikit-learn, and TensorFlow"
413
+ },
414
+ {
415
+ title: "Pattern Recognition and Machine Learning",
416
+ author: "Christopher Bishop",
417
+ url: "https://www.amazon.com/Pattern-Recognition-Learning-Information-Statistics/dp/0387310738",
418
+ description: "Comprehensive theoretical foundation of machine learning"
419
+ }
420
+ ],
421
+ practicalProjects: [
422
+ "Build a house price prediction model",
423
+ "Create a customer segmentation analysis",
424
+ "Develop a recommendation system",
425
+ "Implement classification for medical diagnosis"
426
+ ],
427
+ topics: ["Supervised Learning", "Unsupervised Learning", "Regression", "Classification", "Clustering", "Model Evaluation", "Cross-Validation", "Feature Engineering"]
428
+ },
429
+ {
430
+ title: "Deep Learning",
431
+ objective: "Master neural networks and their applications in various domains.",
432
+ icon: <Brain className="h-6 w-6 text-white" />,
433
+ estimatedTime: "12-16 weeks",
434
+ courses: [
435
+ {
436
+ title: "Deep Learning Specialization",
437
+ platform: "Coursera (deeplearning.ai)",
438
+ url: "https://www.coursera.org/specializations/deep-learning",
439
+ duration: "4 months",
440
+ level: "Intermediate",
441
+ rating: 4.8
442
+ },
443
+ {
444
+ title: "CS231n: Convolutional Neural Networks",
445
+ platform: "Stanford Online",
446
+ url: "http://cs231n.stanford.edu/",
447
+ duration: "16 weeks",
448
+ level: "Advanced"
449
+ },
450
+ {
451
+ title: "Fast.ai Practical Deep Learning",
452
+ platform: "fast.ai",
453
+ url: "https://course.fast.ai/",
454
+ duration: "7 weeks",
455
+ level: "Intermediate",
456
+ rating: 4.9
457
+ },
458
+ {
459
+ title: "PyTorch for Deep Learning",
460
+ platform: "Udacity",
461
+ url: "https://www.udacity.com/course/deep-learning-pytorch--ud188",
462
+ duration: "2 months",
463
+ level: "Intermediate"
464
+ }
465
+ ],
466
+ books: [
467
+ {
468
+ title: "Deep Learning",
469
+ author: "Ian Goodfellow, Yoshua Bengio, Aaron Courville",
470
+ url: "https://www.amazon.com/Deep-Learning-Ian-Goodfellow/dp/0262035618",
471
+ description: "The definitive textbook on deep learning theory and practice"
472
+ }
473
+ ],
474
+ practicalProjects: [
475
+ "Build an image classifier for medical images",
476
+ "Create a neural network for time series forecasting",
477
+ "Develop a generative model for synthetic data",
478
+ "Implement transfer learning for medical imaging"
479
+ ],
480
+ topics: ["Neural Networks", "CNNs", "RNNs", "LSTMs", "GANs", "Transfer Learning", "Optimization", "Regularization", "TensorFlow", "PyTorch"]
481
+ },
482
+ {
483
+ title: "Natural Language Processing",
484
+ objective: "Learn to process and analyze textual data, especially medical literature.",
485
+ icon: <FileText className="h-6 w-6 text-white" />,
486
+ estimatedTime: "8-10 weeks",
487
+ courses: [
488
+ {
489
+ title: "Natural Language Processing Specialization",
490
+ platform: "Coursera (deeplearning.ai)",
491
+ url: "https://www.coursera.org/specializations/natural-language-processing",
492
+ duration: "4 months",
493
+ level: "Intermediate",
494
+ rating: 4.6
495
+ },
496
+ {
497
+ title: "CS224n: Natural Language Processing with Deep Learning",
498
+ platform: "Stanford Online",
499
+ url: "http://web.stanford.edu/class/cs224n/",
500
+ duration: "10 weeks",
501
+ level: "Advanced"
502
+ },
503
+ {
504
+ title: "NLP with Python",
505
+ platform: "DataCamp",
506
+ url: "https://www.datacamp.com/tracks/natural-language-processing-in-python",
507
+ duration: "17 hours",
508
+ level: "Intermediate",
509
+ rating: 4.5
510
+ }
511
+ ],
512
+ books: [
513
+ {
514
+ title: "Natural Language Processing with Python",
515
+ author: "Steven Bird, Ewan Klein, Edward Loper",
516
+ url: "https://www.amazon.com/Natural-Language-Processing-Python-Analyzing/dp/0596516495",
517
+ description: "Practical guide to NLP using NLTK and Python"
518
+ }
519
+ ],
520
+ practicalProjects: [
521
+ "Build a medical text classifier",
522
+ "Create a clinical notes summarizer",
523
+ "Develop sentiment analysis for patient feedback",
524
+ "Implement named entity recognition for medical terms"
525
+ ],
526
+ topics: ["Text Preprocessing", "Tokenization", "Word Embeddings", "Transformers", "BERT", "Sentiment Analysis", "Named Entity Recognition", "Language Models"]
527
+ }
528
+ ]
529
+ },
530
+ {
531
+ phaseNumber: 3,
532
+ title: "AI in Healthcare",
533
+ description: "Apply AI knowledge specifically to healthcare and medical applications",
534
+ icon: <Stethoscope className="h-8 w-8 text-white" />,
535
+ color: "bg-green-500",
536
+ items: [
537
+ {
538
+ title: "Healthcare Data Standards",
539
+ objective: "Master healthcare data formats and interoperability standards.",
540
+ icon: <Database className="h-6 w-6 text-white" />,
541
+ estimatedTime: "6-8 weeks",
542
+ courses: [
543
+ {
544
+ title: "Health Informatics on FHIR",
545
+ platform: "Coursera (UC Davis)",
546
+ url: "https://www.coursera.org/learn/fhir",
547
+ duration: "4 weeks",
548
+ level: "Intermediate",
549
+ rating: 4.5
550
+ },
551
+ {
552
+ title: "Healthcare Data Models and APIs",
553
+ platform: "edX",
554
+ url: "https://www.edx.org/course/healthcare-data-models-and-apis",
555
+ duration: "6 weeks",
556
+ level: "Intermediate"
557
+ },
558
+ {
559
+ title: "DICOM and Medical Imaging",
560
+ platform: "RSNA",
561
+ url: "https://www.rsna.org/education",
562
+ duration: "Self-paced",
563
+ level: "Intermediate"
564
+ }
565
+ ],
566
+ books: [
567
+ {
568
+ title: "Healthcare Information Systems",
569
+ author: "Marion J. Ball",
570
+ url: "https://www.amazon.com/Healthcare-Information-Systems-Marion-Ball/dp/0387403299",
571
+ description: "Comprehensive guide to healthcare IT systems and standards"
572
+ }
573
+ ],
574
+ practicalProjects: [
575
+ "Parse and analyze FHIR resources",
576
+ "Build a DICOM image viewer",
577
+ "Create an HL7 message processor",
578
+ "Develop healthcare data pipeline"
579
+ ],
580
+ topics: ["HL7", "FHIR", "DICOM", "EHR Systems", "Healthcare APIs", "Data Interoperability", "Medical Coding", "Healthcare Databases"]
581
+ },
582
+ {
583
+ title: "AI Applications in Medicine",
584
+ objective: "Study and implement AI solutions for specific medical domains.",
585
+ icon: <Stethoscope className="h-6 w-6 text-white" />,
586
+ estimatedTime: "10-12 weeks",
587
+ courses: [
588
+ {
589
+ title: "AI for Medical Diagnosis",
590
+ platform: "Coursera (deeplearning.ai)",
591
+ url: "https://www.coursera.org/learn/ai-for-medical-diagnosis",
592
+ duration: "3 weeks",
593
+ level: "Intermediate",
594
+ rating: 4.7
595
+ },
596
+ {
597
+ title: "Medical Image Analysis",
598
+ platform: "MIT OpenCourseWare",
599
+ url: "https://ocw.mit.edu/courses/health-sciences-and-technology/",
600
+ duration: "12 weeks",
601
+ level: "Advanced"
602
+ },
603
+ {
604
+ title: "Clinical Data Science",
605
+ platform: "Harvard T.H. Chan School",
606
+ url: "https://www.hsph.harvard.edu/biostatistics/",
607
+ duration: "8 weeks",
608
+ level: "Advanced"
609
+ }
610
+ ],
611
+ books: [
612
+ {
613
+ title: "Artificial Intelligence in Medicine",
614
+ author: "Peter Lucas, Arie Hasman",
615
+ url: "https://www.amazon.com/Artificial-Intelligence-Medicine-Peter-Lucas/dp/0444502753",
616
+ description: "Comprehensive overview of AI applications in healthcare"
617
+ }
618
+ ],
619
+ practicalProjects: [
620
+ "Build a medical image classification system",
621
+ "Create a clinical decision support tool",
622
+ "Develop a drug discovery pipeline",
623
+ "Implement predictive analytics for patient outcomes"
624
+ ],
625
+ topics: ["Medical Imaging AI", "Clinical Decision Support", "Genomics", "Drug Discovery", "Predictive Analytics", "Personalized Medicine", "Telemedicine", "Robotic Surgery"]
626
+ },
627
+ {
628
+ title: "Healthcare AI Ethics & Regulation",
629
+ objective: "Navigate ethical and regulatory challenges in healthcare AI.",
630
+ icon: <Shield className="h-6 w-6 text-white" />,
631
+ estimatedTime: "4-6 weeks",
632
+ courses: [
633
+ {
634
+ title: "AI in Healthcare Ethics",
635
+ platform: "Stanford Medicine",
636
+ url: "https://med.stanford.edu/aiethics.html",
637
+ duration: "4 weeks",
638
+ level: "Intermediate"
639
+ },
640
+ {
641
+ title: "FDA Regulation of AI/ML",
642
+ platform: "FDA",
643
+ url: "https://www.fda.gov/medical-devices/software-medical-device-samd/artificial-intelligence-and-machine-learning-software-medical-device",
644
+ duration: "Self-paced",
645
+ level: "Intermediate"
646
+ }
647
+ ],
648
+ books: [
649
+ {
650
+ title: "The Ethical Algorithm",
651
+ author: "Michael Kearns, Aaron Roth",
652
+ url: "https://www.amazon.com/Ethical-Algorithm-Science-Socially-Design/dp/0190948205",
653
+ description: "Framework for designing ethical AI systems"
654
+ }
655
+ ],
656
+ practicalProjects: [
657
+ "Conduct bias analysis in medical AI models",
658
+ "Design privacy-preserving healthcare AI",
659
+ "Create AI governance framework",
660
+ "Develop explainable AI for medical decisions"
661
+ ],
662
+ topics: ["AI Ethics", "HIPAA Compliance", "FDA Regulations", "Bias Detection", "Explainable AI", "Privacy Protection", "Algorithmic Fairness", "Regulatory Compliance"]
663
+ }
664
+ ]
665
+ },
666
+ {
667
+ phaseNumber: 4,
668
+ title: "Practical Experience",
669
+ description: "Gain hands-on experience with real-world AI projects",
670
+ icon: <Wrench className="h-8 w-8 text-white" />,
671
+ color: "bg-orange-500",
672
+ items: [
673
+ {
674
+ title: "Healthcare AI Projects",
675
+ objective: "Build real-world AI solutions for healthcare challenges.",
676
+ icon: <Laptop className="h-6 w-6 text-white" />,
677
+ estimatedTime: "12-16 weeks",
678
+ courses: [
679
+ {
680
+ title: "Applied Data Science Capstone",
681
+ platform: "Coursera (IBM)",
682
+ url: "https://www.coursera.org/learn/applied-data-science-capstone",
683
+ duration: "6 weeks",
684
+ level: "Advanced",
685
+ rating: 4.4
686
+ },
687
+ {
688
+ title: "Kaggle Learn",
689
+ platform: "Kaggle",
690
+ url: "https://www.kaggle.com/learn",
691
+ duration: "Self-paced",
692
+ level: "Intermediate"
693
+ }
694
+ ],
695
+ practicalProjects: [
696
+ "Medical image analysis with CNNs",
697
+ "Clinical trial outcome prediction",
698
+ "Drug-drug interaction detection",
699
+ "Electronic health record analysis",
700
+ "Medical chatbot development"
701
+ ],
702
+ topics: ["Project Management", "Version Control", "Model Deployment", "Cloud Platforms", "API Development", "Database Management", "Testing", "Documentation"]
703
+ },
704
+ {
705
+ title: "Professional Development",
706
+ objective: "Build network and stay current with healthcare AI trends.",
707
+ icon: <Users2 className="h-6 w-6 text-white" />,
708
+ estimatedTime: "Ongoing",
709
+ courses: [
710
+ {
711
+ title: "Healthcare AI Leadership",
712
+ platform: "MIT xPRO",
713
+ url: "https://learn-xpro.mit.edu/",
714
+ duration: "8 weeks",
715
+ level: "Advanced"
716
+ }
717
+ ],
718
+ practicalProjects: [
719
+ "Join AMIA and attend conferences",
720
+ "Contribute to open-source healthcare AI projects",
721
+ "Publish research papers",
722
+ "Present at healthcare AI meetups"
723
+ ],
724
+ topics: ["Professional Networks", "Research Publications", "Conference Presentations", "Open Source Contribution", "Mentorship", "Industry Trends"]
725
+ }
726
+ ]
727
+ },
728
+ {
729
+ phaseNumber: 5,
730
+ title: "Advanced Topics and Specialization",
731
+ description: "Explore cutting-edge research and develop specialized expertise",
732
+ icon: <TrendingUp className="h-8 w-8 text-white" />,
733
+ color: "bg-red-500",
734
+ items: [
735
+ {
736
+ title: "Advanced AI Research",
737
+ objective: "Master cutting-edge AI techniques and research methodologies.",
738
+ icon: <Award className="h-6 w-6 text-white" />,
739
+ estimatedTime: "16-20 weeks",
740
+ courses: [
741
+ {
742
+ title: "Reinforcement Learning Specialization",
743
+ platform: "Coursera (University of Alberta)",
744
+ url: "https://www.coursera.org/specializations/reinforcement-learning",
745
+ duration: "4 months",
746
+ level: "Advanced",
747
+ rating: 4.7
748
+ },
749
+ {
750
+ title: "Explainable AI",
751
+ platform: "MIT xPRO",
752
+ url: "https://learn-xpro.mit.edu/artificial-intelligence",
753
+ duration: "8 weeks",
754
+ level: "Advanced"
755
+ }
756
+ ],
757
+ topics: ["Reinforcement Learning", "GANs", "Explainable AI", "AutoML", "Federated Learning", "Graph Neural Networks", "Meta-Learning", "Research Methods"]
758
+ },
759
+ {
760
+ title: "Healthcare Specialization",
761
+ objective: "Develop deep expertise in a specific healthcare AI domain.",
762
+ icon: <Target className="h-6 w-6 text-white" />,
763
+ estimatedTime: "6+ months",
764
+ courses: [
765
+ {
766
+ title: "Genomics Data Science",
767
+ platform: "Coursera (Johns Hopkins)",
768
+ url: "https://www.coursera.org/specializations/genomic-data-science",
769
+ duration: "6 months",
770
+ level: "Advanced",
771
+ rating: 4.5
772
+ }
773
+ ],
774
+ topics: ["Medical Imaging", "Genomics", "Clinical Decision Support", "Drug Discovery", "Precision Medicine", "Digital Therapeutics", "Wearables", "Telemedicine"]
775
+ }
776
+ ]
777
+ },
778
+ {
779
+ phaseNumber: 6,
780
+ title: "Implementation and Leadership",
781
+ description: "Lead AI initiatives and drive adoption in healthcare organizations",
782
+ icon: <Crown className="h-8 w-8 text-white" />,
783
+ color: "bg-indigo-500",
784
+ items: [
785
+ {
786
+ title: "Clinical Implementation",
787
+ objective: "Lead successful AI integration in healthcare organizations.",
788
+ icon: <Wrench className="h-6 w-6 text-white" />,
789
+ estimatedTime: "6+ months",
790
+ courses: [
791
+ {
792
+ title: "Healthcare Innovation and Entrepreneurship",
793
+ platform: "Harvard Business School Online",
794
+ url: "https://online.hbs.edu/courses/healthcare-innovation/",
795
+ duration: "8 weeks",
796
+ level: "Advanced"
797
+ }
798
+ ],
799
+ topics: ["Change Management", "Workflow Integration", "ROI Analysis", "Quality Assurance", "Risk Management", "Stakeholder Engagement", "Pilot Studies", "Scale-up Strategies"]
800
+ },
801
+ {
802
+ title: "AI Leadership & Strategy",
803
+ objective: "Drive organizational AI strategy and policy development.",
804
+ icon: <Crown className="h-6 w-6 text-white" />,
805
+ estimatedTime: "Ongoing",
806
+ courses: [
807
+ {
808
+ title: "AI Strategy and Leadership",
809
+ platform: "MIT Sloan",
810
+ url: "https://executive.mit.edu/openenrollment/program/artificial_intelligence_strategy_and_leadership/",
811
+ duration: "3 days",
812
+ level: "Executive"
813
+ }
814
+ ],
815
+ topics: ["Strategic Planning", "Policy Development", "Team Leadership", "Budget Management", "Regulatory Navigation", "Public Speaking", "Grant Writing", "Board Presentations"]
816
+ }
817
+ ]
818
+ }
819
+ ];
820
+
821
+ return (
822
+ <div className="max-w-7xl mx-auto">
823
+ <div className="text-center mb-12">
824
+ <h1 className="text-4xl font-bold text-gray-900 mb-4">AI Learning Roadmap</h1>
825
+ <p className="text-xl text-gray-600 max-w-3xl mx-auto">
826
+ A comprehensive guide to mastering artificial intelligence with a focus on healthcare applications.
827
+ Follow this structured path from foundational concepts to advanced implementation and leadership.
828
+ </p>
829
+ </div>
830
+
831
+ <div className="mb-8">
832
+ <div className="bg-blue-50 border border-blue-200 rounded-lg p-6">
833
+ <div className="flex items-center mb-3">
834
+ <Target className="h-6 w-6 text-blue-600 mr-2" />
835
+ <h3 className="text-lg font-semibold text-blue-900">Learning Journey Overview</h3>
836
+ </div>
837
+ <p className="text-blue-800">
838
+ This roadmap is designed as a progressive learning journey spanning 6 phases. Each phase builds upon the previous one,
839
+ taking you from AI fundamentals to becoming a leader in healthcare AI implementation. Expect to spend 6-12 months on each phase,
840
+ depending on your background and time commitment.
841
+ </p>
842
+ </div>
843
+ </div>
844
+
845
+ <div className="relative">
846
+ {phases.map((phase, index) => (
847
+ <Phase key={phase.phaseNumber} {...phase} isLast={index === phases.length - 1} />
848
+ ))}
849
+ </div>
850
+
851
+ <div className="mt-12 text-center">
852
+ <div className="bg-gradient-to-r from-gray-50 to-gray-100 rounded-lg p-8">
853
+ <Award className="h-12 w-12 text-gray-600 mx-auto mb-4" />
854
+ <h3 className="text-2xl font-bold text-gray-900 mb-4">Ready to Begin Your Journey?</h3>
855
+ <p className="text-gray-700 max-w-2xl mx-auto">
856
+ Remember, this roadmap is a guide, not a rigid prescription. Adapt it to your specific interests,
857
+ background, and career goals. The key is consistent learning and practical application of knowledge.
858
+ </p>
859
+ </div>
860
+ </div>
861
+ </div>
862
+ );
863
+ };
864
+
865
+ export default Roadmap;
frontend/src/pages/Search.tsx ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { Search as SearchIcon, ExternalLink, FileText, Loader2 } from 'lucide-react';
3
+ import api from '../services/api';
4
+ import AlgorithmCard from '../components/AlgorithmCard';
5
+
6
+ interface SearchResult {
7
+ algorithm: string;
8
+ name: string;
9
+ category: string;
10
+ description: string;
11
+ count: number;
12
+ sampleIds: string[];
13
+ }
14
+
15
+ interface SearchResponse {
16
+ problem: string;
17
+ totalAlgorithms: number;
18
+ results: SearchResult[];
19
+ allResults: SearchResult[];
20
+ }
21
+
22
+ const Search: React.FC = () => {
23
+ const [problem, setProblem] = useState('');
24
+ const [results, setResults] = useState<SearchResponse | null>(null);
25
+ const [loading, setLoading] = useState(false);
26
+ const [error, setError] = useState<string | null>(null);
27
+
28
+ const handleSearch = async (e: React.FormEvent) => {
29
+ e.preventDefault();
30
+ if (!problem.trim()) return;
31
+
32
+ try {
33
+ setLoading(true);
34
+ setError(null);
35
+ const response = await api.post('/search/problem', { problem: problem.trim() });
36
+ setResults(response.data);
37
+ } catch (err) {
38
+ setError('Failed to search for algorithms. Please try again.');
39
+ } finally {
40
+ setLoading(false);
41
+ }
42
+ };
43
+
44
+ const handleSeeMorePapers = async (algorithm: string) => {
45
+ try {
46
+ const response = await api.get('/search/pubmed-link', {
47
+ params: { problem, algorithm }
48
+ });
49
+ window.open(response.data.url, '_blank');
50
+ } catch (err) {
51
+ console.error('Failed to generate PubMed link:', err);
52
+ }
53
+ };
54
+
55
+ return (
56
+ <div className="max-w-6xl mx-auto space-y-8">
57
+ <div className="text-center">
58
+ <h1 className="text-4xl font-bold text-gray-900 mb-2">
59
+ Algorithm Search
60
+ </h1>
61
+ <p className="text-xl text-gray-600">
62
+ Search for AI algorithms used in specific medical problems
63
+ </p>
64
+ </div>
65
+
66
+ <div className="bg-white p-6 rounded-lg shadow-lg">
67
+ <form onSubmit={handleSearch} className="space-y-4">
68
+ <div>
69
+ <label htmlFor="problem" className="block text-sm font-medium text-gray-700 mb-2">
70
+ Medical Problem or Condition
71
+ </label>
72
+ <div className="relative">
73
+ <input
74
+ type="text"
75
+ id="problem"
76
+ value={problem}
77
+ onChange={(e) => setProblem(e.target.value)}
78
+ placeholder="e.g., breast cancer detection, diabetes prediction, alzheimer's disease"
79
+ className="w-full px-4 py-3 pl-10 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
80
+ disabled={loading}
81
+ />
82
+ <SearchIcon className="absolute left-3 top-3.5 h-5 w-5 text-gray-400" />
83
+ </div>
84
+ </div>
85
+ <button
86
+ type="submit"
87
+ disabled={loading || !problem.trim()}
88
+ className="w-full bg-blue-600 text-white py-3 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
89
+ >
90
+ {loading ? (
91
+ <>
92
+ <Loader2 className="animate-spin h-5 w-5 mr-2" />
93
+ Searching...
94
+ </>
95
+ ) : (
96
+ <>
97
+ <SearchIcon className="h-5 w-5 mr-2" />
98
+ Search Algorithms
99
+ </>
100
+ )}
101
+ </button>
102
+ </form>
103
+ </div>
104
+
105
+ {error && (
106
+ <div className="bg-red-50 border border-red-200 rounded-md p-4">
107
+ <p className="text-red-800">{error}</p>
108
+ </div>
109
+ )}
110
+
111
+ {results && (
112
+ <div className="space-y-6">
113
+ <div className="bg-white p-6 rounded-lg shadow-lg">
114
+ <h2 className="text-2xl font-bold text-gray-900 mb-2">
115
+ Results for "{results.problem}"
116
+ </h2>
117
+ <p className="text-gray-600">
118
+ Found {results.results.length} algorithms with published research
119
+ </p>
120
+ </div>
121
+
122
+ {results.results.length > 0 ? (
123
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 auto-rows-fr">
124
+ {results.results.map((result) => (
125
+ <AlgorithmCard
126
+ key={result.algorithm}
127
+ algorithm={result}
128
+ problem={results.problem}
129
+ onSeeMorePapers={handleSeeMorePapers}
130
+ />
131
+ ))}
132
+ </div>
133
+ ) : (
134
+ <div className="bg-gray-50 p-8 rounded-lg text-center">
135
+ <FileText className="h-12 w-12 text-gray-400 mx-auto mb-4" />
136
+ <h3 className="text-lg font-medium text-gray-900 mb-2">No Results Found</h3>
137
+ <p className="text-gray-600">
138
+ No algorithms found for this medical problem. Try searching with different terms.
139
+ </p>
140
+ </div>
141
+ )}
142
+
143
+ {results.allResults.some(r => r.count === 0) && (
144
+ <div className="bg-white p-6 rounded-lg shadow-lg">
145
+ <h3 className="text-lg font-medium text-gray-900 mb-4">
146
+ Algorithms with No Results
147
+ </h3>
148
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-2">
149
+ {results.allResults
150
+ .filter(r => r.count === 0)
151
+ .map(result => (
152
+ <span
153
+ key={result.algorithm}
154
+ className="px-3 py-1 bg-gray-100 text-gray-600 rounded-full text-sm"
155
+ >
156
+ {result.name}
157
+ </span>
158
+ ))}
159
+ </div>
160
+ </div>
161
+ )}
162
+ </div>
163
+ )}
164
+ </div>
165
+ );
166
+ };
167
+
168
+ export default Search;
frontend/src/pages/Timeline.tsx ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
3
+ import { Loader2, Calendar, TrendingUp, Database, Clock } from 'lucide-react';
4
+ import api from '../services/api';
5
+
6
+ interface TimelineData {
7
+ year: number;
8
+ [key: string]: number;
9
+ }
10
+
11
+ interface AlgorithmTimeline {
12
+ algorithm: string;
13
+ name: string;
14
+ category: string;
15
+ data: { year: number; count: number }[];
16
+ }
17
+
18
+ interface CacheStats {
19
+ cached: number;
20
+ fetched: number;
21
+ }
22
+
23
+ interface ProgressState {
24
+ current: number;
25
+ total: number;
26
+ currentAlgorithm: string;
27
+ }
28
+
29
+ const Timeline: React.FC = () => {
30
+ const [timelineData, setTimelineData] = useState<TimelineData[]>([]);
31
+ const [algorithms, setAlgorithms] = useState<AlgorithmTimeline[]>([]);
32
+ const [loading, setLoading] = useState(true);
33
+ const [error, setError] = useState<string | null>(null);
34
+ const [selectedAlgorithms, setSelectedAlgorithms] = useState<string[]>([]);
35
+ const [yearRange, setYearRange] = useState({ start: 2015, end: 2024 });
36
+ const [cacheStats, setCacheStats] = useState<CacheStats | null>(null);
37
+ const [progress, setProgress] = useState<ProgressState | null>(null);
38
+
39
+ useEffect(() => {
40
+ fetchTimelineData();
41
+ }, [yearRange]);
42
+
43
+ const fetchTimelineData = async () => {
44
+ try {
45
+ setLoading(true);
46
+ setError(null);
47
+ setProgress(null);
48
+ setCacheStats(null);
49
+
50
+ // Use Server-Sent Events for real-time progress
51
+ const eventSource = new EventSource(`/api/search/timeline-stream?startYear=${yearRange.start}&endYear=${yearRange.end}`);
52
+
53
+ eventSource.onmessage = (event) => {
54
+ const data = JSON.parse(event.data);
55
+
56
+ switch (data.type) {
57
+ case 'init':
58
+ setProgress({
59
+ current: 0,
60
+ total: data.totalOperations,
61
+ currentAlgorithm: data.message
62
+ });
63
+ setCacheStats({
64
+ cached: data.cachedResults,
65
+ fetched: data.fetchedResults
66
+ });
67
+ break;
68
+
69
+ case 'algorithm_start':
70
+ setProgress({
71
+ current: data.completed,
72
+ total: data.total,
73
+ currentAlgorithm: `Processing ${data.algorithm}...`
74
+ });
75
+ break;
76
+
77
+ case 'year_complete':
78
+ setProgress({
79
+ current: data.completed,
80
+ total: data.total,
81
+ currentAlgorithm: `${data.algorithm} (${data.year}): ${data.count} papers`
82
+ });
83
+ break;
84
+
85
+ case 'algorithm_complete':
86
+ setProgress({
87
+ current: data.completed,
88
+ total: data.total,
89
+ currentAlgorithm: `Completed ${data.algorithm}`
90
+ });
91
+ break;
92
+
93
+ case 'cache_saved':
94
+ setProgress(prev => prev ? {
95
+ ...prev,
96
+ currentAlgorithm: data.message
97
+ } : null);
98
+ break;
99
+
100
+ case 'complete':
101
+ setTimelineData(data.timelineData);
102
+ setAlgorithms(data.algorithms);
103
+ setCacheStats(data.cacheStats);
104
+
105
+ // Auto-select top 5 algorithms if none selected
106
+ if (selectedAlgorithms.length === 0) {
107
+ const topAlgorithms = data.algorithms
108
+ .sort((a: AlgorithmTimeline, b: AlgorithmTimeline) => {
109
+ const aTotal = a.data.reduce((sum, item) => sum + item.count, 0);
110
+ const bTotal = b.data.reduce((sum, item) => sum + item.count, 0);
111
+ return bTotal - aTotal;
112
+ })
113
+ .slice(0, 5)
114
+ .map((algo: AlgorithmTimeline) => algo.algorithm);
115
+ setSelectedAlgorithms(topAlgorithms);
116
+ }
117
+
118
+ setLoading(false);
119
+ setProgress(null);
120
+ eventSource.close();
121
+ break;
122
+
123
+ case 'error':
124
+ setError(`Failed to load timeline data: ${data.error}`);
125
+ setLoading(false);
126
+ setProgress(null);
127
+ eventSource.close();
128
+ break;
129
+ }
130
+ };
131
+
132
+ eventSource.onerror = () => {
133
+ setError('Connection lost while loading timeline data');
134
+ setLoading(false);
135
+ setProgress(null);
136
+ eventSource.close();
137
+ };
138
+
139
+ } catch (err) {
140
+ setError('Failed to start timeline data loading');
141
+ setLoading(false);
142
+ setProgress(null);
143
+ }
144
+ };
145
+
146
+ const handleAlgorithmToggle = (algorithmKey: string) => {
147
+ setSelectedAlgorithms(prev =>
148
+ prev.includes(algorithmKey)
149
+ ? prev.filter(key => key !== algorithmKey)
150
+ : [...prev, algorithmKey]
151
+ );
152
+ };
153
+
154
+ const getAlgorithmColor = (algorithmKey: string, index: number): string => {
155
+ // Define a palette of distinct colors for better visualization
156
+ const colors = [
157
+ '#3B82F6', // Blue
158
+ '#EF4444', // Red
159
+ '#10B981', // Green
160
+ '#F59E0B', // Yellow
161
+ '#8B5CF6', // Purple
162
+ '#EC4899', // Pink
163
+ '#06B6D4', // Cyan
164
+ '#84CC16', // Lime
165
+ '#F97316', // Orange
166
+ '#6366F1', // Indigo
167
+ '#14B8A6', // Teal
168
+ '#F43F5E', // Rose
169
+ '#8B5A3C', // Brown
170
+ '#6B7280', // Gray
171
+ '#DC2626', // Dark Red
172
+ '#059669', // Dark Green
173
+ '#7C3AED', // Dark Purple
174
+ '#DB2777', // Dark Pink
175
+ '#0891B2', // Dark Cyan
176
+ '#65A30D' // Dark Lime
177
+ ];
178
+
179
+ // Use algorithm key to get consistent color assignment
180
+ const colorIndex = selectedAlgorithms.indexOf(algorithmKey) % colors.length;
181
+ return colors[colorIndex];
182
+ };
183
+
184
+ if (loading) {
185
+ return (
186
+ <div className="space-y-8">
187
+ <div className="text-center">
188
+ <h1 className="text-4xl font-bold text-gray-900 mb-2">
189
+ Algorithm Usage Timeline
190
+ </h1>
191
+ <p className="text-xl text-gray-600">
192
+ Track how the usage of AI algorithms in medical research has evolved over time
193
+ </p>
194
+ </div>
195
+
196
+ <div className="bg-white p-8 rounded-lg shadow-lg max-w-2xl mx-auto">
197
+ <div className="flex items-center justify-center mb-6">
198
+ <Loader2 className="h-8 w-8 animate-spin text-blue-600" />
199
+ <span className="ml-3 text-lg text-gray-700">Loading timeline data...</span>
200
+ </div>
201
+
202
+ {progress && (
203
+ <div className="space-y-4">
204
+ <div className="flex justify-between items-center">
205
+ <span className="text-sm text-gray-600">
206
+ {progress.currentAlgorithm}
207
+ </span>
208
+ <span className="text-sm font-medium text-gray-900">
209
+ {Math.round((progress.current / progress.total) * 100)}%
210
+ </span>
211
+ </div>
212
+
213
+ <div className="w-full bg-gray-200 rounded-full h-2">
214
+ <div
215
+ className="bg-blue-600 h-2 rounded-full transition-all duration-300 ease-out"
216
+ style={{ width: `${Math.min((progress.current / progress.total) * 100, 100)}%` }}
217
+ ></div>
218
+ </div>
219
+
220
+ <div className="flex justify-between text-xs text-gray-500">
221
+ <span>{progress.current} / {progress.total} requests</span>
222
+ <span>
223
+ {progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0}% complete
224
+ </span>
225
+ </div>
226
+ </div>
227
+ )}
228
+
229
+ <div className="mt-6 p-4 bg-blue-50 rounded-lg">
230
+ <div className="flex items-center mb-2">
231
+ <Database className="h-4 w-4 text-blue-600 mr-2" />
232
+ <span className="text-sm font-medium text-blue-900">Cache Information</span>
233
+ </div>
234
+ <p className="text-xs text-blue-700">
235
+ Historical data is cached on disk to speed up future requests.
236
+ Only the current year data needs to be fetched from PubMed each time.
237
+ </p>
238
+ </div>
239
+ </div>
240
+ </div>
241
+ );
242
+ }
243
+
244
+ if (error) {
245
+ return (
246
+ <div className="text-center text-red-600 p-8">
247
+ <p>{error}</p>
248
+ <button
249
+ onClick={fetchTimelineData}
250
+ className="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
251
+ >
252
+ Retry
253
+ </button>
254
+ </div>
255
+ );
256
+ }
257
+
258
+ const filteredTimelineData = timelineData.map(yearData => {
259
+ const filtered: TimelineData = { year: yearData.year };
260
+ selectedAlgorithms.forEach(algoKey => {
261
+ if (yearData[algoKey] !== undefined) {
262
+ filtered[algoKey] = yearData[algoKey];
263
+ }
264
+ });
265
+ return filtered;
266
+ });
267
+
268
+ return (
269
+ <div className="space-y-8">
270
+ <div className="text-center">
271
+ <h1 className="text-4xl font-bold text-gray-900 mb-2">
272
+ Algorithm Usage Timeline
273
+ </h1>
274
+ <p className="text-xl text-gray-600">
275
+ Track how the usage of AI algorithms in medical research has evolved over time
276
+ </p>
277
+ </div>
278
+
279
+ <div className="bg-white p-6 rounded-lg shadow-lg">
280
+ <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between mb-6">
281
+ <div className="flex items-center mb-4 lg:mb-0">
282
+ <Calendar className="h-5 w-5 text-gray-500 mr-2" />
283
+ <span className="text-sm font-medium text-gray-700">Year Range:</span>
284
+ <input
285
+ type="number"
286
+ min="2010"
287
+ max="2024"
288
+ value={yearRange.start}
289
+ onChange={(e) => setYearRange(prev => ({ ...prev, start: parseInt(e.target.value) }))}
290
+ className="ml-2 px-2 py-1 border border-gray-300 rounded text-sm w-20"
291
+ />
292
+ <span className="mx-2 text-gray-500">to</span>
293
+ <input
294
+ type="number"
295
+ min="2010"
296
+ max="2024"
297
+ value={yearRange.end}
298
+ onChange={(e) => setYearRange(prev => ({ ...prev, end: parseInt(e.target.value) }))}
299
+ className="px-2 py-1 border border-gray-300 rounded text-sm w-20"
300
+ />
301
+ </div>
302
+
303
+ <div className="flex items-center space-x-6">
304
+ <div className="flex items-center">
305
+ <TrendingUp className="h-5 w-5 text-green-500 mr-2" />
306
+ <span className="text-sm text-gray-600">
307
+ {selectedAlgorithms.length} algorithm{selectedAlgorithms.length !== 1 ? 's' : ''} selected
308
+ </span>
309
+ </div>
310
+
311
+ {cacheStats && (
312
+ <div className="flex items-center space-x-4">
313
+ <div className="flex items-center">
314
+ <Database className="h-4 w-4 text-blue-500 mr-1" />
315
+ <span className="text-xs text-gray-600">
316
+ {cacheStats.cached} cached
317
+ </span>
318
+ </div>
319
+ <div className="flex items-center">
320
+ <Clock className="h-4 w-4 text-orange-500 mr-1" />
321
+ <span className="text-xs text-gray-600">
322
+ {cacheStats.fetched} fetched
323
+ </span>
324
+ </div>
325
+ </div>
326
+ )}
327
+ </div>
328
+ </div>
329
+
330
+ <ResponsiveContainer width="100%" height={500}>
331
+ <LineChart data={filteredTimelineData}>
332
+ <CartesianGrid strokeDasharray="3 3" />
333
+ <XAxis
334
+ dataKey="year"
335
+ type="number"
336
+ scale="linear"
337
+ domain={[yearRange.start, yearRange.end]}
338
+ />
339
+ <YAxis />
340
+ <Tooltip />
341
+ <Legend />
342
+ {selectedAlgorithms.map((algoKey, index) => {
343
+ const algo = algorithms.find(a => a.algorithm === algoKey);
344
+ if (!algo) return null;
345
+
346
+ return (
347
+ <Line
348
+ key={algoKey}
349
+ type="monotone"
350
+ dataKey={algoKey}
351
+ stroke={getAlgorithmColor(algoKey, index)}
352
+ strokeWidth={2}
353
+ name={algo.name}
354
+ connectNulls={false}
355
+ />
356
+ );
357
+ })}
358
+ </LineChart>
359
+ </ResponsiveContainer>
360
+ </div>
361
+
362
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
363
+ <div className="bg-white p-6 rounded-lg shadow-lg">
364
+ <h3 className="text-lg font-bold text-gray-900 mb-4">Classical ML</h3>
365
+ <div className="space-y-2">
366
+ {algorithms
367
+ .filter(algo => algo.category === 'classical_ml')
368
+ .slice(0, 8)
369
+ .map(algo => (
370
+ <label key={algo.algorithm} className="flex items-center cursor-pointer">
371
+ <input
372
+ type="checkbox"
373
+ checked={selectedAlgorithms.includes(algo.algorithm)}
374
+ onChange={() => handleAlgorithmToggle(algo.algorithm)}
375
+ className="mr-2"
376
+ />
377
+ <span className="text-sm text-gray-700">{algo.name}</span>
378
+ </label>
379
+ ))}
380
+ </div>
381
+ </div>
382
+
383
+ <div className="bg-white p-6 rounded-lg shadow-lg">
384
+ <h3 className="text-lg font-bold text-gray-900 mb-4">Deep Learning</h3>
385
+ <div className="space-y-2">
386
+ {algorithms
387
+ .filter(algo => algo.category === 'deep_learning')
388
+ .slice(0, 8)
389
+ .map(algo => (
390
+ <label key={algo.algorithm} className="flex items-center cursor-pointer">
391
+ <input
392
+ type="checkbox"
393
+ checked={selectedAlgorithms.includes(algo.algorithm)}
394
+ onChange={() => handleAlgorithmToggle(algo.algorithm)}
395
+ className="mr-2"
396
+ />
397
+ <span className="text-sm text-gray-700">{algo.name}</span>
398
+ </label>
399
+ ))}
400
+ </div>
401
+ </div>
402
+
403
+ <div className="bg-white p-6 rounded-lg shadow-lg">
404
+ <h3 className="text-lg font-bold text-gray-900 mb-4">Large Language Models</h3>
405
+ <div className="space-y-2">
406
+ {algorithms
407
+ .filter(algo => algo.category === 'llms')
408
+ .slice(0, 8)
409
+ .map(algo => (
410
+ <label key={algo.algorithm} className="flex items-center cursor-pointer">
411
+ <input
412
+ type="checkbox"
413
+ checked={selectedAlgorithms.includes(algo.algorithm)}
414
+ onChange={() => handleAlgorithmToggle(algo.algorithm)}
415
+ className="mr-2"
416
+ />
417
+ <span className="text-sm text-gray-700">{algo.name}</span>
418
+ </label>
419
+ ))}
420
+ </div>
421
+ </div>
422
+ </div>
423
+ </div>
424
+ );
425
+ };
426
+
427
+ export default Timeline;
frontend/src/services/api.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from 'axios';
2
+
3
+ const api = axios.create({
4
+ baseURL: '/api',
5
+ timeout: 300000, // 5 minutes for timeline requests
6
+ });
7
+
8
+ api.interceptors.response.use(
9
+ (response) => response,
10
+ (error) => {
11
+ console.error('API Error:', error.response?.data || error.message);
12
+ return Promise.reject(error);
13
+ }
14
+ );
15
+
16
+ export default api;
frontend/tailwind.config.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {},
9
+ },
10
+ plugins: [],
11
+ }
frontend/vite.config.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ port: 3000,
8
+ proxy: {
9
+ '/api': {
10
+ target: 'http://localhost:3001',
11
+ changeOrigin: true
12
+ }
13
+ }
14
+ }
15
+ })
package.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "pubmed-ai-explorer",
3
+ "version": "1.0.0",
4
+ "description": "Web app to explore AI algorithms used in medical papers on PubMed",
5
+ "scripts": {
6
+ "dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"",
7
+ "dev:backend": "cd backend && npm run dev",
8
+ "dev:frontend": "cd frontend && npm run dev",
9
+ "build": "cd frontend && npm run build",
10
+ "start": "cd backend && npm start",
11
+ "install:all": "npm install && cd backend && npm install && cd ../frontend && npm install",
12
+ "docker:build": "docker build -t pubmed-ai-explorer .",
13
+ "docker:run": "docker run -p 3001:3001 pubmed-ai-explorer",
14
+ "docker:up": "docker-compose up",
15
+ "docker:down": "docker-compose down",
16
+ "docker:dev": "docker-compose --profile dev up"
17
+ },
18
+ "devDependencies": {
19
+ "concurrently": "^8.2.2"
20
+ }
21
+ }