Spaces:
Sleeping
Sleeping
Commit ·
d7c0198
0
Parent(s):
Prepare ResumeAI Pro for Hugging Face Spaces deployment
Browse files- .dockerignore +15 -0
- .env.example +6 -0
- .gitignore +17 -0
- Dockerfile +58 -0
- README.md +91 -0
- app/globals.css +97 -0
- app/layout.tsx +33 -0
- app/page.tsx +325 -0
- backend/Dockerfile +12 -0
- backend/app/__init__.py +1 -0
- backend/app/agents/analysis.py +130 -0
- backend/app/agents/cover_letter.py +25 -0
- backend/app/agents/optimization.py +42 -0
- backend/app/agents/parsing.py +36 -0
- backend/app/agents/scoring.py +25 -0
- backend/app/config.py +23 -0
- backend/app/main.py +98 -0
- backend/app/schemas.py +79 -0
- backend/app/services/document_export.py +401 -0
- backend/app/services/openai_generation.py +104 -0
- backend/app/services/pdf.py +16 -0
- backend/app/services/pipeline.py +41 -0
- backend/requirements.txt +8 -0
- components/demo-console.tsx +672 -0
- docker-compose.yml +32 -0
- frontend.Dockerfile +38 -0
- next-env.d.ts +6 -0
- next.config.ts +22 -0
- package-lock.json +2165 -0
- package.json +25 -0
- postcss.config.js +6 -0
- scripts/start-prod.sh +17 -0
- tailwind.config.ts +26 -0
- tsconfig.json +28 -0
.dockerignore
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.next
|
| 3 |
+
node_modules
|
| 4 |
+
backend/__pycache__
|
| 5 |
+
backend/app/__pycache__
|
| 6 |
+
backend/app/agents/__pycache__
|
| 7 |
+
backend/app/services/__pycache__
|
| 8 |
+
*.pyc
|
| 9 |
+
*.pyo
|
| 10 |
+
*.pyd
|
| 11 |
+
.env
|
| 12 |
+
.env.local
|
| 13 |
+
.venv
|
| 14 |
+
dist
|
| 15 |
+
coverage
|
.env.example
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
NEXT_PUBLIC_API_URL=
|
| 2 |
+
BACKEND_API_URL=http://127.0.0.1:8000
|
| 3 |
+
APP_ENV=development
|
| 4 |
+
ALLOWED_ORIGINS=["http://localhost:3000","http://127.0.0.1:3000","http://localhost:7860","http://127.0.0.1:7860"]
|
| 5 |
+
OPENAI_API_KEY=
|
| 6 |
+
OPENAI_MODEL=gpt-5-mini
|
.gitignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.next
|
| 2 |
+
node_modules
|
| 3 |
+
out
|
| 4 |
+
dist
|
| 5 |
+
coverage
|
| 6 |
+
.venv
|
| 7 |
+
__pycache__
|
| 8 |
+
.pytest_cache
|
| 9 |
+
.env
|
| 10 |
+
.env.local
|
| 11 |
+
.env.development.local
|
| 12 |
+
.env.test.local
|
| 13 |
+
.env.production.local
|
| 14 |
+
npm-debug.log*
|
| 15 |
+
yarn-debug.log*
|
| 16 |
+
yarn-error.log*
|
| 17 |
+
pnpm-debug.log*
|
Dockerfile
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:22-bookworm-slim AS frontend-builder
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
ENV NEXT_TELEMETRY_DISABLED=1
|
| 6 |
+
ENV BACKEND_API_URL=http://127.0.0.1:8000
|
| 7 |
+
|
| 8 |
+
COPY package.json package-lock.json ./
|
| 9 |
+
RUN npm ci
|
| 10 |
+
|
| 11 |
+
COPY app ./app
|
| 12 |
+
COPY components ./components
|
| 13 |
+
COPY public ./public
|
| 14 |
+
COPY next.config.ts ./
|
| 15 |
+
COPY next-env.d.ts ./
|
| 16 |
+
COPY tsconfig.json ./
|
| 17 |
+
COPY postcss.config.js ./
|
| 18 |
+
COPY tailwind.config.ts ./
|
| 19 |
+
|
| 20 |
+
RUN npm run build
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
FROM python:3.13-slim AS backend-builder
|
| 24 |
+
|
| 25 |
+
WORKDIR /app/backend
|
| 26 |
+
|
| 27 |
+
COPY backend/requirements.txt ./
|
| 28 |
+
RUN python -m venv /opt/venv \
|
| 29 |
+
&& /opt/venv/bin/pip install --upgrade pip \
|
| 30 |
+
&& /opt/venv/bin/pip install --no-cache-dir -r requirements.txt
|
| 31 |
+
|
| 32 |
+
COPY backend/app ./app
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
FROM python:3.13-slim AS runtime
|
| 36 |
+
|
| 37 |
+
COPY --from=node:22-bookworm-slim /usr/local /usr/local
|
| 38 |
+
|
| 39 |
+
ENV PATH="/opt/venv/bin:$PATH"
|
| 40 |
+
ENV PYTHONUNBUFFERED=1
|
| 41 |
+
ENV NEXT_TELEMETRY_DISABLED=1
|
| 42 |
+
ENV PORT=7860
|
| 43 |
+
ENV HOSTNAME=0.0.0.0
|
| 44 |
+
|
| 45 |
+
WORKDIR /app
|
| 46 |
+
|
| 47 |
+
COPY --from=backend-builder /opt/venv /opt/venv
|
| 48 |
+
COPY --from=backend-builder /app/backend /app/backend
|
| 49 |
+
COPY --from=frontend-builder /app/.next/standalone /app/frontend
|
| 50 |
+
COPY --from=frontend-builder /app/.next/static /app/frontend/.next/static
|
| 51 |
+
COPY --from=frontend-builder /app/public /app/frontend/public
|
| 52 |
+
COPY scripts/start-prod.sh /app/start-prod.sh
|
| 53 |
+
|
| 54 |
+
RUN chmod +x /app/start-prod.sh
|
| 55 |
+
|
| 56 |
+
EXPOSE 7860
|
| 57 |
+
|
| 58 |
+
CMD ["/app/start-prod.sh"]
|
README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: ResumeAI Pro
|
| 3 |
+
sdk: docker
|
| 4 |
+
app_port: 7860
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
# ResumeAI Pro
|
| 8 |
+
|
| 9 |
+
ResumeAI Pro is a full-stack AI agent project that autonomously analyzes, optimizes, and personalizes resumes and cover letters against specific job descriptions.
|
| 10 |
+
|
| 11 |
+
## What is included
|
| 12 |
+
|
| 13 |
+
- A polished Next.js landing page for the project story and architecture
|
| 14 |
+
- A FastAPI backend with agent-style modules for parsing, analysis, optimization, cover letter generation, and ATS scoring
|
| 15 |
+
- Optional OpenAI-powered resume rewriting and cover-letter generation when `OPENAI_API_KEY` is configured
|
| 16 |
+
- PDF upload support for resume ingestion
|
| 17 |
+
- Deployment-ready Docker configuration for local multi-service runs and single-container Spaces deployment
|
| 18 |
+
|
| 19 |
+
## Stack
|
| 20 |
+
|
| 21 |
+
- Next.js 15 + Tailwind CSS for the frontend
|
| 22 |
+
- FastAPI for the backend API layer
|
| 23 |
+
- OpenAI API ready architecture for future reasoning integration
|
| 24 |
+
- Docker and Docker Compose for local and hosted deployment
|
| 25 |
+
|
| 26 |
+
## Frontend setup
|
| 27 |
+
|
| 28 |
+
1. Install Node.js 22 or later.
|
| 29 |
+
2. Run `npm install`.
|
| 30 |
+
3. Start the frontend with `npm run dev`.
|
| 31 |
+
4. Open `http://localhost:3000`.
|
| 32 |
+
|
| 33 |
+
## Backend setup
|
| 34 |
+
|
| 35 |
+
1. Move into `backend`.
|
| 36 |
+
2. Run `python -m pip install -r requirements.txt`.
|
| 37 |
+
3. Start the API with `python -m uvicorn app.main:app --reload`.
|
| 38 |
+
4. Open `http://localhost:8000/docs` for the Swagger UI.
|
| 39 |
+
|
| 40 |
+
## Environment
|
| 41 |
+
|
| 42 |
+
Copy `.env.example` to `.env` and set:
|
| 43 |
+
|
| 44 |
+
- `NEXT_PUBLIC_API_URL` if you want the browser to call an external API directly. Leave it blank to use same-origin proxying.
|
| 45 |
+
- `BACKEND_API_URL` for Next.js server-side rewrites to the FastAPI service
|
| 46 |
+
- `OPENAI_API_KEY` if you want live OpenAI rewriting instead of the local fallback
|
| 47 |
+
- `OPENAI_MODEL` if you want to override the default model
|
| 48 |
+
|
| 49 |
+
## Local Docker setup
|
| 50 |
+
|
| 51 |
+
Run `docker compose up --build` from the project root to start both services together.
|
| 52 |
+
|
| 53 |
+
## Production deployment
|
| 54 |
+
|
| 55 |
+
### Hugging Face Spaces
|
| 56 |
+
|
| 57 |
+
This repository is configured for Docker Spaces deployment:
|
| 58 |
+
|
| 59 |
+
1. Create a new Space with `Docker` as the SDK.
|
| 60 |
+
2. Push this repository to the Space.
|
| 61 |
+
3. Set repository secrets or Space variables for `OPENAI_API_KEY` if you want live OpenAI generation.
|
| 62 |
+
4. The root `Dockerfile` will start both FastAPI and Next.js inside one container and expose the app on port `7860`.
|
| 63 |
+
|
| 64 |
+
### Before push
|
| 65 |
+
|
| 66 |
+
1. Initialize Git in this folder if needed: `git init`
|
| 67 |
+
2. Remove local generated artifacts before committing: `.next`, `node_modules`, and Python `__pycache__` folders
|
| 68 |
+
3. Create your first commit
|
| 69 |
+
4. Connect the repo to Hugging Face Spaces or GitHub
|
| 70 |
+
5. Push to the Docker Space remote
|
| 71 |
+
|
| 72 |
+
### Generic container deployment
|
| 73 |
+
|
| 74 |
+
Build and run the production image from the project root:
|
| 75 |
+
|
| 76 |
+
1. `docker build -t resumeai-pro .`
|
| 77 |
+
2. `docker run -p 7860:7860 --env-file .env resumeai-pro`
|
| 78 |
+
|
| 79 |
+
The production container serves the Next.js frontend publicly and proxies `/api/*` requests internally to the FastAPI backend.
|
| 80 |
+
|
| 81 |
+
## Current API
|
| 82 |
+
|
| 83 |
+
- `GET /health`
|
| 84 |
+
- `POST /api/pipeline`
|
| 85 |
+
- `POST /api/pipeline/upload`
|
| 86 |
+
- `POST /api/export/resume/pdf`
|
| 87 |
+
- `POST /api/export/resume/docx`
|
| 88 |
+
- `POST /api/export/cover-letter/pdf`
|
| 89 |
+
- `POST /api/export/cover-letter/docx`
|
| 90 |
+
|
| 91 |
+
The pipeline works without an API key using deterministic heuristics. If `OPENAI_API_KEY` is present, the backend upgrades the optimization and cover-letter steps with structured output from the OpenAI Responses API.
|
app/globals.css
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@tailwind base;
|
| 2 |
+
@tailwind components;
|
| 3 |
+
@tailwind utilities;
|
| 4 |
+
|
| 5 |
+
:root {
|
| 6 |
+
--background: #f8f3ea;
|
| 7 |
+
--foreground: #10121a;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
html {
|
| 11 |
+
scroll-behavior: smooth;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
body {
|
| 15 |
+
min-height: 100vh;
|
| 16 |
+
background:
|
| 17 |
+
radial-gradient(circle at top left, rgba(215, 162, 108, 0.28), transparent 32%),
|
| 18 |
+
radial-gradient(circle at 85% 12%, rgba(33, 73, 63, 0.18), transparent 24%),
|
| 19 |
+
linear-gradient(180deg, #f6f0e6 0%, #fbf8f2 52%, #f3ece1 100%);
|
| 20 |
+
color: var(--foreground);
|
| 21 |
+
font-family: var(--font-sans), sans-serif;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
a {
|
| 25 |
+
color: inherit;
|
| 26 |
+
text-decoration: none;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.font-display {
|
| 30 |
+
font-family: var(--font-display), serif;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.page-glow {
|
| 34 |
+
pointer-events: none;
|
| 35 |
+
position: absolute;
|
| 36 |
+
border-radius: 999px;
|
| 37 |
+
filter: blur(70px);
|
| 38 |
+
opacity: 0.8;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.page-glow-one {
|
| 42 |
+
right: -8rem;
|
| 43 |
+
top: 14rem;
|
| 44 |
+
height: 18rem;
|
| 45 |
+
width: 18rem;
|
| 46 |
+
background: rgba(127, 59, 59, 0.12);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.page-glow-two {
|
| 50 |
+
bottom: 8rem;
|
| 51 |
+
left: -6rem;
|
| 52 |
+
height: 20rem;
|
| 53 |
+
width: 20rem;
|
| 54 |
+
background: rgba(33, 73, 63, 0.12);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.hero-panel {
|
| 58 |
+
border-radius: 2rem;
|
| 59 |
+
background:
|
| 60 |
+
linear-gradient(150deg, rgba(255, 255, 255, 0.06), transparent 36%),
|
| 61 |
+
linear-gradient(135deg, #141720 0%, #1d302a 70%, #7f3b3b 160%);
|
| 62 |
+
padding: 1.75rem;
|
| 63 |
+
box-shadow: 0 24px 65px rgba(16, 18, 26, 0.18);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.section-intro {
|
| 67 |
+
max-width: 52rem;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.eyebrow {
|
| 71 |
+
font-size: 0.72rem;
|
| 72 |
+
font-weight: 600;
|
| 73 |
+
letter-spacing: 0.28em;
|
| 74 |
+
text-transform: uppercase;
|
| 75 |
+
color: rgba(16, 18, 26, 0.58);
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.section-title {
|
| 79 |
+
margin-top: 1rem;
|
| 80 |
+
font-family: var(--font-display), serif;
|
| 81 |
+
font-size: clamp(2.25rem, 4vw, 4rem);
|
| 82 |
+
line-height: 0.98;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.section-copy {
|
| 86 |
+
margin-top: 1.25rem;
|
| 87 |
+
max-width: 44rem;
|
| 88 |
+
font-size: 1rem;
|
| 89 |
+
line-height: 2;
|
| 90 |
+
color: rgba(16, 18, 26, 0.72);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
@media (min-width: 768px) {
|
| 94 |
+
.hero-panel {
|
| 95 |
+
padding: 2rem;
|
| 96 |
+
}
|
| 97 |
+
}
|
app/layout.tsx
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Metadata } from "next";
|
| 2 |
+
import { Cormorant_Garamond, Space_Grotesk } from "next/font/google";
|
| 3 |
+
import "./globals.css";
|
| 4 |
+
|
| 5 |
+
const display = Cormorant_Garamond({
|
| 6 |
+
subsets: ["latin"],
|
| 7 |
+
variable: "--font-display",
|
| 8 |
+
weight: ["400", "500", "600", "700"],
|
| 9 |
+
});
|
| 10 |
+
|
| 11 |
+
const sans = Space_Grotesk({
|
| 12 |
+
subsets: ["latin"],
|
| 13 |
+
variable: "--font-sans",
|
| 14 |
+
weight: ["400", "500", "700"],
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
export const metadata: Metadata = {
|
| 18 |
+
title: "ResumeAI Pro",
|
| 19 |
+
description:
|
| 20 |
+
"Autonomous resume optimization platform with multi-agent analysis, ATS scoring, and personalized document generation.",
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
export default function RootLayout({
|
| 24 |
+
children,
|
| 25 |
+
}: Readonly<{
|
| 26 |
+
children: React.ReactNode;
|
| 27 |
+
}>) {
|
| 28 |
+
return (
|
| 29 |
+
<html lang="en">
|
| 30 |
+
<body className={`${display.variable} ${sans.variable}`}>{children}</body>
|
| 31 |
+
</html>
|
| 32 |
+
);
|
| 33 |
+
}
|
app/page.tsx
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { DemoConsole } from "@/components/demo-console";
|
| 2 |
+
|
| 3 |
+
const agentStages = [
|
| 4 |
+
{
|
| 5 |
+
title: "Parsing Agent",
|
| 6 |
+
description:
|
| 7 |
+
"Extracts clean, structured content from uploaded PDF resumes and normalizes the text for downstream reasoning.",
|
| 8 |
+
outcomes: ["PDF text extraction", "Structured resume sections", "Content normalization"],
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
title: "Analysis Agent",
|
| 12 |
+
description:
|
| 13 |
+
"Compares the current resume against a specific job description to uncover weak positioning and ATS blind spots.",
|
| 14 |
+
outcomes: ["Skill gap detection", "Missing keyword analysis", "Weak bullet identification"],
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
title: "Optimization Agent",
|
| 18 |
+
description:
|
| 19 |
+
"Rewrites resume bullets with stronger action language, measurable outcomes, and recruiter-friendly clarity.",
|
| 20 |
+
outcomes: ["Impact-driven bullets", "Quantified achievements", "ATS-friendly formatting"],
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
title: "Cover Letter Agent",
|
| 24 |
+
description:
|
| 25 |
+
"Generates role-specific cover letters that align candidate experience, tone, and motivation with job context.",
|
| 26 |
+
outcomes: ["Tailored messaging", "Role alignment", "Context-aware tone"],
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
title: "ATS Scoring Agent",
|
| 30 |
+
description:
|
| 31 |
+
"Scores the resume against hiring criteria and surfaces practical guidance for improving match quality.",
|
| 32 |
+
outcomes: ["Match percentage", "Missing keyword report", "Optimization suggestions"],
|
| 33 |
+
},
|
| 34 |
+
] as const;
|
| 35 |
+
|
| 36 |
+
const features = [
|
| 37 |
+
"Automated resume parsing from PDF uploads",
|
| 38 |
+
"Job-specific optimization and rewrite flow",
|
| 39 |
+
"Personalized cover letter generation",
|
| 40 |
+
"ATS keyword scoring and match reporting",
|
| 41 |
+
"Keyword gap analysis with improvement cues",
|
| 42 |
+
"Before-versus-after resume comparison",
|
| 43 |
+
"Responsive dashboard experience",
|
| 44 |
+
"Real-time AI processing feedback",
|
| 45 |
+
] as const;
|
| 46 |
+
|
| 47 |
+
const stack = [
|
| 48 |
+
{
|
| 49 |
+
label: "Backend",
|
| 50 |
+
summary: "FastAPI powers a modular agent pipeline with clean API boundaries for parsing, analysis, generation, and scoring.",
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
label: "Frontend",
|
| 54 |
+
summary: "Next.js and Tailwind CSS deliver a responsive interface optimized for dashboards, document review, and quick iteration.",
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
label: "Deployment",
|
| 58 |
+
summary: "Docker packaging enables portable deployment, with Hugging Face Spaces used as the production runtime target.",
|
| 59 |
+
},
|
| 60 |
+
] as const;
|
| 61 |
+
|
| 62 |
+
const capabilities = [
|
| 63 |
+
"Task decomposition across specialized agents",
|
| 64 |
+
"Tool-augmented reasoning with parsing and scoring engines",
|
| 65 |
+
"Context-aware generation for tailored resume assets",
|
| 66 |
+
"Iterative refinement aligned to recruiter expectations",
|
| 67 |
+
] as const;
|
| 68 |
+
|
| 69 |
+
const metrics = [
|
| 70 |
+
{ value: "80%+", label: "Manual tailoring effort reduced" },
|
| 71 |
+
{ value: "5", label: "Specialized agents in the pipeline" },
|
| 72 |
+
{ value: "Full-stack", label: "AI, backend, frontend, and deployment ownership" },
|
| 73 |
+
] as const;
|
| 74 |
+
|
| 75 |
+
export default function Home() {
|
| 76 |
+
return (
|
| 77 |
+
<main className="relative overflow-hidden">
|
| 78 |
+
<div className="page-glow page-glow-one" />
|
| 79 |
+
<div className="page-glow page-glow-two" />
|
| 80 |
+
|
| 81 |
+
<section className="mx-auto flex min-h-screen w-full max-w-7xl flex-col px-6 pb-16 pt-8 md:px-10 lg:px-12">
|
| 82 |
+
<header className="flex flex-col gap-2 border-b border-black/10 pb-5 text-sm uppercase tracking-[0.3em] text-ink/65 sm:flex-row sm:items-center sm:justify-between">
|
| 83 |
+
<span>ResumeAI Pro</span>
|
| 84 |
+
<span>Autonomous Resume Optimization Agent</span>
|
| 85 |
+
</header>
|
| 86 |
+
|
| 87 |
+
<div className="grid flex-1 gap-10 py-12 lg:grid-cols-[1.15fr_0.85fr] lg:items-center">
|
| 88 |
+
<div className="space-y-8">
|
| 89 |
+
<div className="inline-flex items-center rounded-full border border-pine/20 bg-white/70 px-4 py-2 text-xs font-medium uppercase tracking-[0.25em] text-pine backdrop-blur">
|
| 90 |
+
AI agent system for recruiter-grade resume personalization
|
| 91 |
+
</div>
|
| 92 |
+
|
| 93 |
+
<div className="space-y-6">
|
| 94 |
+
<p className="max-w-2xl font-display text-6xl leading-[0.95] text-ink md:text-7xl">
|
| 95 |
+
Resume optimization that reasons like a hiring team, not a one-shot prompt.
|
| 96 |
+
</p>
|
| 97 |
+
<p className="max-w-xl text-base leading-8 text-ink/72 md:text-lg">
|
| 98 |
+
ResumeAI Pro is a full-stack multi-agent platform that analyzes resumes, compares them to job descriptions,
|
| 99 |
+
rewrites content for stronger recruiter impact, generates tailored cover letters, and scores ATS alignment in
|
| 100 |
+
one continuous workflow.
|
| 101 |
+
</p>
|
| 102 |
+
</div>
|
| 103 |
+
|
| 104 |
+
<div className="flex flex-wrap gap-4">
|
| 105 |
+
<a className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-sand transition hover:bg-pine" href="#architecture">
|
| 106 |
+
Explore the pipeline
|
| 107 |
+
</a>
|
| 108 |
+
<a
|
| 109 |
+
className="rounded-full border border-ink/15 bg-white/80 px-6 py-3 text-sm font-medium text-ink transition hover:border-ink/30 hover:bg-white"
|
| 110 |
+
href="#implementation"
|
| 111 |
+
>
|
| 112 |
+
View the technical stack
|
| 113 |
+
</a>
|
| 114 |
+
</div>
|
| 115 |
+
|
| 116 |
+
<div className="grid gap-4 pt-2 md:grid-cols-3">
|
| 117 |
+
{metrics.map((metric) => (
|
| 118 |
+
<div key={metric.label} className="rounded-[1.75rem] border border-black/8 bg-white/70 p-5 shadow-card backdrop-blur">
|
| 119 |
+
<p className="text-2xl font-bold text-ink">{metric.value}</p>
|
| 120 |
+
<p className="mt-2 text-sm leading-6 text-ink/68">{metric.label}</p>
|
| 121 |
+
</div>
|
| 122 |
+
))}
|
| 123 |
+
</div>
|
| 124 |
+
</div>
|
| 125 |
+
|
| 126 |
+
<div className="relative">
|
| 127 |
+
<div className="hero-panel">
|
| 128 |
+
<div className="flex items-start justify-between gap-5 border-b border-white/12 pb-5">
|
| 129 |
+
<div>
|
| 130 |
+
<p className="text-xs uppercase tracking-[0.3em] text-sand/70">Live workflow snapshot</p>
|
| 131 |
+
<h2 className="mt-3 text-2xl font-semibold text-sand">Recruiter-style evaluation loop</h2>
|
| 132 |
+
</div>
|
| 133 |
+
<div className="rounded-full border border-white/15 px-3 py-1 text-xs uppercase tracking-[0.2em] text-sand/80">
|
| 134 |
+
Multi-agent
|
| 135 |
+
</div>
|
| 136 |
+
</div>
|
| 137 |
+
|
| 138 |
+
<div className="mt-6 space-y-4">
|
| 139 |
+
{[
|
| 140 |
+
["Resume uploaded", "PDF parsed into structured candidate data"],
|
| 141 |
+
["Job description mapped", "Priority keywords and skill signals extracted"],
|
| 142 |
+
["Optimization run", "Bullets rewritten for impact and ATS alignment"],
|
| 143 |
+
["Cover letter generated", "Role-specific narrative drafted from profile context"],
|
| 144 |
+
].map(([title, text], index) => (
|
| 145 |
+
<div key={title} className="grid grid-cols-[auto_1fr] gap-4 rounded-3xl border border-white/10 bg-white/5 p-4">
|
| 146 |
+
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-clay text-sm font-semibold text-ink">
|
| 147 |
+
0{index + 1}
|
| 148 |
+
</div>
|
| 149 |
+
<div>
|
| 150 |
+
<p className="font-medium text-sand">{title}</p>
|
| 151 |
+
<p className="mt-1 text-sm leading-6 text-sand/72">{text}</p>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
))}
|
| 155 |
+
</div>
|
| 156 |
+
|
| 157 |
+
<div className="mt-6 grid gap-4 sm:grid-cols-2">
|
| 158 |
+
<div className="rounded-3xl bg-sand p-5 text-ink">
|
| 159 |
+
<p className="text-xs uppercase tracking-[0.25em] text-ink/60">ATS Match Score</p>
|
| 160 |
+
<p className="mt-3 text-4xl font-bold">92%</p>
|
| 161 |
+
<p className="mt-2 text-sm leading-6 text-ink/70">Keyword alignment improved after optimization and role-specific rewriting.</p>
|
| 162 |
+
</div>
|
| 163 |
+
<div className="rounded-3xl border border-white/10 bg-white/5 p-5">
|
| 164 |
+
<p className="text-xs uppercase tracking-[0.25em] text-sand/60">Key outputs</p>
|
| 165 |
+
<ul className="mt-3 space-y-3 text-sm leading-6 text-sand/80">
|
| 166 |
+
<li>Resume rewrite with quantified achievements</li>
|
| 167 |
+
<li>Missing keyword and gap report</li>
|
| 168 |
+
<li>Tailored cover letter draft</li>
|
| 169 |
+
</ul>
|
| 170 |
+
</div>
|
| 171 |
+
</div>
|
| 172 |
+
</div>
|
| 173 |
+
</div>
|
| 174 |
+
</div>
|
| 175 |
+
</section>
|
| 176 |
+
|
| 177 |
+
<section id="architecture" className="mx-auto max-w-7xl px-6 py-20 md:px-10 lg:px-12">
|
| 178 |
+
<div className="section-intro">
|
| 179 |
+
<p className="eyebrow">Agent Architecture</p>
|
| 180 |
+
<h2 className="section-title">A specialized pipeline where each agent owns a clear decision boundary.</h2>
|
| 181 |
+
<p className="section-copy">
|
| 182 |
+
Instead of treating resume improvement as a single prompt, the system decomposes the workflow into focused agents
|
| 183 |
+
that parse, evaluate, rewrite, generate, and score outputs in sequence.
|
| 184 |
+
</p>
|
| 185 |
+
</div>
|
| 186 |
+
|
| 187 |
+
<div className="mt-10 grid gap-6 lg:grid-cols-2 xl:grid-cols-3">
|
| 188 |
+
{agentStages.map((stage) => (
|
| 189 |
+
<article key={stage.title} className="rounded-[2rem] border border-black/8 bg-white/75 p-7 shadow-card backdrop-blur">
|
| 190 |
+
<p className="text-xs uppercase tracking-[0.25em] text-pine/70">{stage.title}</p>
|
| 191 |
+
<p className="mt-4 text-xl font-semibold text-ink">{stage.description}</p>
|
| 192 |
+
<ul className="mt-6 space-y-3 text-sm leading-6 text-ink/70">
|
| 193 |
+
{stage.outcomes.map((outcome) => (
|
| 194 |
+
<li key={outcome} className="flex items-start gap-3">
|
| 195 |
+
<span className="mt-2 h-2 w-2 rounded-full bg-clay" />
|
| 196 |
+
<span>{outcome}</span>
|
| 197 |
+
</li>
|
| 198 |
+
))}
|
| 199 |
+
</ul>
|
| 200 |
+
</article>
|
| 201 |
+
))}
|
| 202 |
+
</div>
|
| 203 |
+
</section>
|
| 204 |
+
|
| 205 |
+
<section className="mx-auto max-w-7xl px-6 py-8 md:px-10 lg:px-12">
|
| 206 |
+
<div className="grid gap-8 rounded-[2.25rem] bg-ink px-7 py-8 text-sand md:px-10 md:py-10 lg:grid-cols-[0.9fr_1.1fr]">
|
| 207 |
+
<div>
|
| 208 |
+
<p className="eyebrow text-sand/65">AI Capabilities</p>
|
| 209 |
+
<h2 className="section-title max-w-md text-sand">Built to demonstrate real agent behavior, not just text generation.</h2>
|
| 210 |
+
</div>
|
| 211 |
+
<div className="grid gap-4 sm:grid-cols-2">
|
| 212 |
+
{capabilities.map((item) => (
|
| 213 |
+
<div key={item} className="rounded-[1.5rem] border border-white/10 bg-white/5 p-5 text-sm leading-6 text-sand/82">
|
| 214 |
+
{item}
|
| 215 |
+
</div>
|
| 216 |
+
))}
|
| 217 |
+
</div>
|
| 218 |
+
</div>
|
| 219 |
+
</section>
|
| 220 |
+
|
| 221 |
+
<DemoConsole />
|
| 222 |
+
|
| 223 |
+
<section id="implementation" className="mx-auto max-w-7xl px-6 py-20 md:px-10 lg:px-12">
|
| 224 |
+
<div className="section-intro">
|
| 225 |
+
<p className="eyebrow">Technical Implementation</p>
|
| 226 |
+
<h2 className="section-title">Full-stack architecture designed for practical deployment.</h2>
|
| 227 |
+
<p className="section-copy">
|
| 228 |
+
The project combines modern frontend delivery, modular backend orchestration, and containerized deployment so the
|
| 229 |
+
system can move cleanly from prototype to production-hosted demo.
|
| 230 |
+
</p>
|
| 231 |
+
</div>
|
| 232 |
+
|
| 233 |
+
<div className="mt-10 grid gap-6 lg:grid-cols-3">
|
| 234 |
+
{stack.map((item) => (
|
| 235 |
+
<div key={item.label} className="rounded-[2rem] border border-black/8 bg-[#fffdf8] p-7 shadow-card">
|
| 236 |
+
<p className="text-sm uppercase tracking-[0.25em] text-wine/70">{item.label}</p>
|
| 237 |
+
<p className="mt-5 text-lg leading-8 text-ink">{item.summary}</p>
|
| 238 |
+
</div>
|
| 239 |
+
))}
|
| 240 |
+
</div>
|
| 241 |
+
|
| 242 |
+
<div className="mt-10 grid gap-6 lg:grid-cols-[0.95fr_1.05fr]">
|
| 243 |
+
<div className="rounded-[2rem] border border-black/8 bg-white/75 p-7 shadow-card backdrop-blur">
|
| 244 |
+
<p className="text-sm uppercase tracking-[0.25em] text-pine/70">Core Features</p>
|
| 245 |
+
<div className="mt-5 grid gap-3 sm:grid-cols-2">
|
| 246 |
+
{features.map((feature) => (
|
| 247 |
+
<div key={feature} className="rounded-[1.35rem] bg-sand px-4 py-4 text-sm leading-6 text-ink">
|
| 248 |
+
{feature}
|
| 249 |
+
</div>
|
| 250 |
+
))}
|
| 251 |
+
</div>
|
| 252 |
+
</div>
|
| 253 |
+
|
| 254 |
+
<div className="rounded-[2rem] border border-black/8 bg-[#f2ede4] p-7 shadow-card">
|
| 255 |
+
<p className="text-sm uppercase tracking-[0.25em] text-wine/70">Workflow Narrative</p>
|
| 256 |
+
<div className="mt-5 space-y-5">
|
| 257 |
+
{[
|
| 258 |
+
"Candidate uploads a PDF resume and submits a target job description.",
|
| 259 |
+
"The parsing and analysis agents extract structure, compare requirements, and identify opportunity gaps.",
|
| 260 |
+
"The optimization agent rewrites weak resume content while the ATS scoring agent quantifies alignment gains.",
|
| 261 |
+
"The cover letter agent completes the package with role-specific messaging for the same job context.",
|
| 262 |
+
].map((step, index) => (
|
| 263 |
+
<div key={step} className="grid grid-cols-[auto_1fr] gap-4">
|
| 264 |
+
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-ink text-sm font-semibold text-sand">
|
| 265 |
+
{index + 1}
|
| 266 |
+
</div>
|
| 267 |
+
<p className="pt-1 text-sm leading-7 text-ink/75">{step}</p>
|
| 268 |
+
</div>
|
| 269 |
+
))}
|
| 270 |
+
</div>
|
| 271 |
+
</div>
|
| 272 |
+
</div>
|
| 273 |
+
</section>
|
| 274 |
+
|
| 275 |
+
<section className="mx-auto max-w-7xl px-6 py-8 md:px-10 lg:px-12">
|
| 276 |
+
<div className="grid gap-8 lg:grid-cols-[0.92fr_1.08fr]">
|
| 277 |
+
<div className="rounded-[2.25rem] bg-pine p-8 text-sand shadow-card">
|
| 278 |
+
<p className="eyebrow text-sand/70">Impact And Value</p>
|
| 279 |
+
<h2 className="section-title max-w-md text-sand">A practical system built around a problem candidates face constantly.</h2>
|
| 280 |
+
<p className="mt-5 max-w-lg text-base leading-8 text-sand/80">
|
| 281 |
+
ResumeAI Pro reduces repetitive resume tailoring work, improves ATS keyword alignment, and simulates how recruiters
|
| 282 |
+
actually review candidate materials before shortlisting.
|
| 283 |
+
</p>
|
| 284 |
+
</div>
|
| 285 |
+
|
| 286 |
+
<div className="rounded-[2.25rem] border border-black/8 bg-white/80 p-8 shadow-card backdrop-blur">
|
| 287 |
+
<p className="text-sm uppercase tracking-[0.25em] text-pine/70">Resume Bullet For Your CV</p>
|
| 288 |
+
<div className="mt-5 rounded-[1.75rem] bg-ink p-6 text-sm leading-7 text-sand">
|
| 289 |
+
Developed a multi-agent AI resume optimization system using OpenAI API, FastAPI, and Next.js; implemented ATS
|
| 290 |
+
scoring, keyword gap analysis, and automated document rewriting with Docker-based deployment on Hugging Face
|
| 291 |
+
Spaces.
|
| 292 |
+
</div>
|
| 293 |
+
<div className="mt-6 grid gap-4 sm:grid-cols-2">
|
| 294 |
+
<div className="rounded-[1.5rem] bg-sand p-5">
|
| 295 |
+
<p className="text-xs uppercase tracking-[0.2em] text-ink/60">Why it stands out</p>
|
| 296 |
+
<p className="mt-3 text-sm leading-7 text-ink/75">
|
| 297 |
+
Demonstrates agent orchestration, production-minded architecture, full-stack execution, and a clear real-world use case.
|
| 298 |
+
</p>
|
| 299 |
+
</div>
|
| 300 |
+
<div className="rounded-[1.5rem] bg-mist p-5">
|
| 301 |
+
<p className="text-xs uppercase tracking-[0.2em] text-ink/60">Next up</p>
|
| 302 |
+
<p className="mt-3 text-sm leading-7 text-ink/75">
|
| 303 |
+
Memory-enabled personalization, multi-job comparisons, recruiter feedback simulation, and stronger scoring models.
|
| 304 |
+
</p>
|
| 305 |
+
</div>
|
| 306 |
+
</div>
|
| 307 |
+
</div>
|
| 308 |
+
</div>
|
| 309 |
+
</section>
|
| 310 |
+
|
| 311 |
+
<section className="mx-auto max-w-7xl px-6 pb-20 pt-10 md:px-10 lg:px-12">
|
| 312 |
+
<div className="rounded-[2.25rem] border border-black/8 bg-white/70 px-8 py-10 shadow-card backdrop-blur">
|
| 313 |
+
<p className="eyebrow">Conclusion</p>
|
| 314 |
+
<h2 className="section-title max-w-3xl">
|
| 315 |
+
ResumeAI Pro positions the builder as someone who can ship practical AI systems with real user value.
|
| 316 |
+
</h2>
|
| 317 |
+
<p className="section-copy max-w-3xl">
|
| 318 |
+
The project demonstrates more than prompt engineering. It shows agent orchestration, structured analysis,
|
| 319 |
+
measurable optimization, and production-minded delivery across the full stack.
|
| 320 |
+
</p>
|
| 321 |
+
</div>
|
| 322 |
+
</section>
|
| 323 |
+
</main>
|
| 324 |
+
);
|
| 325 |
+
}
|
backend/Dockerfile
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.13-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY app ./app
|
| 9 |
+
|
| 10 |
+
EXPOSE 8000
|
| 11 |
+
|
| 12 |
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
backend/app/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
backend/app/agents/analysis.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
from app.schemas import AnalysisResult, ParsingResult
|
| 6 |
+
|
| 7 |
+
STOPWORDS = {
|
| 8 |
+
"about",
|
| 9 |
+
"above",
|
| 10 |
+
"after",
|
| 11 |
+
"again",
|
| 12 |
+
"against",
|
| 13 |
+
"along",
|
| 14 |
+
"also",
|
| 15 |
+
"among",
|
| 16 |
+
"and",
|
| 17 |
+
"are",
|
| 18 |
+
"been",
|
| 19 |
+
"being",
|
| 20 |
+
"build",
|
| 21 |
+
"candidate",
|
| 22 |
+
"clients",
|
| 23 |
+
"company",
|
| 24 |
+
"cross",
|
| 25 |
+
"data",
|
| 26 |
+
"deliver",
|
| 27 |
+
"from",
|
| 28 |
+
"have",
|
| 29 |
+
"into",
|
| 30 |
+
"looking",
|
| 31 |
+
"must",
|
| 32 |
+
"need",
|
| 33 |
+
"requirements",
|
| 34 |
+
"role",
|
| 35 |
+
"team",
|
| 36 |
+
"teams",
|
| 37 |
+
"that",
|
| 38 |
+
"their",
|
| 39 |
+
"then",
|
| 40 |
+
"they",
|
| 41 |
+
"this",
|
| 42 |
+
"with",
|
| 43 |
+
"work",
|
| 44 |
+
"years",
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
ACTION_VERBS = {
|
| 48 |
+
"achieved",
|
| 49 |
+
"accelerated",
|
| 50 |
+
"architected",
|
| 51 |
+
"built",
|
| 52 |
+
"created",
|
| 53 |
+
"delivered",
|
| 54 |
+
"designed",
|
| 55 |
+
"developed",
|
| 56 |
+
"drove",
|
| 57 |
+
"implemented",
|
| 58 |
+
"improved",
|
| 59 |
+
"launched",
|
| 60 |
+
"led",
|
| 61 |
+
"optimized",
|
| 62 |
+
"reduced",
|
| 63 |
+
"scaled",
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
KEY_PHRASES = [
|
| 67 |
+
"fastapi",
|
| 68 |
+
"next.js",
|
| 69 |
+
"tailwind css",
|
| 70 |
+
"openai api",
|
| 71 |
+
"docker",
|
| 72 |
+
"hugging face spaces",
|
| 73 |
+
"machine learning",
|
| 74 |
+
"data analysis",
|
| 75 |
+
"project management",
|
| 76 |
+
"stakeholder communication",
|
| 77 |
+
"api integration",
|
| 78 |
+
"automation",
|
| 79 |
+
"keyword analysis",
|
| 80 |
+
"resume optimization",
|
| 81 |
+
]
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def analyze_resume(parsed: ParsingResult, job_description: str) -> AnalysisResult:
|
| 85 |
+
resume_lower = parsed.normalized_text.lower()
|
| 86 |
+
jd_lower = job_description.lower()
|
| 87 |
+
|
| 88 |
+
keywords = _extract_keywords(jd_lower)
|
| 89 |
+
matched = [keyword for keyword in keywords if keyword in resume_lower]
|
| 90 |
+
missing = [keyword for keyword in keywords if keyword not in resume_lower]
|
| 91 |
+
|
| 92 |
+
bullet_lines = [
|
| 93 |
+
line.strip()
|
| 94 |
+
for line in parsed.normalized_text.splitlines()
|
| 95 |
+
if re.match(r"^([-*\u2022]|\d+\.)\s+", line.strip())
|
| 96 |
+
]
|
| 97 |
+
weak_bullets = [line for line in bullet_lines if _is_weak_bullet(line)]
|
| 98 |
+
|
| 99 |
+
strengths = matched[:5] if matched else ["Transferable experience", "Relevant project foundation"]
|
| 100 |
+
|
| 101 |
+
return AnalysisResult(
|
| 102 |
+
matched_keywords=matched[:10],
|
| 103 |
+
missing_keywords=missing[:10],
|
| 104 |
+
skill_gaps=missing[:6],
|
| 105 |
+
weak_bullets=weak_bullets[:4],
|
| 106 |
+
strengths=strengths,
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _extract_keywords(job_description: str) -> list[str]:
|
| 111 |
+
phrase_hits = [phrase for phrase in KEY_PHRASES if phrase in job_description]
|
| 112 |
+
|
| 113 |
+
raw_words = re.findall(r"[a-zA-Z][a-zA-Z0-9+.#/-]{2,}", job_description)
|
| 114 |
+
filtered_words: list[str] = []
|
| 115 |
+
for word in raw_words:
|
| 116 |
+
normalized = word.lower()
|
| 117 |
+
if normalized in STOPWORDS or normalized.isdigit():
|
| 118 |
+
continue
|
| 119 |
+
if normalized not in filtered_words:
|
| 120 |
+
filtered_words.append(normalized)
|
| 121 |
+
|
| 122 |
+
combined = phrase_hits + filtered_words
|
| 123 |
+
return combined[:18]
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def _is_weak_bullet(line: str) -> bool:
|
| 127 |
+
bullet = re.sub(r"^([-*\u2022]|\d+\.)\s+", "", line.strip()).lower()
|
| 128 |
+
has_metric = bool(re.search(r"\d", bullet) or "%" in bullet)
|
| 129 |
+
starts_with_action = any(bullet.startswith(verb) for verb in ACTION_VERBS)
|
| 130 |
+
return not has_metric or not starts_with_action
|
backend/app/agents/cover_letter.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from app.schemas import AnalysisResult, CoverLetterResult, PipelineRequest
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def generate_cover_letter(request: PipelineRequest, analysis: AnalysisResult) -> CoverLetterResult:
|
| 7 |
+
candidate_name = request.candidate_name or "The candidate"
|
| 8 |
+
role = request.target_role or "the role"
|
| 9 |
+
strengths = ", ".join(analysis.strengths[:3]).lower()
|
| 10 |
+
focus = ", ".join(analysis.missing_keywords[:2]).lower() if analysis.missing_keywords else "priority role requirements"
|
| 11 |
+
|
| 12 |
+
opening = (
|
| 13 |
+
f"{candidate_name} is applying for {role} with a background that aligns closely with "
|
| 14 |
+
f"the role's emphasis on {strengths}."
|
| 15 |
+
)
|
| 16 |
+
body = (
|
| 17 |
+
f"The experience presented in the resume shows practical execution and strong project ownership, "
|
| 18 |
+
f"while the optimized draft now better reflects the hiring team's priorities around {focus}."
|
| 19 |
+
)
|
| 20 |
+
closing = (
|
| 21 |
+
"This combination of relevant experience, sharper positioning, and clearer impact makes the "
|
| 22 |
+
"application stronger for recruiter review and ATS screening."
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
return CoverLetterResult(opening=opening, body=body, closing=closing)
|
backend/app/agents/optimization.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
from app.schemas import AnalysisResult, OptimizationResult, OptimizedBullet, PipelineRequest
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def optimize_resume(request: PipelineRequest, analysis: AnalysisResult) -> OptimizationResult:
|
| 9 |
+
role = request.target_role or "the target role"
|
| 10 |
+
focus_areas = analysis.missing_keywords[:3] or [
|
| 11 |
+
"role-specific keywords",
|
| 12 |
+
"quantified impact",
|
| 13 |
+
"clear ownership signals",
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
bullet_improvements = [
|
| 17 |
+
OptimizedBullet(
|
| 18 |
+
original=bullet,
|
| 19 |
+
rewritten=_rewrite_bullet(bullet, focus_areas),
|
| 20 |
+
)
|
| 21 |
+
for bullet in analysis.weak_bullets[:3]
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
summary = (
|
| 25 |
+
f"Results-oriented candidate prepared for {role}, with resume content refocused around "
|
| 26 |
+
f"{', '.join(focus_areas[:2])} and measurable execution."
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
return OptimizationResult(
|
| 30 |
+
professional_summary=summary,
|
| 31 |
+
bullet_improvements=bullet_improvements,
|
| 32 |
+
focus_areas=focus_areas,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _rewrite_bullet(bullet: str, focus_areas: list[str]) -> str:
|
| 37 |
+
cleaned = re.sub(r"^([-*\u2022]|\d+\.)\s+", "", bullet).strip().rstrip(".")
|
| 38 |
+
emphasis = focus_areas[0] if focus_areas else "business impact"
|
| 39 |
+
return (
|
| 40 |
+
f"Developed and improved {cleaned.lower()}, increasing clarity around {emphasis} "
|
| 41 |
+
"and highlighting measurable ownership across the workflow."
|
| 42 |
+
)
|
backend/app/agents/parsing.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
from app.schemas import ParsingResult
|
| 6 |
+
|
| 7 |
+
COMMON_SECTIONS = {
|
| 8 |
+
"summary",
|
| 9 |
+
"experience",
|
| 10 |
+
"education",
|
| 11 |
+
"skills",
|
| 12 |
+
"projects",
|
| 13 |
+
"certifications",
|
| 14 |
+
"achievements",
|
| 15 |
+
"contact",
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def parse_resume(resume_text: str, resume_source: str = "text") -> ParsingResult:
|
| 20 |
+
lines = [line.strip() for line in resume_text.splitlines() if line.strip()]
|
| 21 |
+
normalized_text = "\n".join(lines)
|
| 22 |
+
bullets = [line for line in lines if re.match(r"^([-*\u2022]|\d+\.)\s+", line)]
|
| 23 |
+
|
| 24 |
+
detected_sections: list[str] = []
|
| 25 |
+
for line in lines:
|
| 26 |
+
cleaned = re.sub(r"[^a-zA-Z ]", "", line.strip().lower().rstrip(":")).strip()
|
| 27 |
+
if cleaned in COMMON_SECTIONS and cleaned not in detected_sections:
|
| 28 |
+
detected_sections.append(cleaned.title())
|
| 29 |
+
|
| 30 |
+
return ParsingResult(
|
| 31 |
+
resume_source=resume_source,
|
| 32 |
+
word_count=len(normalized_text.split()),
|
| 33 |
+
bullet_count=len(bullets),
|
| 34 |
+
detected_sections=detected_sections,
|
| 35 |
+
normalized_text=normalized_text,
|
| 36 |
+
)
|
backend/app/agents/scoring.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from app.schemas import AnalysisResult, AtsScoreResult
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def score_resume(analysis: AnalysisResult) -> AtsScoreResult:
|
| 7 |
+
matched = len(analysis.matched_keywords)
|
| 8 |
+
missing = len(analysis.missing_keywords)
|
| 9 |
+
total = max(matched + missing, 1)
|
| 10 |
+
score = round(35 + (matched / total) * 65)
|
| 11 |
+
|
| 12 |
+
suggestions = []
|
| 13 |
+
if analysis.missing_keywords:
|
| 14 |
+
suggestions.append(f"Add role-specific language for {analysis.missing_keywords[0]}.")
|
| 15 |
+
if analysis.weak_bullets:
|
| 16 |
+
suggestions.append("Rewrite weak bullets with action verbs and measurable outcomes.")
|
| 17 |
+
if not suggestions:
|
| 18 |
+
suggestions.append("Maintain keyword coverage and quantified achievements across all sections.")
|
| 19 |
+
|
| 20 |
+
return AtsScoreResult(
|
| 21 |
+
score=min(score, 98),
|
| 22 |
+
matched_count=matched,
|
| 23 |
+
missing_count=missing,
|
| 24 |
+
suggestions=suggestions,
|
| 25 |
+
)
|
backend/app/config.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import Field
|
| 2 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class Settings(BaseSettings):
|
| 6 |
+
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
| 7 |
+
|
| 8 |
+
app_name: str = "ResumeAI Pro API"
|
| 9 |
+
app_env: str = "development"
|
| 10 |
+
allowed_origins: list[str] = Field(
|
| 11 |
+
default_factory=lambda: [
|
| 12 |
+
"http://localhost:3000",
|
| 13 |
+
"http://127.0.0.1:3000",
|
| 14 |
+
"http://localhost:7860",
|
| 15 |
+
"http://127.0.0.1:7860",
|
| 16 |
+
]
|
| 17 |
+
)
|
| 18 |
+
openai_api_key: str | None = None
|
| 19 |
+
openai_model: str = "gpt-5-mini"
|
| 20 |
+
use_openai_when_available: bool = True
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
settings = Settings()
|
backend/app/main.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from fastapi import FastAPI, File, Form, HTTPException, Response, UploadFile
|
| 4 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
+
|
| 6 |
+
from app.config import settings
|
| 7 |
+
from app.schemas import CoverLetterExportRequest, PipelineRequest, PipelineResponse, ResumeExportRequest
|
| 8 |
+
from app.services.document_export import (
|
| 9 |
+
export_cover_letter_docx,
|
| 10 |
+
export_cover_letter_pdf,
|
| 11 |
+
export_resume_docx,
|
| 12 |
+
export_resume_pdf,
|
| 13 |
+
)
|
| 14 |
+
from app.services.pdf import extract_text_from_pdf
|
| 15 |
+
from app.services.pipeline import run_pipeline
|
| 16 |
+
|
| 17 |
+
app = FastAPI(title=settings.app_name)
|
| 18 |
+
|
| 19 |
+
app.add_middleware(
|
| 20 |
+
CORSMiddleware,
|
| 21 |
+
allow_origins=settings.allowed_origins,
|
| 22 |
+
allow_credentials=True,
|
| 23 |
+
allow_methods=["*"],
|
| 24 |
+
allow_headers=["*"],
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@app.get("/health")
|
| 29 |
+
def healthcheck() -> dict[str, str]:
|
| 30 |
+
return {"status": "ok", "environment": settings.app_env}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@app.post("/api/pipeline", response_model=PipelineResponse)
|
| 34 |
+
def pipeline(payload: PipelineRequest) -> PipelineResponse:
|
| 35 |
+
return run_pipeline(payload)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@app.post("/api/pipeline/upload", response_model=PipelineResponse)
|
| 39 |
+
async def pipeline_upload(
|
| 40 |
+
file: UploadFile = File(...),
|
| 41 |
+
job_description: str = Form(...),
|
| 42 |
+
candidate_name: str | None = Form(default=None),
|
| 43 |
+
target_role: str | None = Form(default=None),
|
| 44 |
+
) -> PipelineResponse:
|
| 45 |
+
filename = (file.filename or "").lower()
|
| 46 |
+
if not filename.endswith(".pdf"):
|
| 47 |
+
raise HTTPException(status_code=400, detail="Please upload a PDF resume.")
|
| 48 |
+
|
| 49 |
+
file_bytes = await file.read()
|
| 50 |
+
if not file_bytes:
|
| 51 |
+
raise HTTPException(status_code=400, detail="The uploaded PDF is empty.")
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
resume_text = extract_text_from_pdf(file_bytes)
|
| 55 |
+
except ValueError as error:
|
| 56 |
+
raise HTTPException(status_code=400, detail=str(error)) from error
|
| 57 |
+
|
| 58 |
+
payload = PipelineRequest(
|
| 59 |
+
candidate_name=candidate_name,
|
| 60 |
+
target_role=target_role,
|
| 61 |
+
resume_text=resume_text,
|
| 62 |
+
job_description=job_description,
|
| 63 |
+
)
|
| 64 |
+
return run_pipeline(payload, resume_source="pdf")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@app.post("/api/export/resume/{format}")
|
| 68 |
+
def export_resume(format: str, payload: ResumeExportRequest) -> Response:
|
| 69 |
+
if format == "docx":
|
| 70 |
+
return Response(
|
| 71 |
+
content=export_resume_docx(payload),
|
| 72 |
+
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
| 73 |
+
headers={"Content-Disposition": 'attachment; filename="optimized-resume.docx"'},
|
| 74 |
+
)
|
| 75 |
+
if format == "pdf":
|
| 76 |
+
return Response(
|
| 77 |
+
content=export_resume_pdf(payload),
|
| 78 |
+
media_type="application/pdf",
|
| 79 |
+
headers={"Content-Disposition": 'attachment; filename="optimized-resume.pdf"'},
|
| 80 |
+
)
|
| 81 |
+
raise HTTPException(status_code=400, detail="Unsupported resume export format.")
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
@app.post("/api/export/cover-letter/{format}")
|
| 85 |
+
def export_cover_letter(format: str, payload: CoverLetterExportRequest) -> Response:
|
| 86 |
+
if format == "docx":
|
| 87 |
+
return Response(
|
| 88 |
+
content=export_cover_letter_docx(payload),
|
| 89 |
+
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
| 90 |
+
headers={"Content-Disposition": 'attachment; filename="cover-letter.docx"'},
|
| 91 |
+
)
|
| 92 |
+
if format == "pdf":
|
| 93 |
+
return Response(
|
| 94 |
+
content=export_cover_letter_pdf(payload),
|
| 95 |
+
media_type="application/pdf",
|
| 96 |
+
headers={"Content-Disposition": 'attachment; filename="cover-letter.pdf"'},
|
| 97 |
+
)
|
| 98 |
+
raise HTTPException(status_code=400, detail="Unsupported cover letter export format.")
|
backend/app/schemas.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from pydantic import BaseModel, Field
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class PipelineRequest(BaseModel):
|
| 7 |
+
candidate_name: str | None = Field(default=None, max_length=80)
|
| 8 |
+
target_role: str | None = Field(default=None, max_length=120)
|
| 9 |
+
resume_text: str = Field(min_length=40)
|
| 10 |
+
job_description: str = Field(min_length=40)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ParsingResult(BaseModel):
|
| 14 |
+
resume_source: str
|
| 15 |
+
word_count: int
|
| 16 |
+
bullet_count: int
|
| 17 |
+
detected_sections: list[str]
|
| 18 |
+
normalized_text: str
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class AnalysisResult(BaseModel):
|
| 22 |
+
matched_keywords: list[str]
|
| 23 |
+
missing_keywords: list[str]
|
| 24 |
+
skill_gaps: list[str]
|
| 25 |
+
weak_bullets: list[str]
|
| 26 |
+
strengths: list[str]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class OptimizedBullet(BaseModel):
|
| 30 |
+
original: str
|
| 31 |
+
rewritten: str
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class OptimizationResult(BaseModel):
|
| 35 |
+
professional_summary: str
|
| 36 |
+
bullet_improvements: list[OptimizedBullet]
|
| 37 |
+
focus_areas: list[str]
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class CoverLetterResult(BaseModel):
|
| 41 |
+
opening: str
|
| 42 |
+
body: str
|
| 43 |
+
closing: str
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class AtsScoreResult(BaseModel):
|
| 47 |
+
score: int
|
| 48 |
+
matched_count: int
|
| 49 |
+
missing_count: int
|
| 50 |
+
suggestions: list[str]
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class PipelineResponse(BaseModel):
|
| 54 |
+
generation_mode: str
|
| 55 |
+
parsing: ParsingResult
|
| 56 |
+
analysis: AnalysisResult
|
| 57 |
+
optimization: OptimizationResult
|
| 58 |
+
cover_letter: CoverLetterResult
|
| 59 |
+
ats_score: AtsScoreResult
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class ResumeExportRequest(BaseModel):
|
| 63 |
+
candidate_name: str | None = None
|
| 64 |
+
target_role: str | None = None
|
| 65 |
+
export_style: str = "editorial"
|
| 66 |
+
source_resume_text: str | None = None
|
| 67 |
+
professional_summary: str
|
| 68 |
+
bullet_improvements: list[OptimizedBullet]
|
| 69 |
+
focus_areas: list[str]
|
| 70 |
+
ats_suggestions: list[str]
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class CoverLetterExportRequest(BaseModel):
|
| 74 |
+
candidate_name: str | None = None
|
| 75 |
+
target_role: str | None = None
|
| 76 |
+
export_style: str = "editorial"
|
| 77 |
+
opening: str
|
| 78 |
+
body: str
|
| 79 |
+
closing: str
|
backend/app/services/document_export.py
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from io import BytesIO
|
| 4 |
+
|
| 5 |
+
from docx import Document
|
| 6 |
+
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
| 7 |
+
from docx.oxml import OxmlElement
|
| 8 |
+
from docx.oxml.ns import qn
|
| 9 |
+
from docx.shared import Inches, Pt, RGBColor
|
| 10 |
+
from reportlab.lib import colors
|
| 11 |
+
from reportlab.lib.enums import TA_CENTER
|
| 12 |
+
from reportlab.lib.pagesizes import A4
|
| 13 |
+
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
| 14 |
+
from reportlab.lib.units import inch
|
| 15 |
+
from reportlab.platypus import HRFlowable, ListFlowable, ListItem, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle
|
| 16 |
+
|
| 17 |
+
from app.schemas import CoverLetterExportRequest, ResumeExportRequest
|
| 18 |
+
|
| 19 |
+
BRAND_INK = colors.HexColor("#10121A")
|
| 20 |
+
BRAND_PINE = colors.HexColor("#21493F")
|
| 21 |
+
BRAND_CLAY = colors.HexColor("#D7A26C")
|
| 22 |
+
BRAND_SAND = colors.HexColor("#F7F1E7")
|
| 23 |
+
BRAND_MUTED = colors.HexColor("#5B625F")
|
| 24 |
+
|
| 25 |
+
TEMPLATE_STYLES = {
|
| 26 |
+
"editorial": {
|
| 27 |
+
"pdf_accent": BRAND_CLAY,
|
| 28 |
+
"pdf_heading": BRAND_PINE,
|
| 29 |
+
"pdf_meta_background": BRAND_SAND,
|
| 30 |
+
"pdf_meta_border": colors.HexColor("#E0D3BE"),
|
| 31 |
+
"pdf_align": TA_CENTER,
|
| 32 |
+
"docx_align": WD_ALIGN_PARAGRAPH.CENTER,
|
| 33 |
+
"docx_title_color": RGBColor(0x10, 0x12, 0x1A),
|
| 34 |
+
"docx_heading_color": RGBColor(0x21, 0x49, 0x3F),
|
| 35 |
+
"docx_eyebrow_color": RGBColor(0x7A, 0x58, 0x3A),
|
| 36 |
+
},
|
| 37 |
+
"corporate": {
|
| 38 |
+
"pdf_accent": BRAND_PINE,
|
| 39 |
+
"pdf_heading": BRAND_INK,
|
| 40 |
+
"pdf_meta_background": colors.HexColor("#EEF3F1"),
|
| 41 |
+
"pdf_meta_border": colors.HexColor("#C6D6D0"),
|
| 42 |
+
"pdf_align": 0,
|
| 43 |
+
"docx_align": WD_ALIGN_PARAGRAPH.LEFT,
|
| 44 |
+
"docx_title_color": RGBColor(0x21, 0x49, 0x3F),
|
| 45 |
+
"docx_heading_color": RGBColor(0x10, 0x12, 0x1A),
|
| 46 |
+
"docx_eyebrow_color": RGBColor(0x21, 0x49, 0x3F),
|
| 47 |
+
},
|
| 48 |
+
"minimal": {
|
| 49 |
+
"pdf_accent": colors.HexColor("#8B8F92"),
|
| 50 |
+
"pdf_heading": BRAND_INK,
|
| 51 |
+
"pdf_meta_background": colors.white,
|
| 52 |
+
"pdf_meta_border": colors.HexColor("#D7D9DB"),
|
| 53 |
+
"pdf_align": 0,
|
| 54 |
+
"docx_align": WD_ALIGN_PARAGRAPH.LEFT,
|
| 55 |
+
"docx_title_color": RGBColor(0x10, 0x12, 0x1A),
|
| 56 |
+
"docx_heading_color": RGBColor(0x10, 0x12, 0x1A),
|
| 57 |
+
"docx_eyebrow_color": RGBColor(0x5B, 0x62, 0x5F),
|
| 58 |
+
},
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def export_resume_docx(payload: ResumeExportRequest) -> bytes:
|
| 63 |
+
document = _create_document(payload.export_style)
|
| 64 |
+
_add_document_header(
|
| 65 |
+
document=document,
|
| 66 |
+
title=payload.candidate_name or "Candidate",
|
| 67 |
+
subtitle=payload.target_role,
|
| 68 |
+
eyebrow="ResumeAI Pro Optimized Resume",
|
| 69 |
+
style_name=payload.export_style,
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
_add_docx_section(document, "Professional Summary", [payload.professional_summary], style_name=payload.export_style, bullets=False)
|
| 73 |
+
_add_docx_section(
|
| 74 |
+
document,
|
| 75 |
+
"Optimized Experience Highlights",
|
| 76 |
+
[item.rewritten for item in payload.bullet_improvements] or ["No optimized bullets were produced for this input."],
|
| 77 |
+
style_name=payload.export_style,
|
| 78 |
+
)
|
| 79 |
+
_add_docx_section(document, "Focus Areas", payload.focus_areas or ["No focus areas returned."], style_name=payload.export_style)
|
| 80 |
+
_add_docx_section(document, "ATS Suggestions", payload.ats_suggestions or ["No ATS suggestions returned."], style_name=payload.export_style)
|
| 81 |
+
|
| 82 |
+
if payload.source_resume_text:
|
| 83 |
+
_add_docx_section(document, "Source Resume Snapshot", [payload.source_resume_text], style_name=payload.export_style, bullets=False)
|
| 84 |
+
|
| 85 |
+
buffer = BytesIO()
|
| 86 |
+
document.save(buffer)
|
| 87 |
+
return buffer.getvalue()
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def export_resume_pdf(payload: ResumeExportRequest) -> bytes:
|
| 91 |
+
title = payload.candidate_name or "Candidate"
|
| 92 |
+
subtitle = payload.target_role or ""
|
| 93 |
+
metadata = [
|
| 94 |
+
("Document", "Optimized Resume"),
|
| 95 |
+
("Source", "ResumeAI Pro"),
|
| 96 |
+
("Role", payload.target_role or "Not specified"),
|
| 97 |
+
]
|
| 98 |
+
sections = [
|
| 99 |
+
("Professional Summary", [payload.professional_summary], False),
|
| 100 |
+
(
|
| 101 |
+
"Optimized Experience Highlights",
|
| 102 |
+
[item.rewritten for item in payload.bullet_improvements] or ["No optimized bullets were produced for this input."],
|
| 103 |
+
True,
|
| 104 |
+
),
|
| 105 |
+
("Focus Areas", payload.focus_areas or ["No focus areas returned."], True),
|
| 106 |
+
("ATS Suggestions", payload.ats_suggestions or ["No ATS suggestions returned."], True),
|
| 107 |
+
]
|
| 108 |
+
if payload.source_resume_text:
|
| 109 |
+
sections.append(("Source Resume Snapshot", [payload.source_resume_text], False))
|
| 110 |
+
|
| 111 |
+
return _build_pdf_document(
|
| 112 |
+
title=title,
|
| 113 |
+
subtitle=subtitle,
|
| 114 |
+
eyebrow="ResumeAI Pro Optimized Resume",
|
| 115 |
+
metadata=metadata,
|
| 116 |
+
sections=sections,
|
| 117 |
+
style_name=payload.export_style,
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def export_cover_letter_docx(payload: CoverLetterExportRequest) -> bytes:
|
| 122 |
+
document = _create_document(payload.export_style)
|
| 123 |
+
_add_document_header(
|
| 124 |
+
document=document,
|
| 125 |
+
title="Cover Letter",
|
| 126 |
+
subtitle=payload.target_role,
|
| 127 |
+
eyebrow=payload.candidate_name or "ResumeAI Pro",
|
| 128 |
+
style_name=payload.export_style,
|
| 129 |
+
)
|
| 130 |
+
config = _style_config(payload.export_style)
|
| 131 |
+
|
| 132 |
+
for paragraph_text in [payload.opening, payload.body, payload.closing]:
|
| 133 |
+
paragraph = document.add_paragraph()
|
| 134 |
+
paragraph.paragraph_format.space_after = Pt(12)
|
| 135 |
+
run = paragraph.add_run(paragraph_text)
|
| 136 |
+
run.font.size = Pt(11.5)
|
| 137 |
+
run.font.color.rgb = config["docx_title_color"]
|
| 138 |
+
|
| 139 |
+
buffer = BytesIO()
|
| 140 |
+
document.save(buffer)
|
| 141 |
+
return buffer.getvalue()
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def export_cover_letter_pdf(payload: CoverLetterExportRequest) -> bytes:
|
| 145 |
+
metadata = [
|
| 146 |
+
("Document", "Cover Letter"),
|
| 147 |
+
("Source", "ResumeAI Pro"),
|
| 148 |
+
("Candidate", payload.candidate_name or "Not specified"),
|
| 149 |
+
]
|
| 150 |
+
return _build_pdf_document(
|
| 151 |
+
title="Cover Letter",
|
| 152 |
+
subtitle=payload.target_role or "",
|
| 153 |
+
eyebrow=payload.candidate_name or "ResumeAI Pro",
|
| 154 |
+
metadata=metadata,
|
| 155 |
+
sections=[
|
| 156 |
+
("Opening", [payload.opening], False),
|
| 157 |
+
("Body", [payload.body], False),
|
| 158 |
+
("Closing", [payload.closing], False),
|
| 159 |
+
],
|
| 160 |
+
style_name=payload.export_style,
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def _create_document(style_name: str) -> Document:
|
| 165 |
+
config = _style_config(style_name)
|
| 166 |
+
document = Document()
|
| 167 |
+
section = document.sections[0]
|
| 168 |
+
section.top_margin = Inches(0.65)
|
| 169 |
+
section.bottom_margin = Inches(0.65)
|
| 170 |
+
section.left_margin = Inches(0.8)
|
| 171 |
+
section.right_margin = Inches(0.8)
|
| 172 |
+
|
| 173 |
+
normal_style = document.styles["Normal"]
|
| 174 |
+
normal_style.font.name = "Aptos"
|
| 175 |
+
normal_style.font.size = Pt(11)
|
| 176 |
+
|
| 177 |
+
title_style = document.styles["Title"]
|
| 178 |
+
title_style.font.name = "Aptos Display"
|
| 179 |
+
title_style.font.size = Pt(22)
|
| 180 |
+
title_style.font.bold = True
|
| 181 |
+
title_style.font.color.rgb = config["docx_title_color"]
|
| 182 |
+
|
| 183 |
+
heading_style = document.styles["Heading 1"]
|
| 184 |
+
heading_style.font.name = "Aptos"
|
| 185 |
+
heading_style.font.size = Pt(13)
|
| 186 |
+
heading_style.font.bold = True
|
| 187 |
+
heading_style.font.color.rgb = config["docx_heading_color"]
|
| 188 |
+
|
| 189 |
+
return document
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _add_document_header(document: Document, title: str, subtitle: str | None, eyebrow: str, style_name: str) -> None:
|
| 193 |
+
config = _style_config(style_name)
|
| 194 |
+
eyebrow_paragraph = document.add_paragraph()
|
| 195 |
+
eyebrow_paragraph.alignment = config["docx_align"]
|
| 196 |
+
eyebrow_run = eyebrow_paragraph.add_run(eyebrow.upper())
|
| 197 |
+
eyebrow_run.font.size = Pt(8.5)
|
| 198 |
+
eyebrow_run.font.bold = True
|
| 199 |
+
eyebrow_run.font.color.rgb = config["docx_eyebrow_color"]
|
| 200 |
+
|
| 201 |
+
title_paragraph = document.add_paragraph(style="Title")
|
| 202 |
+
title_paragraph.alignment = config["docx_align"]
|
| 203 |
+
title_paragraph.add_run(title)
|
| 204 |
+
|
| 205 |
+
if subtitle:
|
| 206 |
+
subtitle_paragraph = document.add_paragraph()
|
| 207 |
+
subtitle_paragraph.alignment = config["docx_align"]
|
| 208 |
+
subtitle_paragraph.paragraph_format.space_after = Pt(10)
|
| 209 |
+
subtitle_run = subtitle_paragraph.add_run(subtitle)
|
| 210 |
+
subtitle_run.font.size = Pt(10.5)
|
| 211 |
+
subtitle_run.font.color.rgb = RGBColor(0x5B, 0x62, 0x5F)
|
| 212 |
+
|
| 213 |
+
divider = document.add_paragraph()
|
| 214 |
+
divider.paragraph_format.space_after = Pt(10)
|
| 215 |
+
_apply_bottom_border(divider, config["docx_eyebrow_color"])
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def _add_docx_section(document: Document, heading: str, items: list[str], style_name: str, bullets: bool = True) -> None:
|
| 219 |
+
config = _style_config(style_name)
|
| 220 |
+
document.add_heading(heading, level=1)
|
| 221 |
+
if bullets:
|
| 222 |
+
for item in items:
|
| 223 |
+
paragraph = document.add_paragraph(style="List Bullet")
|
| 224 |
+
paragraph.paragraph_format.space_after = Pt(4)
|
| 225 |
+
run = paragraph.add_run(item)
|
| 226 |
+
run.font.size = Pt(11)
|
| 227 |
+
run.font.color.rgb = config["docx_title_color"]
|
| 228 |
+
else:
|
| 229 |
+
for item in items:
|
| 230 |
+
paragraph = document.add_paragraph()
|
| 231 |
+
paragraph.paragraph_format.space_after = Pt(10)
|
| 232 |
+
run = paragraph.add_run(item)
|
| 233 |
+
run.font.size = Pt(11)
|
| 234 |
+
run.font.color.rgb = config["docx_title_color"]
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def _apply_bottom_border(paragraph, color: RGBColor) -> None:
|
| 238 |
+
paragraph_format = paragraph._p.get_or_add_pPr()
|
| 239 |
+
border = OxmlElement("w:pBdr")
|
| 240 |
+
bottom = OxmlElement("w:bottom")
|
| 241 |
+
bottom.set(qn("w:val"), "single")
|
| 242 |
+
bottom.set(qn("w:sz"), "6")
|
| 243 |
+
bottom.set(qn("w:space"), "1")
|
| 244 |
+
bottom.set(qn("w:color"), f"{color[0]:02X}{color[1]:02X}{color[2]:02X}")
|
| 245 |
+
border.append(bottom)
|
| 246 |
+
paragraph_format.append(border)
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def _build_pdf_document(
|
| 250 |
+
title: str,
|
| 251 |
+
subtitle: str,
|
| 252 |
+
eyebrow: str,
|
| 253 |
+
metadata: list[tuple[str, str]],
|
| 254 |
+
sections: list[tuple[str, list[str], bool]],
|
| 255 |
+
style_name: str,
|
| 256 |
+
) -> bytes:
|
| 257 |
+
config = _style_config(style_name)
|
| 258 |
+
buffer = BytesIO()
|
| 259 |
+
document = SimpleDocTemplate(
|
| 260 |
+
buffer,
|
| 261 |
+
pagesize=A4,
|
| 262 |
+
rightMargin=0.72 * inch,
|
| 263 |
+
leftMargin=0.72 * inch,
|
| 264 |
+
topMargin=0.82 * inch,
|
| 265 |
+
bottomMargin=0.72 * inch,
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
styles = getSampleStyleSheet()
|
| 269 |
+
eyebrow_style = ParagraphStyle(
|
| 270 |
+
"Eyebrow",
|
| 271 |
+
parent=styles["BodyText"],
|
| 272 |
+
fontName="Helvetica-Bold",
|
| 273 |
+
fontSize=8,
|
| 274 |
+
leading=10,
|
| 275 |
+
textColor=config["pdf_accent"],
|
| 276 |
+
alignment=config["pdf_align"],
|
| 277 |
+
spaceAfter=6,
|
| 278 |
+
)
|
| 279 |
+
title_style = ParagraphStyle(
|
| 280 |
+
"ResumeTitle",
|
| 281 |
+
parent=styles["Title"],
|
| 282 |
+
fontName="Helvetica-Bold",
|
| 283 |
+
fontSize=23,
|
| 284 |
+
leading=27,
|
| 285 |
+
alignment=config["pdf_align"],
|
| 286 |
+
textColor=BRAND_INK,
|
| 287 |
+
spaceAfter=6,
|
| 288 |
+
)
|
| 289 |
+
subtitle_style = ParagraphStyle(
|
| 290 |
+
"ResumeSubtitle",
|
| 291 |
+
parent=styles["BodyText"],
|
| 292 |
+
fontName="Helvetica",
|
| 293 |
+
fontSize=10.5,
|
| 294 |
+
leading=14,
|
| 295 |
+
alignment=config["pdf_align"],
|
| 296 |
+
textColor=BRAND_MUTED,
|
| 297 |
+
spaceAfter=14,
|
| 298 |
+
)
|
| 299 |
+
heading_style = ParagraphStyle(
|
| 300 |
+
"SectionHeading",
|
| 301 |
+
parent=styles["Heading2"],
|
| 302 |
+
fontName="Helvetica-Bold",
|
| 303 |
+
fontSize=11.5,
|
| 304 |
+
leading=14,
|
| 305 |
+
textColor=config["pdf_heading"],
|
| 306 |
+
spaceBefore=8,
|
| 307 |
+
spaceAfter=7,
|
| 308 |
+
)
|
| 309 |
+
body_style = ParagraphStyle(
|
| 310 |
+
"SectionBody",
|
| 311 |
+
parent=styles["BodyText"],
|
| 312 |
+
fontName="Helvetica",
|
| 313 |
+
fontSize=10.4,
|
| 314 |
+
leading=15,
|
| 315 |
+
textColor=BRAND_INK,
|
| 316 |
+
spaceAfter=8,
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
story = [
|
| 320 |
+
Paragraph(eyebrow.upper(), eyebrow_style),
|
| 321 |
+
Paragraph(title, title_style),
|
| 322 |
+
]
|
| 323 |
+
if subtitle:
|
| 324 |
+
story.append(Paragraph(subtitle, subtitle_style))
|
| 325 |
+
story.append(HRFlowable(width="100%", thickness=1, color=config["pdf_accent"], spaceBefore=0, spaceAfter=14))
|
| 326 |
+
story.append(_metadata_table(metadata, style_name))
|
| 327 |
+
story.append(Spacer(1, 12))
|
| 328 |
+
|
| 329 |
+
for heading, items, bullets in sections:
|
| 330 |
+
story.append(Paragraph(heading, heading_style))
|
| 331 |
+
if bullets:
|
| 332 |
+
story.append(
|
| 333 |
+
ListFlowable(
|
| 334 |
+
[
|
| 335 |
+
ListItem(
|
| 336 |
+
Paragraph(item.replace("\n", "<br/>"), body_style),
|
| 337 |
+
bulletColor=config["pdf_heading"],
|
| 338 |
+
)
|
| 339 |
+
for item in items
|
| 340 |
+
],
|
| 341 |
+
bulletType="bullet",
|
| 342 |
+
leftIndent=16,
|
| 343 |
+
)
|
| 344 |
+
)
|
| 345 |
+
else:
|
| 346 |
+
for item in items:
|
| 347 |
+
story.append(Paragraph(item.replace("\n", "<br/>"), body_style))
|
| 348 |
+
story.append(Spacer(1, 8))
|
| 349 |
+
|
| 350 |
+
document.build(
|
| 351 |
+
story,
|
| 352 |
+
onFirstPage=lambda canvas, doc: _draw_page_frame(canvas, doc, style_name),
|
| 353 |
+
onLaterPages=lambda canvas, doc: _draw_page_frame(canvas, doc, style_name),
|
| 354 |
+
)
|
| 355 |
+
return buffer.getvalue()
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def _metadata_table(metadata: list[tuple[str, str]], style_name: str) -> Table:
|
| 359 |
+
config = _style_config(style_name)
|
| 360 |
+
rows = [[f"<b>{label}</b>", value] for label, value in metadata]
|
| 361 |
+
table = Table(rows, colWidths=[1.25 * inch, 4.85 * inch])
|
| 362 |
+
table.setStyle(
|
| 363 |
+
TableStyle(
|
| 364 |
+
[
|
| 365 |
+
("BACKGROUND", (0, 0), (-1, -1), config["pdf_meta_background"]),
|
| 366 |
+
("BOX", (0, 0), (-1, -1), 0.6, config["pdf_meta_border"]),
|
| 367 |
+
("INNERGRID", (0, 0), (-1, -1), 0.35, config["pdf_meta_border"]),
|
| 368 |
+
("TEXTCOLOR", (0, 0), (-1, -1), BRAND_INK),
|
| 369 |
+
("LEFTPADDING", (0, 0), (-1, -1), 10),
|
| 370 |
+
("RIGHTPADDING", (0, 0), (-1, -1), 10),
|
| 371 |
+
("TOPPADDING", (0, 0), (-1, -1), 6),
|
| 372 |
+
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
|
| 373 |
+
]
|
| 374 |
+
)
|
| 375 |
+
)
|
| 376 |
+
return table
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
def _draw_page_frame(canvas, document, style_name: str) -> None:
|
| 380 |
+
config = _style_config(style_name)
|
| 381 |
+
canvas.saveState()
|
| 382 |
+
page_width, page_height = A4
|
| 383 |
+
|
| 384 |
+
canvas.setFillColor(BRAND_INK if style_name != "minimal" else colors.HexColor("#EDEDED"))
|
| 385 |
+
canvas.rect(0, page_height - 18, page_width, 18, stroke=0, fill=1)
|
| 386 |
+
|
| 387 |
+
canvas.setFillColor(config["pdf_accent"])
|
| 388 |
+
canvas.rect(document.leftMargin, page_height - 48, 115, 7, stroke=0, fill=1)
|
| 389 |
+
|
| 390 |
+
canvas.setStrokeColor(config["pdf_meta_border"])
|
| 391 |
+
canvas.line(document.leftMargin, 34, page_width - document.rightMargin, 34)
|
| 392 |
+
|
| 393 |
+
canvas.setFont("Helvetica", 8.5)
|
| 394 |
+
canvas.setFillColor(BRAND_MUTED)
|
| 395 |
+
canvas.drawString(document.leftMargin, 20, "Generated by ResumeAI Pro")
|
| 396 |
+
canvas.drawRightString(page_width - document.rightMargin, 20, f"Page {canvas.getPageNumber()}")
|
| 397 |
+
canvas.restoreState()
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
def _style_config(style_name: str) -> dict:
|
| 401 |
+
return TEMPLATE_STYLES.get(style_name, TEMPLATE_STYLES["editorial"])
|
backend/app/services/openai_generation.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from pydantic import BaseModel
|
| 4 |
+
from openai import OpenAI
|
| 5 |
+
|
| 6 |
+
from app.config import settings
|
| 7 |
+
from app.schemas import AnalysisResult, CoverLetterResult, OptimizationResult, OptimizedBullet, PipelineRequest
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class LLMBulletImprovement(BaseModel):
|
| 11 |
+
original: str
|
| 12 |
+
rewritten: str
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class LLMCoverLetter(BaseModel):
|
| 16 |
+
opening: str
|
| 17 |
+
body: str
|
| 18 |
+
closing: str
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class LLMEnhancementPackage(BaseModel):
|
| 22 |
+
professional_summary: str
|
| 23 |
+
focus_areas: list[str]
|
| 24 |
+
bullet_improvements: list[LLMBulletImprovement]
|
| 25 |
+
cover_letter: LLMCoverLetter
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def generate_openai_enhancements(
|
| 29 |
+
request: PipelineRequest,
|
| 30 |
+
analysis: AnalysisResult,
|
| 31 |
+
) -> tuple[OptimizationResult, CoverLetterResult] | None:
|
| 32 |
+
if not settings.use_openai_when_available or not settings.openai_api_key:
|
| 33 |
+
return None
|
| 34 |
+
|
| 35 |
+
client = OpenAI(api_key=settings.openai_api_key)
|
| 36 |
+
response = client.responses.parse(
|
| 37 |
+
model=settings.openai_model,
|
| 38 |
+
input=[
|
| 39 |
+
{
|
| 40 |
+
"role": "system",
|
| 41 |
+
"content": (
|
| 42 |
+
"You are ResumeAI Pro, an expert resume strategist. Strengthen resume messaging without inventing "
|
| 43 |
+
"employers, projects, tools, or metrics that are not supported by the source material. Keep the "
|
| 44 |
+
"output recruiter-friendly, ATS-aware, and concise."
|
| 45 |
+
),
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"role": "user",
|
| 49 |
+
"content": _build_prompt(request, analysis),
|
| 50 |
+
},
|
| 51 |
+
],
|
| 52 |
+
text_format=LLMEnhancementPackage,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
parsed = response.output_parsed
|
| 56 |
+
if parsed is None:
|
| 57 |
+
return None
|
| 58 |
+
|
| 59 |
+
optimization = OptimizationResult(
|
| 60 |
+
professional_summary=parsed.professional_summary,
|
| 61 |
+
bullet_improvements=[
|
| 62 |
+
OptimizedBullet(original=item.original, rewritten=item.rewritten)
|
| 63 |
+
for item in parsed.bullet_improvements
|
| 64 |
+
],
|
| 65 |
+
focus_areas=parsed.focus_areas,
|
| 66 |
+
)
|
| 67 |
+
cover_letter = CoverLetterResult(
|
| 68 |
+
opening=parsed.cover_letter.opening,
|
| 69 |
+
body=parsed.cover_letter.body,
|
| 70 |
+
closing=parsed.cover_letter.closing,
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
return optimization, cover_letter
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _build_prompt(request: PipelineRequest, analysis: AnalysisResult) -> str:
|
| 77 |
+
role = request.target_role or "the target role"
|
| 78 |
+
candidate_name = request.candidate_name or "the candidate"
|
| 79 |
+
|
| 80 |
+
return f"""
|
| 81 |
+
Candidate name: {candidate_name}
|
| 82 |
+
Target role: {role}
|
| 83 |
+
|
| 84 |
+
Resume text:
|
| 85 |
+
{request.resume_text}
|
| 86 |
+
|
| 87 |
+
Job description:
|
| 88 |
+
{request.job_description}
|
| 89 |
+
|
| 90 |
+
Matched keywords:
|
| 91 |
+
{", ".join(analysis.matched_keywords) or "None"}
|
| 92 |
+
|
| 93 |
+
Missing keywords:
|
| 94 |
+
{", ".join(analysis.missing_keywords) or "None"}
|
| 95 |
+
|
| 96 |
+
Weak bullets:
|
| 97 |
+
{chr(10).join(analysis.weak_bullets) or "None"}
|
| 98 |
+
|
| 99 |
+
Create:
|
| 100 |
+
1. A professional summary tailored to the role
|
| 101 |
+
2. Up to 3 improved resume bullets based on the weak bullets
|
| 102 |
+
3. Three short cover-letter sections: opening, body, closing
|
| 103 |
+
4. A short list of focus areas for the candidate to improve
|
| 104 |
+
""".strip()
|
backend/app/services/pdf.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from io import BytesIO
|
| 4 |
+
|
| 5 |
+
from pypdf import PdfReader
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def extract_text_from_pdf(file_bytes: bytes) -> str:
|
| 9 |
+
reader = PdfReader(BytesIO(file_bytes))
|
| 10 |
+
pages = [page.extract_text() or "" for page in reader.pages]
|
| 11 |
+
text = "\n".join(page.strip() for page in pages if page.strip())
|
| 12 |
+
|
| 13 |
+
if not text:
|
| 14 |
+
raise ValueError("No readable text could be extracted from the uploaded PDF.")
|
| 15 |
+
|
| 16 |
+
return text
|
backend/app/services/pipeline.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
|
| 5 |
+
from app.agents.analysis import analyze_resume
|
| 6 |
+
from app.agents.cover_letter import generate_cover_letter
|
| 7 |
+
from app.agents.optimization import optimize_resume
|
| 8 |
+
from app.agents.parsing import parse_resume
|
| 9 |
+
from app.agents.scoring import score_resume
|
| 10 |
+
from app.services.openai_generation import generate_openai_enhancements
|
| 11 |
+
from app.schemas import PipelineRequest, PipelineResponse
|
| 12 |
+
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def run_pipeline(payload: PipelineRequest, resume_source: str = "text") -> PipelineResponse:
|
| 17 |
+
parsing = parse_resume(payload.resume_text, resume_source=resume_source)
|
| 18 |
+
analysis = analyze_resume(parsing, payload.job_description)
|
| 19 |
+
generation_mode = "heuristic"
|
| 20 |
+
|
| 21 |
+
optimization = optimize_resume(payload, analysis)
|
| 22 |
+
cover_letter = generate_cover_letter(payload, analysis)
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
llm_enhancements = generate_openai_enhancements(payload, analysis)
|
| 26 |
+
if llm_enhancements is not None:
|
| 27 |
+
optimization, cover_letter = llm_enhancements
|
| 28 |
+
generation_mode = "openai"
|
| 29 |
+
except Exception: # noqa: BLE001
|
| 30 |
+
logger.exception("OpenAI enhancement failed; falling back to heuristic pipeline.")
|
| 31 |
+
|
| 32 |
+
ats_score = score_resume(analysis)
|
| 33 |
+
|
| 34 |
+
return PipelineResponse(
|
| 35 |
+
generation_mode=generation_mode,
|
| 36 |
+
parsing=parsing,
|
| 37 |
+
analysis=analysis,
|
| 38 |
+
optimization=optimization,
|
| 39 |
+
cover_letter=cover_letter,
|
| 40 |
+
ats_score=ats_score,
|
| 41 |
+
)
|
backend/requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.115.12
|
| 2 |
+
uvicorn[standard]==0.34.0
|
| 3 |
+
pydantic-settings==2.8.1
|
| 4 |
+
openai==2.30.0
|
| 5 |
+
pypdf==6.9.2
|
| 6 |
+
python-multipart==0.0.22
|
| 7 |
+
python-docx==1.2.0
|
| 8 |
+
reportlab==4.4.10
|
components/demo-console.tsx
ADDED
|
@@ -0,0 +1,672 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import type { FormEvent } from "react";
|
| 4 |
+
import { useState } from "react";
|
| 5 |
+
|
| 6 |
+
type ExportStyle = "editorial" | "corporate" | "minimal";
|
| 7 |
+
|
| 8 |
+
type PipelineResponse = {
|
| 9 |
+
generation_mode: string;
|
| 10 |
+
parsing: {
|
| 11 |
+
resume_source: string;
|
| 12 |
+
};
|
| 13 |
+
analysis: {
|
| 14 |
+
matched_keywords: string[];
|
| 15 |
+
missing_keywords: string[];
|
| 16 |
+
weak_bullets: string[];
|
| 17 |
+
};
|
| 18 |
+
optimization: {
|
| 19 |
+
professional_summary: string;
|
| 20 |
+
focus_areas: string[];
|
| 21 |
+
bullet_improvements: Array<{
|
| 22 |
+
original: string;
|
| 23 |
+
rewritten: string;
|
| 24 |
+
}>;
|
| 25 |
+
};
|
| 26 |
+
cover_letter: {
|
| 27 |
+
opening: string;
|
| 28 |
+
body: string;
|
| 29 |
+
closing: string;
|
| 30 |
+
};
|
| 31 |
+
ats_score: {
|
| 32 |
+
score: number;
|
| 33 |
+
suggestions: string[];
|
| 34 |
+
};
|
| 35 |
+
};
|
| 36 |
+
|
| 37 |
+
const sampleResume = `SUMMARY
|
| 38 |
+
AI-focused full-stack developer building resume and career tools with FastAPI, Next.js, and OpenAI API workflows.
|
| 39 |
+
|
| 40 |
+
EXPERIENCE
|
| 41 |
+
- Built resume tooling for students and job seekers
|
| 42 |
+
- Improved dashboard performance by 28% across multiple user journeys
|
| 43 |
+
- Worked with Python, FastAPI, Next.js, Tailwind CSS, Docker, and REST APIs
|
| 44 |
+
|
| 45 |
+
SKILLS
|
| 46 |
+
Python, FastAPI, Next.js, Tailwind CSS, Docker, OpenAI API, JavaScript`;
|
| 47 |
+
|
| 48 |
+
const sampleJobDescription = `We are hiring an AI Engineer to build automation systems for career products. The ideal candidate has experience with FastAPI, Next.js, OpenAI API, ATS scoring, keyword analysis, stakeholder communication, and Docker-based deployment.`;
|
| 49 |
+
const exportStyles: ExportStyle[] = ["editorial", "corporate", "minimal"];
|
| 50 |
+
|
| 51 |
+
export function DemoConsole() {
|
| 52 |
+
const [candidateName, setCandidateName] = useState("Aarav Sharma");
|
| 53 |
+
const [targetRole, setTargetRole] = useState("AI Engineer");
|
| 54 |
+
const [resumeText, setResumeText] = useState(sampleResume);
|
| 55 |
+
const [jobDescription, setJobDescription] = useState(sampleJobDescription);
|
| 56 |
+
const [resumeFile, setResumeFile] = useState<File | null>(null);
|
| 57 |
+
const [result, setResult] = useState<PipelineResponse | null>(null);
|
| 58 |
+
const [error, setError] = useState<string | null>(null);
|
| 59 |
+
const [isLoading, setIsLoading] = useState(false);
|
| 60 |
+
const [isExporting, setIsExporting] = useState(false);
|
| 61 |
+
const [exportStyle, setExportStyle] = useState<ExportStyle>("editorial");
|
| 62 |
+
const [includeFocusAreas, setIncludeFocusAreas] = useState(true);
|
| 63 |
+
const [includeAtsSuggestions, setIncludeAtsSuggestions] = useState(true);
|
| 64 |
+
const [includeSourceResume, setIncludeSourceResume] = useState(true);
|
| 65 |
+
const [selectedBullets, setSelectedBullets] = useState<Record<number, boolean>>({});
|
| 66 |
+
const [editableSummary, setEditableSummary] = useState("");
|
| 67 |
+
const [editableBullets, setEditableBullets] = useState<Array<{ original: string; rewritten: string }>>([]);
|
| 68 |
+
const [editableFocusAreas, setEditableFocusAreas] = useState<string[]>([]);
|
| 69 |
+
const [editableAtsSuggestions, setEditableAtsSuggestions] = useState<string[]>([]);
|
| 70 |
+
const [editableCoverLetter, setEditableCoverLetter] = useState({
|
| 71 |
+
opening: "",
|
| 72 |
+
body: "",
|
| 73 |
+
closing: "",
|
| 74 |
+
});
|
| 75 |
+
|
| 76 |
+
const selectedBulletImprovements = editableBullets.filter((_, index) => selectedBullets[index] ?? true);
|
| 77 |
+
|
| 78 |
+
const optimizedResumeText = result
|
| 79 |
+
? buildOptimizedResume({
|
| 80 |
+
candidateName,
|
| 81 |
+
targetRole,
|
| 82 |
+
resumeText,
|
| 83 |
+
professionalSummary: editableSummary,
|
| 84 |
+
bulletImprovements: selectedBulletImprovements,
|
| 85 |
+
focusAreas: includeFocusAreas ? editableFocusAreas : [],
|
| 86 |
+
atsSuggestions: includeAtsSuggestions ? editableAtsSuggestions : [],
|
| 87 |
+
includeSourceResume,
|
| 88 |
+
})
|
| 89 |
+
: "";
|
| 90 |
+
|
| 91 |
+
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
| 92 |
+
event.preventDefault();
|
| 93 |
+
setIsLoading(true);
|
| 94 |
+
setError(null);
|
| 95 |
+
|
| 96 |
+
try {
|
| 97 |
+
let response: Response;
|
| 98 |
+
const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL || "";
|
| 99 |
+
|
| 100 |
+
if (resumeFile) {
|
| 101 |
+
const formData = new FormData();
|
| 102 |
+
formData.append("file", resumeFile);
|
| 103 |
+
formData.append("candidate_name", candidateName);
|
| 104 |
+
formData.append("target_role", targetRole);
|
| 105 |
+
formData.append("job_description", jobDescription);
|
| 106 |
+
|
| 107 |
+
response = await fetch(`${apiBaseUrl}/api/pipeline/upload`, {
|
| 108 |
+
method: "POST",
|
| 109 |
+
body: formData,
|
| 110 |
+
});
|
| 111 |
+
} else {
|
| 112 |
+
response = await fetch(`${apiBaseUrl}/api/pipeline`, {
|
| 113 |
+
method: "POST",
|
| 114 |
+
headers: {
|
| 115 |
+
"Content-Type": "application/json",
|
| 116 |
+
},
|
| 117 |
+
body: JSON.stringify({
|
| 118 |
+
candidate_name: candidateName,
|
| 119 |
+
target_role: targetRole,
|
| 120 |
+
resume_text: resumeText,
|
| 121 |
+
job_description: jobDescription,
|
| 122 |
+
}),
|
| 123 |
+
});
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
if (!response.ok) {
|
| 127 |
+
const payload = (await response.json().catch(() => null)) as { detail?: string } | null;
|
| 128 |
+
throw new Error(payload?.detail ?? "The analysis request failed. Start the FastAPI backend and try again.");
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
const data = (await response.json()) as PipelineResponse;
|
| 132 |
+
setResult(data);
|
| 133 |
+
setEditableSummary(data.optimization.professional_summary);
|
| 134 |
+
setEditableBullets(data.optimization.bullet_improvements);
|
| 135 |
+
setEditableFocusAreas(data.optimization.focus_areas);
|
| 136 |
+
setEditableAtsSuggestions(data.ats_score.suggestions);
|
| 137 |
+
setEditableCoverLetter(data.cover_letter);
|
| 138 |
+
setSelectedBullets(
|
| 139 |
+
Object.fromEntries(data.optimization.bullet_improvements.map((_, index) => [index, true])),
|
| 140 |
+
);
|
| 141 |
+
} catch (submitError) {
|
| 142 |
+
const message =
|
| 143 |
+
submitError instanceof Error
|
| 144 |
+
? submitError.message
|
| 145 |
+
: "Something went wrong while contacting the backend.";
|
| 146 |
+
setError(message);
|
| 147 |
+
setResult(null);
|
| 148 |
+
} finally {
|
| 149 |
+
setIsLoading(false);
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
function loadSampleData() {
|
| 154 |
+
setCandidateName("Aarav Sharma");
|
| 155 |
+
setTargetRole("AI Engineer");
|
| 156 |
+
setResumeText(sampleResume);
|
| 157 |
+
setJobDescription(sampleJobDescription);
|
| 158 |
+
setResumeFile(null);
|
| 159 |
+
setError(null);
|
| 160 |
+
setExportStyle("editorial");
|
| 161 |
+
setIncludeFocusAreas(true);
|
| 162 |
+
setIncludeAtsSuggestions(true);
|
| 163 |
+
setIncludeSourceResume(true);
|
| 164 |
+
setEditableSummary("");
|
| 165 |
+
setEditableBullets([]);
|
| 166 |
+
setEditableFocusAreas([]);
|
| 167 |
+
setEditableAtsSuggestions([]);
|
| 168 |
+
setEditableCoverLetter({
|
| 169 |
+
opening: "",
|
| 170 |
+
body: "",
|
| 171 |
+
closing: "",
|
| 172 |
+
});
|
| 173 |
+
setSelectedBullets({});
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
function downloadTextFile(filename: string, content: string) {
|
| 177 |
+
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
| 178 |
+
const url = URL.createObjectURL(blob);
|
| 179 |
+
const anchor = document.createElement("a");
|
| 180 |
+
anchor.href = url;
|
| 181 |
+
anchor.download = filename;
|
| 182 |
+
anchor.click();
|
| 183 |
+
URL.revokeObjectURL(url);
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
async function downloadGeneratedDocument(kind: "resume" | "cover-letter", format: "pdf" | "docx") {
|
| 187 |
+
if (!result) {
|
| 188 |
+
return;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
setIsExporting(true);
|
| 192 |
+
setError(null);
|
| 193 |
+
|
| 194 |
+
try {
|
| 195 |
+
const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL || "";
|
| 196 |
+
const endpoint =
|
| 197 |
+
kind === "resume" ? `/api/export/resume/${format}` : `/api/export/cover-letter/${format}`;
|
| 198 |
+
const payload =
|
| 199 |
+
kind === "resume"
|
| 200 |
+
? {
|
| 201 |
+
candidate_name: candidateName,
|
| 202 |
+
target_role: targetRole,
|
| 203 |
+
export_style: exportStyle,
|
| 204 |
+
source_resume_text: includeSourceResume ? resumeText || "Source resume uploaded as PDF." : null,
|
| 205 |
+
professional_summary: editableSummary,
|
| 206 |
+
bullet_improvements: selectedBulletImprovements,
|
| 207 |
+
focus_areas: includeFocusAreas ? editableFocusAreas : [],
|
| 208 |
+
ats_suggestions: includeAtsSuggestions ? editableAtsSuggestions : [],
|
| 209 |
+
}
|
| 210 |
+
: {
|
| 211 |
+
candidate_name: candidateName,
|
| 212 |
+
target_role: targetRole,
|
| 213 |
+
export_style: exportStyle,
|
| 214 |
+
opening: editableCoverLetter.opening,
|
| 215 |
+
body: editableCoverLetter.body,
|
| 216 |
+
closing: editableCoverLetter.closing,
|
| 217 |
+
};
|
| 218 |
+
|
| 219 |
+
const response = await fetch(`${apiBaseUrl}${endpoint}`, {
|
| 220 |
+
method: "POST",
|
| 221 |
+
headers: {
|
| 222 |
+
"Content-Type": "application/json",
|
| 223 |
+
},
|
| 224 |
+
body: JSON.stringify(payload),
|
| 225 |
+
});
|
| 226 |
+
|
| 227 |
+
if (!response.ok) {
|
| 228 |
+
const payload = (await response.json().catch(() => null)) as { detail?: string } | null;
|
| 229 |
+
throw new Error(payload?.detail ?? "The export request failed.");
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
const blob = await response.blob();
|
| 233 |
+
const url = URL.createObjectURL(blob);
|
| 234 |
+
const anchor = document.createElement("a");
|
| 235 |
+
anchor.href = url;
|
| 236 |
+
anchor.download = kind === "resume" ? `optimized-resume.${format}` : `cover-letter.${format}`;
|
| 237 |
+
anchor.click();
|
| 238 |
+
URL.revokeObjectURL(url);
|
| 239 |
+
} catch (exportError) {
|
| 240 |
+
const message =
|
| 241 |
+
exportError instanceof Error ? exportError.message : "Something went wrong while exporting the document.";
|
| 242 |
+
setError(message);
|
| 243 |
+
} finally {
|
| 244 |
+
setIsExporting(false);
|
| 245 |
+
}
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
return (
|
| 249 |
+
<section className="mx-auto max-w-7xl px-6 py-20 md:px-10 lg:px-12">
|
| 250 |
+
<div className="section-intro">
|
| 251 |
+
<p className="eyebrow">Interactive Demo</p>
|
| 252 |
+
<h2 className="section-title">Run the analysis pipeline with a live frontend-to-backend request.</h2>
|
| 253 |
+
<p className="section-copy">
|
| 254 |
+
This demo posts resume text or a PDF resume to the FastAPI pipeline, then turns the response into recruiter-style
|
| 255 |
+
comparisons, downloadable outputs, and a polished cover-letter draft.
|
| 256 |
+
</p>
|
| 257 |
+
</div>
|
| 258 |
+
|
| 259 |
+
<div className="mt-10 grid gap-6 xl:grid-cols-[1.02fr_0.98fr]">
|
| 260 |
+
<form onSubmit={handleSubmit} className="rounded-[2rem] border border-black/8 bg-white/75 p-7 shadow-card backdrop-blur">
|
| 261 |
+
<div className="grid gap-4 md:grid-cols-2">
|
| 262 |
+
<label className="space-y-2 text-sm text-ink/72">
|
| 263 |
+
<span className="font-medium text-ink">Candidate name</span>
|
| 264 |
+
<input
|
| 265 |
+
value={candidateName}
|
| 266 |
+
onChange={(event) => setCandidateName(event.target.value)}
|
| 267 |
+
className="w-full rounded-2xl border border-black/10 bg-white px-4 py-3 text-sm text-ink outline-none transition focus:border-pine"
|
| 268 |
+
/>
|
| 269 |
+
</label>
|
| 270 |
+
|
| 271 |
+
<label className="space-y-2 text-sm text-ink/72">
|
| 272 |
+
<span className="font-medium text-ink">Target role</span>
|
| 273 |
+
<input
|
| 274 |
+
value={targetRole}
|
| 275 |
+
onChange={(event) => setTargetRole(event.target.value)}
|
| 276 |
+
className="w-full rounded-2xl border border-black/10 bg-white px-4 py-3 text-sm text-ink outline-none transition focus:border-pine"
|
| 277 |
+
/>
|
| 278 |
+
</label>
|
| 279 |
+
</div>
|
| 280 |
+
|
| 281 |
+
<div className="mt-5 grid gap-5">
|
| 282 |
+
<label className="space-y-2 text-sm text-ink/72">
|
| 283 |
+
<span className="font-medium text-ink">Resume PDF</span>
|
| 284 |
+
<input
|
| 285 |
+
type="file"
|
| 286 |
+
accept=".pdf,application/pdf"
|
| 287 |
+
onChange={(event) => setResumeFile(event.target.files?.[0] ?? null)}
|
| 288 |
+
className="w-full rounded-2xl border border-black/10 bg-white px-4 py-3 text-sm text-ink outline-none transition file:mr-4 file:rounded-full file:border-0 file:bg-ink file:px-4 file:py-2 file:text-sm file:font-medium file:text-sand"
|
| 289 |
+
/>
|
| 290 |
+
<p className="text-xs leading-6 text-ink/55">
|
| 291 |
+
Upload a PDF to test parsing, or leave this empty and paste resume text below.
|
| 292 |
+
</p>
|
| 293 |
+
</label>
|
| 294 |
+
|
| 295 |
+
<label className="space-y-2 text-sm text-ink/72">
|
| 296 |
+
<span className="font-medium text-ink">Resume text</span>
|
| 297 |
+
<textarea
|
| 298 |
+
value={resumeText}
|
| 299 |
+
onChange={(event) => setResumeText(event.target.value)}
|
| 300 |
+
rows={12}
|
| 301 |
+
placeholder="Paste resume text here if you are not uploading a PDF."
|
| 302 |
+
className="w-full rounded-[1.5rem] border border-black/10 bg-white px-4 py-4 text-sm leading-7 text-ink outline-none transition focus:border-pine"
|
| 303 |
+
/>
|
| 304 |
+
</label>
|
| 305 |
+
|
| 306 |
+
<label className="space-y-2 text-sm text-ink/72">
|
| 307 |
+
<span className="font-medium text-ink">Job description</span>
|
| 308 |
+
<textarea
|
| 309 |
+
value={jobDescription}
|
| 310 |
+
onChange={(event) => setJobDescription(event.target.value)}
|
| 311 |
+
rows={8}
|
| 312 |
+
className="w-full rounded-[1.5rem] border border-black/10 bg-white px-4 py-4 text-sm leading-7 text-ink outline-none transition focus:border-pine"
|
| 313 |
+
/>
|
| 314 |
+
</label>
|
| 315 |
+
</div>
|
| 316 |
+
|
| 317 |
+
<div className="mt-6 flex flex-wrap gap-3">
|
| 318 |
+
<button
|
| 319 |
+
type="submit"
|
| 320 |
+
disabled={isLoading}
|
| 321 |
+
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-sand transition hover:bg-pine disabled:cursor-not-allowed disabled:opacity-70"
|
| 322 |
+
>
|
| 323 |
+
{isLoading ? "Running analysis..." : "Run pipeline"}
|
| 324 |
+
</button>
|
| 325 |
+
<button
|
| 326 |
+
type="button"
|
| 327 |
+
onClick={loadSampleData}
|
| 328 |
+
className="rounded-full border border-ink/15 bg-white px-6 py-3 text-sm font-medium text-ink transition hover:border-ink/30"
|
| 329 |
+
>
|
| 330 |
+
Load sample content
|
| 331 |
+
</button>
|
| 332 |
+
</div>
|
| 333 |
+
|
| 334 |
+
{error ? (
|
| 335 |
+
<div className="mt-5 rounded-[1.5rem] border border-wine/20 bg-[#fff3f0] px-4 py-4 text-sm leading-7 text-wine">
|
| 336 |
+
{error}
|
| 337 |
+
</div>
|
| 338 |
+
) : null}
|
| 339 |
+
</form>
|
| 340 |
+
|
| 341 |
+
<div className="rounded-[2rem] bg-ink p-7 text-sand shadow-card">
|
| 342 |
+
<div className="flex items-center justify-between gap-4 border-b border-white/10 pb-5">
|
| 343 |
+
<div>
|
| 344 |
+
<p className="text-xs uppercase tracking-[0.25em] text-sand/65">Pipeline output</p>
|
| 345 |
+
<h3 className="mt-2 text-2xl font-semibold">Analysis results</h3>
|
| 346 |
+
</div>
|
| 347 |
+
<div className="rounded-full border border-white/10 px-4 py-2 text-xs uppercase tracking-[0.2em] text-sand/70">
|
| 348 |
+
Live API
|
| 349 |
+
</div>
|
| 350 |
+
</div>
|
| 351 |
+
|
| 352 |
+
{result ? (
|
| 353 |
+
<div className="mt-6 space-y-5">
|
| 354 |
+
<div className="rounded-[1.75rem] bg-sand p-5 text-ink">
|
| 355 |
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
| 356 |
+
<p className="text-xs uppercase tracking-[0.2em] text-ink/60">ATS score</p>
|
| 357 |
+
<div className="flex flex-wrap gap-2 text-[11px] uppercase tracking-[0.18em]">
|
| 358 |
+
<span className="rounded-full bg-ink px-3 py-1 text-sand">{result.generation_mode}</span>
|
| 359 |
+
<span className="rounded-full border border-ink/10 px-3 py-1 text-ink/70">{result.parsing.resume_source}</span>
|
| 360 |
+
</div>
|
| 361 |
+
</div>
|
| 362 |
+
<p className="mt-3 text-4xl font-bold">{result.ats_score.score}%</p>
|
| 363 |
+
<textarea
|
| 364 |
+
value={editableSummary}
|
| 365 |
+
onChange={(event) => setEditableSummary(event.target.value)}
|
| 366 |
+
rows={4}
|
| 367 |
+
className="mt-3 w-full rounded-[1.25rem] border border-black/10 bg-white px-4 py-3 text-sm leading-6 text-ink/72 outline-none transition focus:border-pine"
|
| 368 |
+
/>
|
| 369 |
+
|
| 370 |
+
<div className="mt-5 flex flex-wrap gap-3">
|
| 371 |
+
<button
|
| 372 |
+
type="button"
|
| 373 |
+
onClick={() => downloadGeneratedDocument("resume", "pdf")}
|
| 374 |
+
className="rounded-full bg-ink px-4 py-2 text-sm font-medium text-sand transition hover:bg-pine"
|
| 375 |
+
disabled={isExporting}
|
| 376 |
+
>
|
| 377 |
+
{isExporting ? "Preparing..." : "Resume PDF"}
|
| 378 |
+
</button>
|
| 379 |
+
<button
|
| 380 |
+
type="button"
|
| 381 |
+
onClick={() => downloadGeneratedDocument("resume", "docx")}
|
| 382 |
+
className="rounded-full border border-ink/15 bg-white px-4 py-2 text-sm font-medium text-ink transition hover:border-ink/30"
|
| 383 |
+
disabled={isExporting}
|
| 384 |
+
>
|
| 385 |
+
Resume DOCX
|
| 386 |
+
</button>
|
| 387 |
+
<button
|
| 388 |
+
type="button"
|
| 389 |
+
onClick={() => downloadGeneratedDocument("cover-letter", "pdf")}
|
| 390 |
+
className="rounded-full border border-ink/15 bg-white px-4 py-2 text-sm font-medium text-ink transition hover:border-ink/30"
|
| 391 |
+
disabled={isExporting}
|
| 392 |
+
>
|
| 393 |
+
Letter PDF
|
| 394 |
+
</button>
|
| 395 |
+
<button
|
| 396 |
+
type="button"
|
| 397 |
+
onClick={() => downloadGeneratedDocument("cover-letter", "docx")}
|
| 398 |
+
className="rounded-full border border-ink/15 bg-white px-4 py-2 text-sm font-medium text-ink transition hover:border-ink/30"
|
| 399 |
+
disabled={isExporting}
|
| 400 |
+
>
|
| 401 |
+
Letter DOCX
|
| 402 |
+
</button>
|
| 403 |
+
</div>
|
| 404 |
+
</div>
|
| 405 |
+
|
| 406 |
+
<div className="grid gap-4 md:grid-cols-2">
|
| 407 |
+
<div className="rounded-[1.5rem] border border-white/10 bg-white/5 p-5">
|
| 408 |
+
<p className="text-xs uppercase tracking-[0.2em] text-sand/60">Matched keywords</p>
|
| 409 |
+
<div className="mt-3 flex flex-wrap gap-2">
|
| 410 |
+
{result.analysis.matched_keywords.slice(0, 6).map((keyword) => (
|
| 411 |
+
<span key={keyword} className="rounded-full bg-white/10 px-3 py-1 text-xs text-sand/85">
|
| 412 |
+
{keyword}
|
| 413 |
+
</span>
|
| 414 |
+
))}
|
| 415 |
+
</div>
|
| 416 |
+
</div>
|
| 417 |
+
|
| 418 |
+
<div className="rounded-[1.5rem] border border-white/10 bg-white/5 p-5">
|
| 419 |
+
<p className="text-xs uppercase tracking-[0.2em] text-sand/60">Missing keywords</p>
|
| 420 |
+
<div className="mt-3 flex flex-wrap gap-2">
|
| 421 |
+
{result.analysis.missing_keywords.slice(0, 6).map((keyword) => (
|
| 422 |
+
<span key={keyword} className="rounded-full bg-clay px-3 py-1 text-xs text-ink">
|
| 423 |
+
{keyword}
|
| 424 |
+
</span>
|
| 425 |
+
))}
|
| 426 |
+
</div>
|
| 427 |
+
</div>
|
| 428 |
+
</div>
|
| 429 |
+
|
| 430 |
+
<div className="rounded-[1.5rem] border border-white/10 bg-white/5 p-5">
|
| 431 |
+
<p className="text-xs uppercase tracking-[0.2em] text-sand/60">Optimization focus</p>
|
| 432 |
+
<div className="mt-3 space-y-3">
|
| 433 |
+
{editableFocusAreas.map((area, index) => (
|
| 434 |
+
<input
|
| 435 |
+
key={`${area}-${index}`}
|
| 436 |
+
value={area}
|
| 437 |
+
onChange={(event) =>
|
| 438 |
+
setEditableFocusAreas((current) =>
|
| 439 |
+
current.map((item, itemIndex) => (itemIndex === index ? event.target.value : item)),
|
| 440 |
+
)
|
| 441 |
+
}
|
| 442 |
+
className="w-full rounded-xl border border-white/10 bg-white/10 px-3 py-2 text-sm leading-6 text-sand/82 outline-none transition focus:border-white/30"
|
| 443 |
+
/>
|
| 444 |
+
))}
|
| 445 |
+
</div>
|
| 446 |
+
</div>
|
| 447 |
+
|
| 448 |
+
<div className="rounded-[1.5rem] border border-white/10 bg-white/5 p-5">
|
| 449 |
+
<p className="text-xs uppercase tracking-[0.2em] text-sand/60">Export controls</p>
|
| 450 |
+
<div className="mt-4 flex flex-wrap gap-2">
|
| 451 |
+
{exportStyles.map((style) => (
|
| 452 |
+
<button
|
| 453 |
+
key={style}
|
| 454 |
+
type="button"
|
| 455 |
+
onClick={() => setExportStyle(style)}
|
| 456 |
+
className={`rounded-full px-3 py-2 text-xs uppercase tracking-[0.18em] transition ${
|
| 457 |
+
exportStyle === style
|
| 458 |
+
? "bg-sand text-ink"
|
| 459 |
+
: "border border-white/10 bg-white/5 text-sand/78 hover:bg-white/10"
|
| 460 |
+
}`}
|
| 461 |
+
>
|
| 462 |
+
{style}
|
| 463 |
+
</button>
|
| 464 |
+
))}
|
| 465 |
+
</div>
|
| 466 |
+
|
| 467 |
+
<div className="mt-4 grid gap-3 text-sm text-sand/82 sm:grid-cols-2">
|
| 468 |
+
<label className="flex items-center gap-3">
|
| 469 |
+
<input
|
| 470 |
+
type="checkbox"
|
| 471 |
+
checked={includeFocusAreas}
|
| 472 |
+
onChange={(event) => setIncludeFocusAreas(event.target.checked)}
|
| 473 |
+
/>
|
| 474 |
+
<span>Include focus areas</span>
|
| 475 |
+
</label>
|
| 476 |
+
<label className="flex items-center gap-3">
|
| 477 |
+
<input
|
| 478 |
+
type="checkbox"
|
| 479 |
+
checked={includeAtsSuggestions}
|
| 480 |
+
onChange={(event) => setIncludeAtsSuggestions(event.target.checked)}
|
| 481 |
+
/>
|
| 482 |
+
<span>Include ATS suggestions</span>
|
| 483 |
+
</label>
|
| 484 |
+
<label className="flex items-center gap-3 sm:col-span-2">
|
| 485 |
+
<input
|
| 486 |
+
type="checkbox"
|
| 487 |
+
checked={includeSourceResume}
|
| 488 |
+
onChange={(event) => setIncludeSourceResume(event.target.checked)}
|
| 489 |
+
/>
|
| 490 |
+
<span>Include source resume snapshot</span>
|
| 491 |
+
</label>
|
| 492 |
+
</div>
|
| 493 |
+
</div>
|
| 494 |
+
|
| 495 |
+
<div className="rounded-[1.5rem] border border-white/10 bg-white/5 p-5">
|
| 496 |
+
<p className="text-xs uppercase tracking-[0.2em] text-sand/60">ATS suggestions</p>
|
| 497 |
+
<div className="mt-3 space-y-3">
|
| 498 |
+
{editableAtsSuggestions.map((suggestion, index) => (
|
| 499 |
+
<textarea
|
| 500 |
+
key={`${suggestion}-${index}`}
|
| 501 |
+
value={suggestion}
|
| 502 |
+
onChange={(event) =>
|
| 503 |
+
setEditableAtsSuggestions((current) =>
|
| 504 |
+
current.map((item, itemIndex) => (itemIndex === index ? event.target.value : item)),
|
| 505 |
+
)
|
| 506 |
+
}
|
| 507 |
+
rows={2}
|
| 508 |
+
className="w-full rounded-xl border border-white/10 bg-white/10 px-3 py-2 text-sm leading-6 text-sand/82 outline-none transition focus:border-white/30"
|
| 509 |
+
/>
|
| 510 |
+
))}
|
| 511 |
+
</div>
|
| 512 |
+
</div>
|
| 513 |
+
</div>
|
| 514 |
+
) : (
|
| 515 |
+
<div className="mt-6 rounded-[1.75rem] border border-dashed border-white/15 bg-white/5 p-6 text-sm leading-7 text-sand/72">
|
| 516 |
+
Run the pipeline to see matched keywords, missing terms, optimization focus areas, and ATS suggestions here.
|
| 517 |
+
</div>
|
| 518 |
+
)}
|
| 519 |
+
</div>
|
| 520 |
+
</div>
|
| 521 |
+
|
| 522 |
+
{result ? (
|
| 523 |
+
<div className="mt-6 grid gap-6 xl:grid-cols-[1fr_1fr]">
|
| 524 |
+
<div className="rounded-[2rem] border border-black/8 bg-white/80 p-7 shadow-card backdrop-blur">
|
| 525 |
+
<p className="text-sm uppercase tracking-[0.25em] text-pine/70">Before And After</p>
|
| 526 |
+
<div className="mt-5 space-y-4">
|
| 527 |
+
{editableBullets.length > 0 ? (
|
| 528 |
+
editableBullets.map((item, index) => (
|
| 529 |
+
<div key={`${item.original}-${index}`} className="grid gap-4 rounded-[1.5rem] bg-sand p-5 lg:grid-cols-2">
|
| 530 |
+
<div>
|
| 531 |
+
<label className="inline-flex items-center gap-2 rounded-full bg-white px-3 py-1 text-[11px] uppercase tracking-[0.16em] text-ink/65">
|
| 532 |
+
<input
|
| 533 |
+
type="checkbox"
|
| 534 |
+
checked={selectedBullets[index] ?? true}
|
| 535 |
+
onChange={() =>
|
| 536 |
+
setSelectedBullets((current) => ({
|
| 537 |
+
...current,
|
| 538 |
+
[index]: !(current[index] ?? true),
|
| 539 |
+
}))
|
| 540 |
+
}
|
| 541 |
+
/>
|
| 542 |
+
Include in export
|
| 543 |
+
</label>
|
| 544 |
+
<p className="text-xs uppercase tracking-[0.18em] text-ink/55">Original bullet</p>
|
| 545 |
+
<p className="mt-3 text-sm leading-7 text-ink/74">{item.original}</p>
|
| 546 |
+
</div>
|
| 547 |
+
<div>
|
| 548 |
+
<p className="text-xs uppercase tracking-[0.18em] text-ink/55">Optimized bullet</p>
|
| 549 |
+
<textarea
|
| 550 |
+
value={item.rewritten}
|
| 551 |
+
onChange={(event) =>
|
| 552 |
+
setEditableBullets((current) =>
|
| 553 |
+
current.map((bullet, bulletIndex) =>
|
| 554 |
+
bulletIndex === index ? { ...bullet, rewritten: event.target.value } : bullet,
|
| 555 |
+
),
|
| 556 |
+
)
|
| 557 |
+
}
|
| 558 |
+
rows={5}
|
| 559 |
+
className="mt-3 w-full rounded-[1.25rem] border border-black/10 bg-white px-4 py-3 text-sm leading-7 text-ink outline-none transition focus:border-pine"
|
| 560 |
+
/>
|
| 561 |
+
</div>
|
| 562 |
+
</div>
|
| 563 |
+
))
|
| 564 |
+
) : (
|
| 565 |
+
<div className="rounded-[1.5rem] bg-sand p-5 text-sm leading-7 text-ink/72">
|
| 566 |
+
No bullet rewrites were generated for this input. Try a resume with more experience bullets to see a side-by-side comparison.
|
| 567 |
+
</div>
|
| 568 |
+
)}
|
| 569 |
+
</div>
|
| 570 |
+
</div>
|
| 571 |
+
|
| 572 |
+
<div className="rounded-[2rem] border border-black/8 bg-white/80 p-7 shadow-card backdrop-blur">
|
| 573 |
+
<p className="text-sm uppercase tracking-[0.25em] text-pine/70">Generated Cover Letter</p>
|
| 574 |
+
<div className="mt-5 rounded-[1.5rem] bg-[#f4eee3] p-6">
|
| 575 |
+
<textarea
|
| 576 |
+
value={editableCoverLetter.opening}
|
| 577 |
+
onChange={(event) =>
|
| 578 |
+
setEditableCoverLetter((current) => ({
|
| 579 |
+
...current,
|
| 580 |
+
opening: event.target.value,
|
| 581 |
+
}))
|
| 582 |
+
}
|
| 583 |
+
rows={4}
|
| 584 |
+
className="w-full rounded-[1.25rem] border border-black/10 bg-white px-4 py-3 text-sm leading-8 text-ink/82 outline-none transition focus:border-pine"
|
| 585 |
+
/>
|
| 586 |
+
<textarea
|
| 587 |
+
value={editableCoverLetter.body}
|
| 588 |
+
onChange={(event) =>
|
| 589 |
+
setEditableCoverLetter((current) => ({
|
| 590 |
+
...current,
|
| 591 |
+
body: event.target.value,
|
| 592 |
+
}))
|
| 593 |
+
}
|
| 594 |
+
rows={6}
|
| 595 |
+
className="mt-4 w-full rounded-[1.25rem] border border-black/10 bg-white px-4 py-3 text-sm leading-8 text-ink/82 outline-none transition focus:border-pine"
|
| 596 |
+
/>
|
| 597 |
+
<textarea
|
| 598 |
+
value={editableCoverLetter.closing}
|
| 599 |
+
onChange={(event) =>
|
| 600 |
+
setEditableCoverLetter((current) => ({
|
| 601 |
+
...current,
|
| 602 |
+
closing: event.target.value,
|
| 603 |
+
}))
|
| 604 |
+
}
|
| 605 |
+
rows={4}
|
| 606 |
+
className="mt-4 w-full rounded-[1.25rem] border border-black/10 bg-white px-4 py-3 text-sm leading-8 text-ink/82 outline-none transition focus:border-pine"
|
| 607 |
+
/>
|
| 608 |
+
</div>
|
| 609 |
+
</div>
|
| 610 |
+
|
| 611 |
+
<div className="rounded-[2rem] border border-black/8 bg-white/80 p-7 shadow-card backdrop-blur xl:col-span-2">
|
| 612 |
+
<div className="flex flex-wrap items-center justify-between gap-4">
|
| 613 |
+
<div>
|
| 614 |
+
<p className="text-sm uppercase tracking-[0.25em] text-pine/70">Optimized Resume Draft</p>
|
| 615 |
+
<p className="mt-2 text-sm leading-7 text-ink/68">
|
| 616 |
+
A simple export-ready draft assembled from the pipeline output for quick iteration or handoff.
|
| 617 |
+
</p>
|
| 618 |
+
</div>
|
| 619 |
+
<button
|
| 620 |
+
type="button"
|
| 621 |
+
onClick={() => downloadTextFile("optimized-resume.txt", optimizedResumeText)}
|
| 622 |
+
className="rounded-full bg-ink px-4 py-2 text-sm font-medium text-sand transition hover:bg-pine"
|
| 623 |
+
>
|
| 624 |
+
Download .txt
|
| 625 |
+
</button>
|
| 626 |
+
</div>
|
| 627 |
+
|
| 628 |
+
<pre className="mt-5 overflow-x-auto rounded-[1.5rem] bg-ink p-6 text-sm leading-7 text-sand whitespace-pre-wrap">
|
| 629 |
+
{optimizedResumeText}
|
| 630 |
+
</pre>
|
| 631 |
+
</div>
|
| 632 |
+
</div>
|
| 633 |
+
) : null}
|
| 634 |
+
</section>
|
| 635 |
+
);
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
function buildOptimizedResume({
|
| 639 |
+
candidateName,
|
| 640 |
+
targetRole,
|
| 641 |
+
resumeText,
|
| 642 |
+
professionalSummary,
|
| 643 |
+
bulletImprovements,
|
| 644 |
+
focusAreas,
|
| 645 |
+
atsSuggestions,
|
| 646 |
+
includeSourceResume,
|
| 647 |
+
}: {
|
| 648 |
+
candidateName: string;
|
| 649 |
+
targetRole: string;
|
| 650 |
+
resumeText: string;
|
| 651 |
+
professionalSummary: string;
|
| 652 |
+
bulletImprovements: Array<{ rewritten: string }>;
|
| 653 |
+
focusAreas: string[];
|
| 654 |
+
atsSuggestions: string[];
|
| 655 |
+
includeSourceResume: boolean;
|
| 656 |
+
}) {
|
| 657 |
+
const sections = [
|
| 658 |
+
candidateName || "Candidate",
|
| 659 |
+
targetRole ? `Target Role: ${targetRole}` : "",
|
| 660 |
+
"",
|
| 661 |
+
"PROFESSIONAL SUMMARY",
|
| 662 |
+
professionalSummary,
|
| 663 |
+
"",
|
| 664 |
+
"OPTIMIZED EXPERIENCE HIGHLIGHTS",
|
| 665 |
+
...(bulletImprovements.length > 0 ? bulletImprovements.map((item) => `- ${item.rewritten}`) : ["- No bullets selected for export."]),
|
| 666 |
+
...(focusAreas.length > 0 ? ["", "FOCUS AREAS", ...focusAreas.map((area) => `- ${area}`)] : []),
|
| 667 |
+
...(atsSuggestions.length > 0 ? ["", "ATS NOTES", ...atsSuggestions.map((suggestion) => `- ${suggestion}`)] : []),
|
| 668 |
+
...(includeSourceResume ? ["", "SOURCE RESUME", resumeText || "Resume text came from uploaded PDF."] : []),
|
| 669 |
+
];
|
| 670 |
+
|
| 671 |
+
return sections.filter((section, index, array) => !(section === "" && array[index - 1] === "")).join("\n");
|
| 672 |
+
}
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
frontend:
|
| 3 |
+
build:
|
| 4 |
+
context: .
|
| 5 |
+
dockerfile: frontend.Dockerfile
|
| 6 |
+
environment:
|
| 7 |
+
BACKEND_API_URL: http://backend:8000
|
| 8 |
+
ports:
|
| 9 |
+
- "3000:3000"
|
| 10 |
+
depends_on:
|
| 11 |
+
backend:
|
| 12 |
+
condition: service_healthy
|
| 13 |
+
healthcheck:
|
| 14 |
+
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
| 15 |
+
interval: 20s
|
| 16 |
+
timeout: 5s
|
| 17 |
+
retries: 5
|
| 18 |
+
|
| 19 |
+
backend:
|
| 20 |
+
build:
|
| 21 |
+
context: ./backend
|
| 22 |
+
dockerfile: Dockerfile
|
| 23 |
+
environment:
|
| 24 |
+
APP_ENV: development
|
| 25 |
+
ALLOWED_ORIGINS: '["http://localhost:3000","http://127.0.0.1:3000"]'
|
| 26 |
+
ports:
|
| 27 |
+
- "8000:8000"
|
| 28 |
+
healthcheck:
|
| 29 |
+
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"]
|
| 30 |
+
interval: 20s
|
| 31 |
+
timeout: 5s
|
| 32 |
+
retries: 5
|
frontend.Dockerfile
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:22-bookworm-slim AS builder
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
ENV NEXT_TELEMETRY_DISABLED=1
|
| 6 |
+
ENV BACKEND_API_URL=http://backend:8000
|
| 7 |
+
|
| 8 |
+
COPY package.json package-lock.json ./
|
| 9 |
+
RUN npm ci
|
| 10 |
+
|
| 11 |
+
COPY app ./app
|
| 12 |
+
COPY components ./components
|
| 13 |
+
COPY public ./public
|
| 14 |
+
COPY next.config.ts ./
|
| 15 |
+
COPY next-env.d.ts ./
|
| 16 |
+
COPY tsconfig.json ./
|
| 17 |
+
COPY postcss.config.js ./
|
| 18 |
+
COPY tailwind.config.ts ./
|
| 19 |
+
|
| 20 |
+
RUN npm run build
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
FROM node:22-bookworm-slim AS runner
|
| 24 |
+
|
| 25 |
+
WORKDIR /app
|
| 26 |
+
|
| 27 |
+
ENV NODE_ENV=production
|
| 28 |
+
ENV NEXT_TELEMETRY_DISABLED=1
|
| 29 |
+
ENV PORT=3000
|
| 30 |
+
ENV HOSTNAME=0.0.0.0
|
| 31 |
+
|
| 32 |
+
COPY --from=builder /app/.next/standalone ./
|
| 33 |
+
COPY --from=builder /app/.next/static ./.next/static
|
| 34 |
+
COPY --from=builder /app/public ./public
|
| 35 |
+
|
| 36 |
+
EXPOSE 3000
|
| 37 |
+
|
| 38 |
+
CMD ["node", "server.js"]
|
next-env.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// <reference types="next" />
|
| 2 |
+
/// <reference types="next/image-types/global" />
|
| 3 |
+
/// <reference path="./.next/types/routes.d.ts" />
|
| 4 |
+
|
| 5 |
+
// NOTE: This file should not be edited
|
| 6 |
+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
next.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { NextConfig } from "next";
|
| 2 |
+
|
| 3 |
+
const backendApiUrl = process.env.BACKEND_API_URL || "http://127.0.0.1:8000";
|
| 4 |
+
|
| 5 |
+
const nextConfig: NextConfig = {
|
| 6 |
+
reactStrictMode: true,
|
| 7 |
+
output: "standalone",
|
| 8 |
+
async rewrites() {
|
| 9 |
+
return [
|
| 10 |
+
{
|
| 11 |
+
source: "/api/:path*",
|
| 12 |
+
destination: `${backendApiUrl}/api/:path*`,
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
source: "/healthz",
|
| 16 |
+
destination: `${backendApiUrl}/health`,
|
| 17 |
+
},
|
| 18 |
+
];
|
| 19 |
+
},
|
| 20 |
+
};
|
| 21 |
+
|
| 22 |
+
export default nextConfig;
|
package-lock.json
ADDED
|
@@ -0,0 +1,2165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "resumeai-pro",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "resumeai-pro",
|
| 9 |
+
"version": "0.1.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"next": "15.5.14",
|
| 12 |
+
"react": "19.1.0",
|
| 13 |
+
"react-dom": "19.1.0"
|
| 14 |
+
},
|
| 15 |
+
"devDependencies": {
|
| 16 |
+
"@types/node": "22.14.1",
|
| 17 |
+
"@types/react": "19.1.1",
|
| 18 |
+
"@types/react-dom": "19.1.2",
|
| 19 |
+
"autoprefixer": "10.4.21",
|
| 20 |
+
"postcss": "8.5.3",
|
| 21 |
+
"tailwindcss": "3.4.17",
|
| 22 |
+
"typescript": "5.8.3"
|
| 23 |
+
}
|
| 24 |
+
},
|
| 25 |
+
"node_modules/@alloc/quick-lru": {
|
| 26 |
+
"version": "5.2.0",
|
| 27 |
+
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
| 28 |
+
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
| 29 |
+
"dev": true,
|
| 30 |
+
"license": "MIT",
|
| 31 |
+
"engines": {
|
| 32 |
+
"node": ">=10"
|
| 33 |
+
},
|
| 34 |
+
"funding": {
|
| 35 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
"node_modules/@emnapi/runtime": {
|
| 39 |
+
"version": "1.9.1",
|
| 40 |
+
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
|
| 41 |
+
"integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
|
| 42 |
+
"license": "MIT",
|
| 43 |
+
"optional": true,
|
| 44 |
+
"dependencies": {
|
| 45 |
+
"tslib": "^2.4.0"
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
"node_modules/@img/colour": {
|
| 49 |
+
"version": "1.1.0",
|
| 50 |
+
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
| 51 |
+
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
| 52 |
+
"license": "MIT",
|
| 53 |
+
"optional": true,
|
| 54 |
+
"engines": {
|
| 55 |
+
"node": ">=18"
|
| 56 |
+
}
|
| 57 |
+
},
|
| 58 |
+
"node_modules/@img/sharp-darwin-arm64": {
|
| 59 |
+
"version": "0.34.5",
|
| 60 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
|
| 61 |
+
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
|
| 62 |
+
"cpu": [
|
| 63 |
+
"arm64"
|
| 64 |
+
],
|
| 65 |
+
"license": "Apache-2.0",
|
| 66 |
+
"optional": true,
|
| 67 |
+
"os": [
|
| 68 |
+
"darwin"
|
| 69 |
+
],
|
| 70 |
+
"engines": {
|
| 71 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 72 |
+
},
|
| 73 |
+
"funding": {
|
| 74 |
+
"url": "https://opencollective.com/libvips"
|
| 75 |
+
},
|
| 76 |
+
"optionalDependencies": {
|
| 77 |
+
"@img/sharp-libvips-darwin-arm64": "1.2.4"
|
| 78 |
+
}
|
| 79 |
+
},
|
| 80 |
+
"node_modules/@img/sharp-darwin-x64": {
|
| 81 |
+
"version": "0.34.5",
|
| 82 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
|
| 83 |
+
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
|
| 84 |
+
"cpu": [
|
| 85 |
+
"x64"
|
| 86 |
+
],
|
| 87 |
+
"license": "Apache-2.0",
|
| 88 |
+
"optional": true,
|
| 89 |
+
"os": [
|
| 90 |
+
"darwin"
|
| 91 |
+
],
|
| 92 |
+
"engines": {
|
| 93 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 94 |
+
},
|
| 95 |
+
"funding": {
|
| 96 |
+
"url": "https://opencollective.com/libvips"
|
| 97 |
+
},
|
| 98 |
+
"optionalDependencies": {
|
| 99 |
+
"@img/sharp-libvips-darwin-x64": "1.2.4"
|
| 100 |
+
}
|
| 101 |
+
},
|
| 102 |
+
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
| 103 |
+
"version": "1.2.4",
|
| 104 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
|
| 105 |
+
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
|
| 106 |
+
"cpu": [
|
| 107 |
+
"arm64"
|
| 108 |
+
],
|
| 109 |
+
"license": "LGPL-3.0-or-later",
|
| 110 |
+
"optional": true,
|
| 111 |
+
"os": [
|
| 112 |
+
"darwin"
|
| 113 |
+
],
|
| 114 |
+
"funding": {
|
| 115 |
+
"url": "https://opencollective.com/libvips"
|
| 116 |
+
}
|
| 117 |
+
},
|
| 118 |
+
"node_modules/@img/sharp-libvips-darwin-x64": {
|
| 119 |
+
"version": "1.2.4",
|
| 120 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
|
| 121 |
+
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
|
| 122 |
+
"cpu": [
|
| 123 |
+
"x64"
|
| 124 |
+
],
|
| 125 |
+
"license": "LGPL-3.0-or-later",
|
| 126 |
+
"optional": true,
|
| 127 |
+
"os": [
|
| 128 |
+
"darwin"
|
| 129 |
+
],
|
| 130 |
+
"funding": {
|
| 131 |
+
"url": "https://opencollective.com/libvips"
|
| 132 |
+
}
|
| 133 |
+
},
|
| 134 |
+
"node_modules/@img/sharp-libvips-linux-arm": {
|
| 135 |
+
"version": "1.2.4",
|
| 136 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
|
| 137 |
+
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
|
| 138 |
+
"cpu": [
|
| 139 |
+
"arm"
|
| 140 |
+
],
|
| 141 |
+
"libc": [
|
| 142 |
+
"glibc"
|
| 143 |
+
],
|
| 144 |
+
"license": "LGPL-3.0-or-later",
|
| 145 |
+
"optional": true,
|
| 146 |
+
"os": [
|
| 147 |
+
"linux"
|
| 148 |
+
],
|
| 149 |
+
"funding": {
|
| 150 |
+
"url": "https://opencollective.com/libvips"
|
| 151 |
+
}
|
| 152 |
+
},
|
| 153 |
+
"node_modules/@img/sharp-libvips-linux-arm64": {
|
| 154 |
+
"version": "1.2.4",
|
| 155 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
|
| 156 |
+
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
|
| 157 |
+
"cpu": [
|
| 158 |
+
"arm64"
|
| 159 |
+
],
|
| 160 |
+
"libc": [
|
| 161 |
+
"glibc"
|
| 162 |
+
],
|
| 163 |
+
"license": "LGPL-3.0-or-later",
|
| 164 |
+
"optional": true,
|
| 165 |
+
"os": [
|
| 166 |
+
"linux"
|
| 167 |
+
],
|
| 168 |
+
"funding": {
|
| 169 |
+
"url": "https://opencollective.com/libvips"
|
| 170 |
+
}
|
| 171 |
+
},
|
| 172 |
+
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
| 173 |
+
"version": "1.2.4",
|
| 174 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
|
| 175 |
+
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
|
| 176 |
+
"cpu": [
|
| 177 |
+
"ppc64"
|
| 178 |
+
],
|
| 179 |
+
"libc": [
|
| 180 |
+
"glibc"
|
| 181 |
+
],
|
| 182 |
+
"license": "LGPL-3.0-or-later",
|
| 183 |
+
"optional": true,
|
| 184 |
+
"os": [
|
| 185 |
+
"linux"
|
| 186 |
+
],
|
| 187 |
+
"funding": {
|
| 188 |
+
"url": "https://opencollective.com/libvips"
|
| 189 |
+
}
|
| 190 |
+
},
|
| 191 |
+
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
| 192 |
+
"version": "1.2.4",
|
| 193 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
|
| 194 |
+
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
|
| 195 |
+
"cpu": [
|
| 196 |
+
"riscv64"
|
| 197 |
+
],
|
| 198 |
+
"libc": [
|
| 199 |
+
"glibc"
|
| 200 |
+
],
|
| 201 |
+
"license": "LGPL-3.0-or-later",
|
| 202 |
+
"optional": true,
|
| 203 |
+
"os": [
|
| 204 |
+
"linux"
|
| 205 |
+
],
|
| 206 |
+
"funding": {
|
| 207 |
+
"url": "https://opencollective.com/libvips"
|
| 208 |
+
}
|
| 209 |
+
},
|
| 210 |
+
"node_modules/@img/sharp-libvips-linux-s390x": {
|
| 211 |
+
"version": "1.2.4",
|
| 212 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
|
| 213 |
+
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
|
| 214 |
+
"cpu": [
|
| 215 |
+
"s390x"
|
| 216 |
+
],
|
| 217 |
+
"libc": [
|
| 218 |
+
"glibc"
|
| 219 |
+
],
|
| 220 |
+
"license": "LGPL-3.0-or-later",
|
| 221 |
+
"optional": true,
|
| 222 |
+
"os": [
|
| 223 |
+
"linux"
|
| 224 |
+
],
|
| 225 |
+
"funding": {
|
| 226 |
+
"url": "https://opencollective.com/libvips"
|
| 227 |
+
}
|
| 228 |
+
},
|
| 229 |
+
"node_modules/@img/sharp-libvips-linux-x64": {
|
| 230 |
+
"version": "1.2.4",
|
| 231 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
|
| 232 |
+
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
|
| 233 |
+
"cpu": [
|
| 234 |
+
"x64"
|
| 235 |
+
],
|
| 236 |
+
"libc": [
|
| 237 |
+
"glibc"
|
| 238 |
+
],
|
| 239 |
+
"license": "LGPL-3.0-or-later",
|
| 240 |
+
"optional": true,
|
| 241 |
+
"os": [
|
| 242 |
+
"linux"
|
| 243 |
+
],
|
| 244 |
+
"funding": {
|
| 245 |
+
"url": "https://opencollective.com/libvips"
|
| 246 |
+
}
|
| 247 |
+
},
|
| 248 |
+
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
| 249 |
+
"version": "1.2.4",
|
| 250 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
|
| 251 |
+
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
|
| 252 |
+
"cpu": [
|
| 253 |
+
"arm64"
|
| 254 |
+
],
|
| 255 |
+
"libc": [
|
| 256 |
+
"musl"
|
| 257 |
+
],
|
| 258 |
+
"license": "LGPL-3.0-or-later",
|
| 259 |
+
"optional": true,
|
| 260 |
+
"os": [
|
| 261 |
+
"linux"
|
| 262 |
+
],
|
| 263 |
+
"funding": {
|
| 264 |
+
"url": "https://opencollective.com/libvips"
|
| 265 |
+
}
|
| 266 |
+
},
|
| 267 |
+
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
| 268 |
+
"version": "1.2.4",
|
| 269 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
|
| 270 |
+
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
|
| 271 |
+
"cpu": [
|
| 272 |
+
"x64"
|
| 273 |
+
],
|
| 274 |
+
"libc": [
|
| 275 |
+
"musl"
|
| 276 |
+
],
|
| 277 |
+
"license": "LGPL-3.0-or-later",
|
| 278 |
+
"optional": true,
|
| 279 |
+
"os": [
|
| 280 |
+
"linux"
|
| 281 |
+
],
|
| 282 |
+
"funding": {
|
| 283 |
+
"url": "https://opencollective.com/libvips"
|
| 284 |
+
}
|
| 285 |
+
},
|
| 286 |
+
"node_modules/@img/sharp-linux-arm": {
|
| 287 |
+
"version": "0.34.5",
|
| 288 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
|
| 289 |
+
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
|
| 290 |
+
"cpu": [
|
| 291 |
+
"arm"
|
| 292 |
+
],
|
| 293 |
+
"libc": [
|
| 294 |
+
"glibc"
|
| 295 |
+
],
|
| 296 |
+
"license": "Apache-2.0",
|
| 297 |
+
"optional": true,
|
| 298 |
+
"os": [
|
| 299 |
+
"linux"
|
| 300 |
+
],
|
| 301 |
+
"engines": {
|
| 302 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 303 |
+
},
|
| 304 |
+
"funding": {
|
| 305 |
+
"url": "https://opencollective.com/libvips"
|
| 306 |
+
},
|
| 307 |
+
"optionalDependencies": {
|
| 308 |
+
"@img/sharp-libvips-linux-arm": "1.2.4"
|
| 309 |
+
}
|
| 310 |
+
},
|
| 311 |
+
"node_modules/@img/sharp-linux-arm64": {
|
| 312 |
+
"version": "0.34.5",
|
| 313 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
|
| 314 |
+
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
|
| 315 |
+
"cpu": [
|
| 316 |
+
"arm64"
|
| 317 |
+
],
|
| 318 |
+
"libc": [
|
| 319 |
+
"glibc"
|
| 320 |
+
],
|
| 321 |
+
"license": "Apache-2.0",
|
| 322 |
+
"optional": true,
|
| 323 |
+
"os": [
|
| 324 |
+
"linux"
|
| 325 |
+
],
|
| 326 |
+
"engines": {
|
| 327 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 328 |
+
},
|
| 329 |
+
"funding": {
|
| 330 |
+
"url": "https://opencollective.com/libvips"
|
| 331 |
+
},
|
| 332 |
+
"optionalDependencies": {
|
| 333 |
+
"@img/sharp-libvips-linux-arm64": "1.2.4"
|
| 334 |
+
}
|
| 335 |
+
},
|
| 336 |
+
"node_modules/@img/sharp-linux-ppc64": {
|
| 337 |
+
"version": "0.34.5",
|
| 338 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
|
| 339 |
+
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
|
| 340 |
+
"cpu": [
|
| 341 |
+
"ppc64"
|
| 342 |
+
],
|
| 343 |
+
"libc": [
|
| 344 |
+
"glibc"
|
| 345 |
+
],
|
| 346 |
+
"license": "Apache-2.0",
|
| 347 |
+
"optional": true,
|
| 348 |
+
"os": [
|
| 349 |
+
"linux"
|
| 350 |
+
],
|
| 351 |
+
"engines": {
|
| 352 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 353 |
+
},
|
| 354 |
+
"funding": {
|
| 355 |
+
"url": "https://opencollective.com/libvips"
|
| 356 |
+
},
|
| 357 |
+
"optionalDependencies": {
|
| 358 |
+
"@img/sharp-libvips-linux-ppc64": "1.2.4"
|
| 359 |
+
}
|
| 360 |
+
},
|
| 361 |
+
"node_modules/@img/sharp-linux-riscv64": {
|
| 362 |
+
"version": "0.34.5",
|
| 363 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
|
| 364 |
+
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
|
| 365 |
+
"cpu": [
|
| 366 |
+
"riscv64"
|
| 367 |
+
],
|
| 368 |
+
"libc": [
|
| 369 |
+
"glibc"
|
| 370 |
+
],
|
| 371 |
+
"license": "Apache-2.0",
|
| 372 |
+
"optional": true,
|
| 373 |
+
"os": [
|
| 374 |
+
"linux"
|
| 375 |
+
],
|
| 376 |
+
"engines": {
|
| 377 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 378 |
+
},
|
| 379 |
+
"funding": {
|
| 380 |
+
"url": "https://opencollective.com/libvips"
|
| 381 |
+
},
|
| 382 |
+
"optionalDependencies": {
|
| 383 |
+
"@img/sharp-libvips-linux-riscv64": "1.2.4"
|
| 384 |
+
}
|
| 385 |
+
},
|
| 386 |
+
"node_modules/@img/sharp-linux-s390x": {
|
| 387 |
+
"version": "0.34.5",
|
| 388 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
|
| 389 |
+
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
|
| 390 |
+
"cpu": [
|
| 391 |
+
"s390x"
|
| 392 |
+
],
|
| 393 |
+
"libc": [
|
| 394 |
+
"glibc"
|
| 395 |
+
],
|
| 396 |
+
"license": "Apache-2.0",
|
| 397 |
+
"optional": true,
|
| 398 |
+
"os": [
|
| 399 |
+
"linux"
|
| 400 |
+
],
|
| 401 |
+
"engines": {
|
| 402 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 403 |
+
},
|
| 404 |
+
"funding": {
|
| 405 |
+
"url": "https://opencollective.com/libvips"
|
| 406 |
+
},
|
| 407 |
+
"optionalDependencies": {
|
| 408 |
+
"@img/sharp-libvips-linux-s390x": "1.2.4"
|
| 409 |
+
}
|
| 410 |
+
},
|
| 411 |
+
"node_modules/@img/sharp-linux-x64": {
|
| 412 |
+
"version": "0.34.5",
|
| 413 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
|
| 414 |
+
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
|
| 415 |
+
"cpu": [
|
| 416 |
+
"x64"
|
| 417 |
+
],
|
| 418 |
+
"libc": [
|
| 419 |
+
"glibc"
|
| 420 |
+
],
|
| 421 |
+
"license": "Apache-2.0",
|
| 422 |
+
"optional": true,
|
| 423 |
+
"os": [
|
| 424 |
+
"linux"
|
| 425 |
+
],
|
| 426 |
+
"engines": {
|
| 427 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 428 |
+
},
|
| 429 |
+
"funding": {
|
| 430 |
+
"url": "https://opencollective.com/libvips"
|
| 431 |
+
},
|
| 432 |
+
"optionalDependencies": {
|
| 433 |
+
"@img/sharp-libvips-linux-x64": "1.2.4"
|
| 434 |
+
}
|
| 435 |
+
},
|
| 436 |
+
"node_modules/@img/sharp-linuxmusl-arm64": {
|
| 437 |
+
"version": "0.34.5",
|
| 438 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
|
| 439 |
+
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
|
| 440 |
+
"cpu": [
|
| 441 |
+
"arm64"
|
| 442 |
+
],
|
| 443 |
+
"libc": [
|
| 444 |
+
"musl"
|
| 445 |
+
],
|
| 446 |
+
"license": "Apache-2.0",
|
| 447 |
+
"optional": true,
|
| 448 |
+
"os": [
|
| 449 |
+
"linux"
|
| 450 |
+
],
|
| 451 |
+
"engines": {
|
| 452 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 453 |
+
},
|
| 454 |
+
"funding": {
|
| 455 |
+
"url": "https://opencollective.com/libvips"
|
| 456 |
+
},
|
| 457 |
+
"optionalDependencies": {
|
| 458 |
+
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
|
| 459 |
+
}
|
| 460 |
+
},
|
| 461 |
+
"node_modules/@img/sharp-linuxmusl-x64": {
|
| 462 |
+
"version": "0.34.5",
|
| 463 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
|
| 464 |
+
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
|
| 465 |
+
"cpu": [
|
| 466 |
+
"x64"
|
| 467 |
+
],
|
| 468 |
+
"libc": [
|
| 469 |
+
"musl"
|
| 470 |
+
],
|
| 471 |
+
"license": "Apache-2.0",
|
| 472 |
+
"optional": true,
|
| 473 |
+
"os": [
|
| 474 |
+
"linux"
|
| 475 |
+
],
|
| 476 |
+
"engines": {
|
| 477 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 478 |
+
},
|
| 479 |
+
"funding": {
|
| 480 |
+
"url": "https://opencollective.com/libvips"
|
| 481 |
+
},
|
| 482 |
+
"optionalDependencies": {
|
| 483 |
+
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
|
| 484 |
+
}
|
| 485 |
+
},
|
| 486 |
+
"node_modules/@img/sharp-wasm32": {
|
| 487 |
+
"version": "0.34.5",
|
| 488 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
|
| 489 |
+
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
|
| 490 |
+
"cpu": [
|
| 491 |
+
"wasm32"
|
| 492 |
+
],
|
| 493 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
| 494 |
+
"optional": true,
|
| 495 |
+
"dependencies": {
|
| 496 |
+
"@emnapi/runtime": "^1.7.0"
|
| 497 |
+
},
|
| 498 |
+
"engines": {
|
| 499 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 500 |
+
},
|
| 501 |
+
"funding": {
|
| 502 |
+
"url": "https://opencollective.com/libvips"
|
| 503 |
+
}
|
| 504 |
+
},
|
| 505 |
+
"node_modules/@img/sharp-win32-arm64": {
|
| 506 |
+
"version": "0.34.5",
|
| 507 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
|
| 508 |
+
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
|
| 509 |
+
"cpu": [
|
| 510 |
+
"arm64"
|
| 511 |
+
],
|
| 512 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
| 513 |
+
"optional": true,
|
| 514 |
+
"os": [
|
| 515 |
+
"win32"
|
| 516 |
+
],
|
| 517 |
+
"engines": {
|
| 518 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 519 |
+
},
|
| 520 |
+
"funding": {
|
| 521 |
+
"url": "https://opencollective.com/libvips"
|
| 522 |
+
}
|
| 523 |
+
},
|
| 524 |
+
"node_modules/@img/sharp-win32-ia32": {
|
| 525 |
+
"version": "0.34.5",
|
| 526 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
|
| 527 |
+
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
|
| 528 |
+
"cpu": [
|
| 529 |
+
"ia32"
|
| 530 |
+
],
|
| 531 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
| 532 |
+
"optional": true,
|
| 533 |
+
"os": [
|
| 534 |
+
"win32"
|
| 535 |
+
],
|
| 536 |
+
"engines": {
|
| 537 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 538 |
+
},
|
| 539 |
+
"funding": {
|
| 540 |
+
"url": "https://opencollective.com/libvips"
|
| 541 |
+
}
|
| 542 |
+
},
|
| 543 |
+
"node_modules/@img/sharp-win32-x64": {
|
| 544 |
+
"version": "0.34.5",
|
| 545 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
|
| 546 |
+
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
|
| 547 |
+
"cpu": [
|
| 548 |
+
"x64"
|
| 549 |
+
],
|
| 550 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
| 551 |
+
"optional": true,
|
| 552 |
+
"os": [
|
| 553 |
+
"win32"
|
| 554 |
+
],
|
| 555 |
+
"engines": {
|
| 556 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 557 |
+
},
|
| 558 |
+
"funding": {
|
| 559 |
+
"url": "https://opencollective.com/libvips"
|
| 560 |
+
}
|
| 561 |
+
},
|
| 562 |
+
"node_modules/@jridgewell/gen-mapping": {
|
| 563 |
+
"version": "0.3.13",
|
| 564 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 565 |
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 566 |
+
"dev": true,
|
| 567 |
+
"license": "MIT",
|
| 568 |
+
"dependencies": {
|
| 569 |
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
| 570 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 571 |
+
}
|
| 572 |
+
},
|
| 573 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 574 |
+
"version": "3.1.2",
|
| 575 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 576 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 577 |
+
"dev": true,
|
| 578 |
+
"license": "MIT",
|
| 579 |
+
"engines": {
|
| 580 |
+
"node": ">=6.0.0"
|
| 581 |
+
}
|
| 582 |
+
},
|
| 583 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 584 |
+
"version": "1.5.5",
|
| 585 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 586 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 587 |
+
"dev": true,
|
| 588 |
+
"license": "MIT"
|
| 589 |
+
},
|
| 590 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 591 |
+
"version": "0.3.31",
|
| 592 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 593 |
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 594 |
+
"dev": true,
|
| 595 |
+
"license": "MIT",
|
| 596 |
+
"dependencies": {
|
| 597 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
| 598 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 599 |
+
}
|
| 600 |
+
},
|
| 601 |
+
"node_modules/@next/env": {
|
| 602 |
+
"version": "15.5.14",
|
| 603 |
+
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.14.tgz",
|
| 604 |
+
"integrity": "sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA==",
|
| 605 |
+
"license": "MIT"
|
| 606 |
+
},
|
| 607 |
+
"node_modules/@next/swc-darwin-arm64": {
|
| 608 |
+
"version": "15.5.14",
|
| 609 |
+
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.14.tgz",
|
| 610 |
+
"integrity": "sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg==",
|
| 611 |
+
"cpu": [
|
| 612 |
+
"arm64"
|
| 613 |
+
],
|
| 614 |
+
"license": "MIT",
|
| 615 |
+
"optional": true,
|
| 616 |
+
"os": [
|
| 617 |
+
"darwin"
|
| 618 |
+
],
|
| 619 |
+
"engines": {
|
| 620 |
+
"node": ">= 10"
|
| 621 |
+
}
|
| 622 |
+
},
|
| 623 |
+
"node_modules/@next/swc-darwin-x64": {
|
| 624 |
+
"version": "15.5.14",
|
| 625 |
+
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.14.tgz",
|
| 626 |
+
"integrity": "sha512-aNnkSMjSFRTOmkd7qoNI2/rETQm/vKD6c/Ac9BZGa9CtoOzy3c2njgz7LvebQJ8iPxdeTuGnAjagyis8a9ifBw==",
|
| 627 |
+
"cpu": [
|
| 628 |
+
"x64"
|
| 629 |
+
],
|
| 630 |
+
"license": "MIT",
|
| 631 |
+
"optional": true,
|
| 632 |
+
"os": [
|
| 633 |
+
"darwin"
|
| 634 |
+
],
|
| 635 |
+
"engines": {
|
| 636 |
+
"node": ">= 10"
|
| 637 |
+
}
|
| 638 |
+
},
|
| 639 |
+
"node_modules/@next/swc-linux-arm64-gnu": {
|
| 640 |
+
"version": "15.5.14",
|
| 641 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.14.tgz",
|
| 642 |
+
"integrity": "sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==",
|
| 643 |
+
"cpu": [
|
| 644 |
+
"arm64"
|
| 645 |
+
],
|
| 646 |
+
"libc": [
|
| 647 |
+
"glibc"
|
| 648 |
+
],
|
| 649 |
+
"license": "MIT",
|
| 650 |
+
"optional": true,
|
| 651 |
+
"os": [
|
| 652 |
+
"linux"
|
| 653 |
+
],
|
| 654 |
+
"engines": {
|
| 655 |
+
"node": ">= 10"
|
| 656 |
+
}
|
| 657 |
+
},
|
| 658 |
+
"node_modules/@next/swc-linux-arm64-musl": {
|
| 659 |
+
"version": "15.5.14",
|
| 660 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.14.tgz",
|
| 661 |
+
"integrity": "sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==",
|
| 662 |
+
"cpu": [
|
| 663 |
+
"arm64"
|
| 664 |
+
],
|
| 665 |
+
"libc": [
|
| 666 |
+
"musl"
|
| 667 |
+
],
|
| 668 |
+
"license": "MIT",
|
| 669 |
+
"optional": true,
|
| 670 |
+
"os": [
|
| 671 |
+
"linux"
|
| 672 |
+
],
|
| 673 |
+
"engines": {
|
| 674 |
+
"node": ">= 10"
|
| 675 |
+
}
|
| 676 |
+
},
|
| 677 |
+
"node_modules/@next/swc-linux-x64-gnu": {
|
| 678 |
+
"version": "15.5.14",
|
| 679 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.14.tgz",
|
| 680 |
+
"integrity": "sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==",
|
| 681 |
+
"cpu": [
|
| 682 |
+
"x64"
|
| 683 |
+
],
|
| 684 |
+
"libc": [
|
| 685 |
+
"glibc"
|
| 686 |
+
],
|
| 687 |
+
"license": "MIT",
|
| 688 |
+
"optional": true,
|
| 689 |
+
"os": [
|
| 690 |
+
"linux"
|
| 691 |
+
],
|
| 692 |
+
"engines": {
|
| 693 |
+
"node": ">= 10"
|
| 694 |
+
}
|
| 695 |
+
},
|
| 696 |
+
"node_modules/@next/swc-linux-x64-musl": {
|
| 697 |
+
"version": "15.5.14",
|
| 698 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.14.tgz",
|
| 699 |
+
"integrity": "sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==",
|
| 700 |
+
"cpu": [
|
| 701 |
+
"x64"
|
| 702 |
+
],
|
| 703 |
+
"libc": [
|
| 704 |
+
"musl"
|
| 705 |
+
],
|
| 706 |
+
"license": "MIT",
|
| 707 |
+
"optional": true,
|
| 708 |
+
"os": [
|
| 709 |
+
"linux"
|
| 710 |
+
],
|
| 711 |
+
"engines": {
|
| 712 |
+
"node": ">= 10"
|
| 713 |
+
}
|
| 714 |
+
},
|
| 715 |
+
"node_modules/@next/swc-win32-arm64-msvc": {
|
| 716 |
+
"version": "15.5.14",
|
| 717 |
+
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.14.tgz",
|
| 718 |
+
"integrity": "sha512-OTmiBlYThppnvnsqx0rBqjDRemlmIeZ8/o4zI7veaXoeO1PVHoyj2lfTfXTiiGjCyRDhA10y4h6ZvZvBiynr2g==",
|
| 719 |
+
"cpu": [
|
| 720 |
+
"arm64"
|
| 721 |
+
],
|
| 722 |
+
"license": "MIT",
|
| 723 |
+
"optional": true,
|
| 724 |
+
"os": [
|
| 725 |
+
"win32"
|
| 726 |
+
],
|
| 727 |
+
"engines": {
|
| 728 |
+
"node": ">= 10"
|
| 729 |
+
}
|
| 730 |
+
},
|
| 731 |
+
"node_modules/@next/swc-win32-x64-msvc": {
|
| 732 |
+
"version": "15.5.14",
|
| 733 |
+
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.14.tgz",
|
| 734 |
+
"integrity": "sha512-+W7eFf3RS7m4G6tppVTOSyP9Y6FsJXfOuKzav1qKniiFm3KFByQfPEcouHdjlZmysl4zJGuGLQ/M9XyVeyeNEg==",
|
| 735 |
+
"cpu": [
|
| 736 |
+
"x64"
|
| 737 |
+
],
|
| 738 |
+
"license": "MIT",
|
| 739 |
+
"optional": true,
|
| 740 |
+
"os": [
|
| 741 |
+
"win32"
|
| 742 |
+
],
|
| 743 |
+
"engines": {
|
| 744 |
+
"node": ">= 10"
|
| 745 |
+
}
|
| 746 |
+
},
|
| 747 |
+
"node_modules/@nodelib/fs.scandir": {
|
| 748 |
+
"version": "2.1.5",
|
| 749 |
+
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
| 750 |
+
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
| 751 |
+
"dev": true,
|
| 752 |
+
"license": "MIT",
|
| 753 |
+
"dependencies": {
|
| 754 |
+
"@nodelib/fs.stat": "2.0.5",
|
| 755 |
+
"run-parallel": "^1.1.9"
|
| 756 |
+
},
|
| 757 |
+
"engines": {
|
| 758 |
+
"node": ">= 8"
|
| 759 |
+
}
|
| 760 |
+
},
|
| 761 |
+
"node_modules/@nodelib/fs.stat": {
|
| 762 |
+
"version": "2.0.5",
|
| 763 |
+
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
| 764 |
+
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
| 765 |
+
"dev": true,
|
| 766 |
+
"license": "MIT",
|
| 767 |
+
"engines": {
|
| 768 |
+
"node": ">= 8"
|
| 769 |
+
}
|
| 770 |
+
},
|
| 771 |
+
"node_modules/@nodelib/fs.walk": {
|
| 772 |
+
"version": "1.2.8",
|
| 773 |
+
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
| 774 |
+
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
| 775 |
+
"dev": true,
|
| 776 |
+
"license": "MIT",
|
| 777 |
+
"dependencies": {
|
| 778 |
+
"@nodelib/fs.scandir": "2.1.5",
|
| 779 |
+
"fastq": "^1.6.0"
|
| 780 |
+
},
|
| 781 |
+
"engines": {
|
| 782 |
+
"node": ">= 8"
|
| 783 |
+
}
|
| 784 |
+
},
|
| 785 |
+
"node_modules/@swc/helpers": {
|
| 786 |
+
"version": "0.5.15",
|
| 787 |
+
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
| 788 |
+
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
| 789 |
+
"license": "Apache-2.0",
|
| 790 |
+
"dependencies": {
|
| 791 |
+
"tslib": "^2.8.0"
|
| 792 |
+
}
|
| 793 |
+
},
|
| 794 |
+
"node_modules/@types/node": {
|
| 795 |
+
"version": "22.14.1",
|
| 796 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
|
| 797 |
+
"integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==",
|
| 798 |
+
"dev": true,
|
| 799 |
+
"license": "MIT",
|
| 800 |
+
"dependencies": {
|
| 801 |
+
"undici-types": "~6.21.0"
|
| 802 |
+
}
|
| 803 |
+
},
|
| 804 |
+
"node_modules/@types/react": {
|
| 805 |
+
"version": "19.1.1",
|
| 806 |
+
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.1.tgz",
|
| 807 |
+
"integrity": "sha512-ePapxDL7qrgqSF67s0h9m412d9DbXyC1n59O2st+9rjuuamWsZuD2w55rqY12CbzsZ7uVXb5Nw0gEp9Z8MMutQ==",
|
| 808 |
+
"dev": true,
|
| 809 |
+
"license": "MIT",
|
| 810 |
+
"dependencies": {
|
| 811 |
+
"csstype": "^3.0.2"
|
| 812 |
+
}
|
| 813 |
+
},
|
| 814 |
+
"node_modules/@types/react-dom": {
|
| 815 |
+
"version": "19.1.2",
|
| 816 |
+
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz",
|
| 817 |
+
"integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==",
|
| 818 |
+
"dev": true,
|
| 819 |
+
"license": "MIT",
|
| 820 |
+
"peerDependencies": {
|
| 821 |
+
"@types/react": "^19.0.0"
|
| 822 |
+
}
|
| 823 |
+
},
|
| 824 |
+
"node_modules/any-promise": {
|
| 825 |
+
"version": "1.3.0",
|
| 826 |
+
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
| 827 |
+
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
| 828 |
+
"dev": true,
|
| 829 |
+
"license": "MIT"
|
| 830 |
+
},
|
| 831 |
+
"node_modules/anymatch": {
|
| 832 |
+
"version": "3.1.3",
|
| 833 |
+
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
| 834 |
+
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
| 835 |
+
"dev": true,
|
| 836 |
+
"license": "ISC",
|
| 837 |
+
"dependencies": {
|
| 838 |
+
"normalize-path": "^3.0.0",
|
| 839 |
+
"picomatch": "^2.0.4"
|
| 840 |
+
},
|
| 841 |
+
"engines": {
|
| 842 |
+
"node": ">= 8"
|
| 843 |
+
}
|
| 844 |
+
},
|
| 845 |
+
"node_modules/arg": {
|
| 846 |
+
"version": "5.0.2",
|
| 847 |
+
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
| 848 |
+
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
| 849 |
+
"dev": true,
|
| 850 |
+
"license": "MIT"
|
| 851 |
+
},
|
| 852 |
+
"node_modules/autoprefixer": {
|
| 853 |
+
"version": "10.4.21",
|
| 854 |
+
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
| 855 |
+
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
|
| 856 |
+
"dev": true,
|
| 857 |
+
"funding": [
|
| 858 |
+
{
|
| 859 |
+
"type": "opencollective",
|
| 860 |
+
"url": "https://opencollective.com/postcss/"
|
| 861 |
+
},
|
| 862 |
+
{
|
| 863 |
+
"type": "tidelift",
|
| 864 |
+
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
| 865 |
+
},
|
| 866 |
+
{
|
| 867 |
+
"type": "github",
|
| 868 |
+
"url": "https://github.com/sponsors/ai"
|
| 869 |
+
}
|
| 870 |
+
],
|
| 871 |
+
"license": "MIT",
|
| 872 |
+
"dependencies": {
|
| 873 |
+
"browserslist": "^4.24.4",
|
| 874 |
+
"caniuse-lite": "^1.0.30001702",
|
| 875 |
+
"fraction.js": "^4.3.7",
|
| 876 |
+
"normalize-range": "^0.1.2",
|
| 877 |
+
"picocolors": "^1.1.1",
|
| 878 |
+
"postcss-value-parser": "^4.2.0"
|
| 879 |
+
},
|
| 880 |
+
"bin": {
|
| 881 |
+
"autoprefixer": "bin/autoprefixer"
|
| 882 |
+
},
|
| 883 |
+
"engines": {
|
| 884 |
+
"node": "^10 || ^12 || >=14"
|
| 885 |
+
},
|
| 886 |
+
"peerDependencies": {
|
| 887 |
+
"postcss": "^8.1.0"
|
| 888 |
+
}
|
| 889 |
+
},
|
| 890 |
+
"node_modules/baseline-browser-mapping": {
|
| 891 |
+
"version": "2.10.10",
|
| 892 |
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz",
|
| 893 |
+
"integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==",
|
| 894 |
+
"dev": true,
|
| 895 |
+
"license": "Apache-2.0",
|
| 896 |
+
"bin": {
|
| 897 |
+
"baseline-browser-mapping": "dist/cli.cjs"
|
| 898 |
+
},
|
| 899 |
+
"engines": {
|
| 900 |
+
"node": ">=6.0.0"
|
| 901 |
+
}
|
| 902 |
+
},
|
| 903 |
+
"node_modules/binary-extensions": {
|
| 904 |
+
"version": "2.3.0",
|
| 905 |
+
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
| 906 |
+
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
| 907 |
+
"dev": true,
|
| 908 |
+
"license": "MIT",
|
| 909 |
+
"engines": {
|
| 910 |
+
"node": ">=8"
|
| 911 |
+
},
|
| 912 |
+
"funding": {
|
| 913 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 914 |
+
}
|
| 915 |
+
},
|
| 916 |
+
"node_modules/braces": {
|
| 917 |
+
"version": "3.0.3",
|
| 918 |
+
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
| 919 |
+
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
| 920 |
+
"dev": true,
|
| 921 |
+
"license": "MIT",
|
| 922 |
+
"dependencies": {
|
| 923 |
+
"fill-range": "^7.1.1"
|
| 924 |
+
},
|
| 925 |
+
"engines": {
|
| 926 |
+
"node": ">=8"
|
| 927 |
+
}
|
| 928 |
+
},
|
| 929 |
+
"node_modules/browserslist": {
|
| 930 |
+
"version": "4.28.1",
|
| 931 |
+
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
|
| 932 |
+
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
|
| 933 |
+
"dev": true,
|
| 934 |
+
"funding": [
|
| 935 |
+
{
|
| 936 |
+
"type": "opencollective",
|
| 937 |
+
"url": "https://opencollective.com/browserslist"
|
| 938 |
+
},
|
| 939 |
+
{
|
| 940 |
+
"type": "tidelift",
|
| 941 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 942 |
+
},
|
| 943 |
+
{
|
| 944 |
+
"type": "github",
|
| 945 |
+
"url": "https://github.com/sponsors/ai"
|
| 946 |
+
}
|
| 947 |
+
],
|
| 948 |
+
"license": "MIT",
|
| 949 |
+
"dependencies": {
|
| 950 |
+
"baseline-browser-mapping": "^2.9.0",
|
| 951 |
+
"caniuse-lite": "^1.0.30001759",
|
| 952 |
+
"electron-to-chromium": "^1.5.263",
|
| 953 |
+
"node-releases": "^2.0.27",
|
| 954 |
+
"update-browserslist-db": "^1.2.0"
|
| 955 |
+
},
|
| 956 |
+
"bin": {
|
| 957 |
+
"browserslist": "cli.js"
|
| 958 |
+
},
|
| 959 |
+
"engines": {
|
| 960 |
+
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 961 |
+
}
|
| 962 |
+
},
|
| 963 |
+
"node_modules/camelcase-css": {
|
| 964 |
+
"version": "2.0.1",
|
| 965 |
+
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
| 966 |
+
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
| 967 |
+
"dev": true,
|
| 968 |
+
"license": "MIT",
|
| 969 |
+
"engines": {
|
| 970 |
+
"node": ">= 6"
|
| 971 |
+
}
|
| 972 |
+
},
|
| 973 |
+
"node_modules/caniuse-lite": {
|
| 974 |
+
"version": "1.0.30001781",
|
| 975 |
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz",
|
| 976 |
+
"integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==",
|
| 977 |
+
"funding": [
|
| 978 |
+
{
|
| 979 |
+
"type": "opencollective",
|
| 980 |
+
"url": "https://opencollective.com/browserslist"
|
| 981 |
+
},
|
| 982 |
+
{
|
| 983 |
+
"type": "tidelift",
|
| 984 |
+
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
| 985 |
+
},
|
| 986 |
+
{
|
| 987 |
+
"type": "github",
|
| 988 |
+
"url": "https://github.com/sponsors/ai"
|
| 989 |
+
}
|
| 990 |
+
],
|
| 991 |
+
"license": "CC-BY-4.0"
|
| 992 |
+
},
|
| 993 |
+
"node_modules/chokidar": {
|
| 994 |
+
"version": "3.6.0",
|
| 995 |
+
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
| 996 |
+
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
| 997 |
+
"dev": true,
|
| 998 |
+
"license": "MIT",
|
| 999 |
+
"dependencies": {
|
| 1000 |
+
"anymatch": "~3.1.2",
|
| 1001 |
+
"braces": "~3.0.2",
|
| 1002 |
+
"glob-parent": "~5.1.2",
|
| 1003 |
+
"is-binary-path": "~2.1.0",
|
| 1004 |
+
"is-glob": "~4.0.1",
|
| 1005 |
+
"normalize-path": "~3.0.0",
|
| 1006 |
+
"readdirp": "~3.6.0"
|
| 1007 |
+
},
|
| 1008 |
+
"engines": {
|
| 1009 |
+
"node": ">= 8.10.0"
|
| 1010 |
+
},
|
| 1011 |
+
"funding": {
|
| 1012 |
+
"url": "https://paulmillr.com/funding/"
|
| 1013 |
+
},
|
| 1014 |
+
"optionalDependencies": {
|
| 1015 |
+
"fsevents": "~2.3.2"
|
| 1016 |
+
}
|
| 1017 |
+
},
|
| 1018 |
+
"node_modules/chokidar/node_modules/glob-parent": {
|
| 1019 |
+
"version": "5.1.2",
|
| 1020 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
| 1021 |
+
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
| 1022 |
+
"dev": true,
|
| 1023 |
+
"license": "ISC",
|
| 1024 |
+
"dependencies": {
|
| 1025 |
+
"is-glob": "^4.0.1"
|
| 1026 |
+
},
|
| 1027 |
+
"engines": {
|
| 1028 |
+
"node": ">= 6"
|
| 1029 |
+
}
|
| 1030 |
+
},
|
| 1031 |
+
"node_modules/client-only": {
|
| 1032 |
+
"version": "0.0.1",
|
| 1033 |
+
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
| 1034 |
+
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
| 1035 |
+
"license": "MIT"
|
| 1036 |
+
},
|
| 1037 |
+
"node_modules/commander": {
|
| 1038 |
+
"version": "4.1.1",
|
| 1039 |
+
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
| 1040 |
+
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
| 1041 |
+
"dev": true,
|
| 1042 |
+
"license": "MIT",
|
| 1043 |
+
"engines": {
|
| 1044 |
+
"node": ">= 6"
|
| 1045 |
+
}
|
| 1046 |
+
},
|
| 1047 |
+
"node_modules/cssesc": {
|
| 1048 |
+
"version": "3.0.0",
|
| 1049 |
+
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
| 1050 |
+
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
| 1051 |
+
"dev": true,
|
| 1052 |
+
"license": "MIT",
|
| 1053 |
+
"bin": {
|
| 1054 |
+
"cssesc": "bin/cssesc"
|
| 1055 |
+
},
|
| 1056 |
+
"engines": {
|
| 1057 |
+
"node": ">=4"
|
| 1058 |
+
}
|
| 1059 |
+
},
|
| 1060 |
+
"node_modules/csstype": {
|
| 1061 |
+
"version": "3.2.3",
|
| 1062 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
| 1063 |
+
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
| 1064 |
+
"dev": true,
|
| 1065 |
+
"license": "MIT"
|
| 1066 |
+
},
|
| 1067 |
+
"node_modules/detect-libc": {
|
| 1068 |
+
"version": "2.1.2",
|
| 1069 |
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
| 1070 |
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
| 1071 |
+
"license": "Apache-2.0",
|
| 1072 |
+
"optional": true,
|
| 1073 |
+
"engines": {
|
| 1074 |
+
"node": ">=8"
|
| 1075 |
+
}
|
| 1076 |
+
},
|
| 1077 |
+
"node_modules/didyoumean": {
|
| 1078 |
+
"version": "1.2.2",
|
| 1079 |
+
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
| 1080 |
+
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
| 1081 |
+
"dev": true,
|
| 1082 |
+
"license": "Apache-2.0"
|
| 1083 |
+
},
|
| 1084 |
+
"node_modules/dlv": {
|
| 1085 |
+
"version": "1.1.3",
|
| 1086 |
+
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
| 1087 |
+
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
| 1088 |
+
"dev": true,
|
| 1089 |
+
"license": "MIT"
|
| 1090 |
+
},
|
| 1091 |
+
"node_modules/electron-to-chromium": {
|
| 1092 |
+
"version": "1.5.325",
|
| 1093 |
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.325.tgz",
|
| 1094 |
+
"integrity": "sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA==",
|
| 1095 |
+
"dev": true,
|
| 1096 |
+
"license": "ISC"
|
| 1097 |
+
},
|
| 1098 |
+
"node_modules/escalade": {
|
| 1099 |
+
"version": "3.2.0",
|
| 1100 |
+
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 1101 |
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
| 1102 |
+
"dev": true,
|
| 1103 |
+
"license": "MIT",
|
| 1104 |
+
"engines": {
|
| 1105 |
+
"node": ">=6"
|
| 1106 |
+
}
|
| 1107 |
+
},
|
| 1108 |
+
"node_modules/fast-glob": {
|
| 1109 |
+
"version": "3.3.3",
|
| 1110 |
+
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
| 1111 |
+
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
| 1112 |
+
"dev": true,
|
| 1113 |
+
"license": "MIT",
|
| 1114 |
+
"dependencies": {
|
| 1115 |
+
"@nodelib/fs.stat": "^2.0.2",
|
| 1116 |
+
"@nodelib/fs.walk": "^1.2.3",
|
| 1117 |
+
"glob-parent": "^5.1.2",
|
| 1118 |
+
"merge2": "^1.3.0",
|
| 1119 |
+
"micromatch": "^4.0.8"
|
| 1120 |
+
},
|
| 1121 |
+
"engines": {
|
| 1122 |
+
"node": ">=8.6.0"
|
| 1123 |
+
}
|
| 1124 |
+
},
|
| 1125 |
+
"node_modules/fast-glob/node_modules/glob-parent": {
|
| 1126 |
+
"version": "5.1.2",
|
| 1127 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
| 1128 |
+
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
| 1129 |
+
"dev": true,
|
| 1130 |
+
"license": "ISC",
|
| 1131 |
+
"dependencies": {
|
| 1132 |
+
"is-glob": "^4.0.1"
|
| 1133 |
+
},
|
| 1134 |
+
"engines": {
|
| 1135 |
+
"node": ">= 6"
|
| 1136 |
+
}
|
| 1137 |
+
},
|
| 1138 |
+
"node_modules/fastq": {
|
| 1139 |
+
"version": "1.20.1",
|
| 1140 |
+
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
|
| 1141 |
+
"integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
|
| 1142 |
+
"dev": true,
|
| 1143 |
+
"license": "ISC",
|
| 1144 |
+
"dependencies": {
|
| 1145 |
+
"reusify": "^1.0.4"
|
| 1146 |
+
}
|
| 1147 |
+
},
|
| 1148 |
+
"node_modules/fill-range": {
|
| 1149 |
+
"version": "7.1.1",
|
| 1150 |
+
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
| 1151 |
+
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
| 1152 |
+
"dev": true,
|
| 1153 |
+
"license": "MIT",
|
| 1154 |
+
"dependencies": {
|
| 1155 |
+
"to-regex-range": "^5.0.1"
|
| 1156 |
+
},
|
| 1157 |
+
"engines": {
|
| 1158 |
+
"node": ">=8"
|
| 1159 |
+
}
|
| 1160 |
+
},
|
| 1161 |
+
"node_modules/fraction.js": {
|
| 1162 |
+
"version": "4.3.7",
|
| 1163 |
+
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
| 1164 |
+
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
|
| 1165 |
+
"dev": true,
|
| 1166 |
+
"license": "MIT",
|
| 1167 |
+
"engines": {
|
| 1168 |
+
"node": "*"
|
| 1169 |
+
},
|
| 1170 |
+
"funding": {
|
| 1171 |
+
"type": "patreon",
|
| 1172 |
+
"url": "https://github.com/sponsors/rawify"
|
| 1173 |
+
}
|
| 1174 |
+
},
|
| 1175 |
+
"node_modules/fsevents": {
|
| 1176 |
+
"version": "2.3.3",
|
| 1177 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1178 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1179 |
+
"dev": true,
|
| 1180 |
+
"hasInstallScript": true,
|
| 1181 |
+
"license": "MIT",
|
| 1182 |
+
"optional": true,
|
| 1183 |
+
"os": [
|
| 1184 |
+
"darwin"
|
| 1185 |
+
],
|
| 1186 |
+
"engines": {
|
| 1187 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1188 |
+
}
|
| 1189 |
+
},
|
| 1190 |
+
"node_modules/function-bind": {
|
| 1191 |
+
"version": "1.1.2",
|
| 1192 |
+
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 1193 |
+
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
| 1194 |
+
"dev": true,
|
| 1195 |
+
"license": "MIT",
|
| 1196 |
+
"funding": {
|
| 1197 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1198 |
+
}
|
| 1199 |
+
},
|
| 1200 |
+
"node_modules/glob-parent": {
|
| 1201 |
+
"version": "6.0.2",
|
| 1202 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
| 1203 |
+
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
| 1204 |
+
"dev": true,
|
| 1205 |
+
"license": "ISC",
|
| 1206 |
+
"dependencies": {
|
| 1207 |
+
"is-glob": "^4.0.3"
|
| 1208 |
+
},
|
| 1209 |
+
"engines": {
|
| 1210 |
+
"node": ">=10.13.0"
|
| 1211 |
+
}
|
| 1212 |
+
},
|
| 1213 |
+
"node_modules/hasown": {
|
| 1214 |
+
"version": "2.0.2",
|
| 1215 |
+
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
| 1216 |
+
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
| 1217 |
+
"dev": true,
|
| 1218 |
+
"license": "MIT",
|
| 1219 |
+
"dependencies": {
|
| 1220 |
+
"function-bind": "^1.1.2"
|
| 1221 |
+
},
|
| 1222 |
+
"engines": {
|
| 1223 |
+
"node": ">= 0.4"
|
| 1224 |
+
}
|
| 1225 |
+
},
|
| 1226 |
+
"node_modules/is-binary-path": {
|
| 1227 |
+
"version": "2.1.0",
|
| 1228 |
+
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
| 1229 |
+
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
| 1230 |
+
"dev": true,
|
| 1231 |
+
"license": "MIT",
|
| 1232 |
+
"dependencies": {
|
| 1233 |
+
"binary-extensions": "^2.0.0"
|
| 1234 |
+
},
|
| 1235 |
+
"engines": {
|
| 1236 |
+
"node": ">=8"
|
| 1237 |
+
}
|
| 1238 |
+
},
|
| 1239 |
+
"node_modules/is-core-module": {
|
| 1240 |
+
"version": "2.16.1",
|
| 1241 |
+
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
| 1242 |
+
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
| 1243 |
+
"dev": true,
|
| 1244 |
+
"license": "MIT",
|
| 1245 |
+
"dependencies": {
|
| 1246 |
+
"hasown": "^2.0.2"
|
| 1247 |
+
},
|
| 1248 |
+
"engines": {
|
| 1249 |
+
"node": ">= 0.4"
|
| 1250 |
+
},
|
| 1251 |
+
"funding": {
|
| 1252 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1253 |
+
}
|
| 1254 |
+
},
|
| 1255 |
+
"node_modules/is-extglob": {
|
| 1256 |
+
"version": "2.1.1",
|
| 1257 |
+
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
| 1258 |
+
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
| 1259 |
+
"dev": true,
|
| 1260 |
+
"license": "MIT",
|
| 1261 |
+
"engines": {
|
| 1262 |
+
"node": ">=0.10.0"
|
| 1263 |
+
}
|
| 1264 |
+
},
|
| 1265 |
+
"node_modules/is-glob": {
|
| 1266 |
+
"version": "4.0.3",
|
| 1267 |
+
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
| 1268 |
+
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
| 1269 |
+
"dev": true,
|
| 1270 |
+
"license": "MIT",
|
| 1271 |
+
"dependencies": {
|
| 1272 |
+
"is-extglob": "^2.1.1"
|
| 1273 |
+
},
|
| 1274 |
+
"engines": {
|
| 1275 |
+
"node": ">=0.10.0"
|
| 1276 |
+
}
|
| 1277 |
+
},
|
| 1278 |
+
"node_modules/is-number": {
|
| 1279 |
+
"version": "7.0.0",
|
| 1280 |
+
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
| 1281 |
+
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
| 1282 |
+
"dev": true,
|
| 1283 |
+
"license": "MIT",
|
| 1284 |
+
"engines": {
|
| 1285 |
+
"node": ">=0.12.0"
|
| 1286 |
+
}
|
| 1287 |
+
},
|
| 1288 |
+
"node_modules/jiti": {
|
| 1289 |
+
"version": "1.21.7",
|
| 1290 |
+
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
| 1291 |
+
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
| 1292 |
+
"dev": true,
|
| 1293 |
+
"license": "MIT",
|
| 1294 |
+
"bin": {
|
| 1295 |
+
"jiti": "bin/jiti.js"
|
| 1296 |
+
}
|
| 1297 |
+
},
|
| 1298 |
+
"node_modules/lilconfig": {
|
| 1299 |
+
"version": "3.1.3",
|
| 1300 |
+
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
| 1301 |
+
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
| 1302 |
+
"dev": true,
|
| 1303 |
+
"license": "MIT",
|
| 1304 |
+
"engines": {
|
| 1305 |
+
"node": ">=14"
|
| 1306 |
+
},
|
| 1307 |
+
"funding": {
|
| 1308 |
+
"url": "https://github.com/sponsors/antonk52"
|
| 1309 |
+
}
|
| 1310 |
+
},
|
| 1311 |
+
"node_modules/lines-and-columns": {
|
| 1312 |
+
"version": "1.2.4",
|
| 1313 |
+
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
| 1314 |
+
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
| 1315 |
+
"dev": true,
|
| 1316 |
+
"license": "MIT"
|
| 1317 |
+
},
|
| 1318 |
+
"node_modules/merge2": {
|
| 1319 |
+
"version": "1.4.1",
|
| 1320 |
+
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
| 1321 |
+
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
| 1322 |
+
"dev": true,
|
| 1323 |
+
"license": "MIT",
|
| 1324 |
+
"engines": {
|
| 1325 |
+
"node": ">= 8"
|
| 1326 |
+
}
|
| 1327 |
+
},
|
| 1328 |
+
"node_modules/micromatch": {
|
| 1329 |
+
"version": "4.0.8",
|
| 1330 |
+
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
| 1331 |
+
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
| 1332 |
+
"dev": true,
|
| 1333 |
+
"license": "MIT",
|
| 1334 |
+
"dependencies": {
|
| 1335 |
+
"braces": "^3.0.3",
|
| 1336 |
+
"picomatch": "^2.3.1"
|
| 1337 |
+
},
|
| 1338 |
+
"engines": {
|
| 1339 |
+
"node": ">=8.6"
|
| 1340 |
+
}
|
| 1341 |
+
},
|
| 1342 |
+
"node_modules/mz": {
|
| 1343 |
+
"version": "2.7.0",
|
| 1344 |
+
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
| 1345 |
+
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
| 1346 |
+
"dev": true,
|
| 1347 |
+
"license": "MIT",
|
| 1348 |
+
"dependencies": {
|
| 1349 |
+
"any-promise": "^1.0.0",
|
| 1350 |
+
"object-assign": "^4.0.1",
|
| 1351 |
+
"thenify-all": "^1.0.0"
|
| 1352 |
+
}
|
| 1353 |
+
},
|
| 1354 |
+
"node_modules/nanoid": {
|
| 1355 |
+
"version": "3.3.11",
|
| 1356 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
| 1357 |
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
| 1358 |
+
"funding": [
|
| 1359 |
+
{
|
| 1360 |
+
"type": "github",
|
| 1361 |
+
"url": "https://github.com/sponsors/ai"
|
| 1362 |
+
}
|
| 1363 |
+
],
|
| 1364 |
+
"license": "MIT",
|
| 1365 |
+
"bin": {
|
| 1366 |
+
"nanoid": "bin/nanoid.cjs"
|
| 1367 |
+
},
|
| 1368 |
+
"engines": {
|
| 1369 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 1370 |
+
}
|
| 1371 |
+
},
|
| 1372 |
+
"node_modules/next": {
|
| 1373 |
+
"version": "15.5.14",
|
| 1374 |
+
"resolved": "https://registry.npmjs.org/next/-/next-15.5.14.tgz",
|
| 1375 |
+
"integrity": "sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==",
|
| 1376 |
+
"license": "MIT",
|
| 1377 |
+
"dependencies": {
|
| 1378 |
+
"@next/env": "15.5.14",
|
| 1379 |
+
"@swc/helpers": "0.5.15",
|
| 1380 |
+
"caniuse-lite": "^1.0.30001579",
|
| 1381 |
+
"postcss": "8.4.31",
|
| 1382 |
+
"styled-jsx": "5.1.6"
|
| 1383 |
+
},
|
| 1384 |
+
"bin": {
|
| 1385 |
+
"next": "dist/bin/next"
|
| 1386 |
+
},
|
| 1387 |
+
"engines": {
|
| 1388 |
+
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
| 1389 |
+
},
|
| 1390 |
+
"optionalDependencies": {
|
| 1391 |
+
"@next/swc-darwin-arm64": "15.5.14",
|
| 1392 |
+
"@next/swc-darwin-x64": "15.5.14",
|
| 1393 |
+
"@next/swc-linux-arm64-gnu": "15.5.14",
|
| 1394 |
+
"@next/swc-linux-arm64-musl": "15.5.14",
|
| 1395 |
+
"@next/swc-linux-x64-gnu": "15.5.14",
|
| 1396 |
+
"@next/swc-linux-x64-musl": "15.5.14",
|
| 1397 |
+
"@next/swc-win32-arm64-msvc": "15.5.14",
|
| 1398 |
+
"@next/swc-win32-x64-msvc": "15.5.14",
|
| 1399 |
+
"sharp": "^0.34.3"
|
| 1400 |
+
},
|
| 1401 |
+
"peerDependencies": {
|
| 1402 |
+
"@opentelemetry/api": "^1.1.0",
|
| 1403 |
+
"@playwright/test": "^1.51.1",
|
| 1404 |
+
"babel-plugin-react-compiler": "*",
|
| 1405 |
+
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
| 1406 |
+
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
| 1407 |
+
"sass": "^1.3.0"
|
| 1408 |
+
},
|
| 1409 |
+
"peerDependenciesMeta": {
|
| 1410 |
+
"@opentelemetry/api": {
|
| 1411 |
+
"optional": true
|
| 1412 |
+
},
|
| 1413 |
+
"@playwright/test": {
|
| 1414 |
+
"optional": true
|
| 1415 |
+
},
|
| 1416 |
+
"babel-plugin-react-compiler": {
|
| 1417 |
+
"optional": true
|
| 1418 |
+
},
|
| 1419 |
+
"sass": {
|
| 1420 |
+
"optional": true
|
| 1421 |
+
}
|
| 1422 |
+
}
|
| 1423 |
+
},
|
| 1424 |
+
"node_modules/next/node_modules/postcss": {
|
| 1425 |
+
"version": "8.4.31",
|
| 1426 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
| 1427 |
+
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
| 1428 |
+
"funding": [
|
| 1429 |
+
{
|
| 1430 |
+
"type": "opencollective",
|
| 1431 |
+
"url": "https://opencollective.com/postcss/"
|
| 1432 |
+
},
|
| 1433 |
+
{
|
| 1434 |
+
"type": "tidelift",
|
| 1435 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 1436 |
+
},
|
| 1437 |
+
{
|
| 1438 |
+
"type": "github",
|
| 1439 |
+
"url": "https://github.com/sponsors/ai"
|
| 1440 |
+
}
|
| 1441 |
+
],
|
| 1442 |
+
"license": "MIT",
|
| 1443 |
+
"dependencies": {
|
| 1444 |
+
"nanoid": "^3.3.6",
|
| 1445 |
+
"picocolors": "^1.0.0",
|
| 1446 |
+
"source-map-js": "^1.0.2"
|
| 1447 |
+
},
|
| 1448 |
+
"engines": {
|
| 1449 |
+
"node": "^10 || ^12 || >=14"
|
| 1450 |
+
}
|
| 1451 |
+
},
|
| 1452 |
+
"node_modules/node-releases": {
|
| 1453 |
+
"version": "2.0.36",
|
| 1454 |
+
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
|
| 1455 |
+
"integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
|
| 1456 |
+
"dev": true,
|
| 1457 |
+
"license": "MIT"
|
| 1458 |
+
},
|
| 1459 |
+
"node_modules/normalize-path": {
|
| 1460 |
+
"version": "3.0.0",
|
| 1461 |
+
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
| 1462 |
+
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
| 1463 |
+
"dev": true,
|
| 1464 |
+
"license": "MIT",
|
| 1465 |
+
"engines": {
|
| 1466 |
+
"node": ">=0.10.0"
|
| 1467 |
+
}
|
| 1468 |
+
},
|
| 1469 |
+
"node_modules/normalize-range": {
|
| 1470 |
+
"version": "0.1.2",
|
| 1471 |
+
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
|
| 1472 |
+
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
|
| 1473 |
+
"dev": true,
|
| 1474 |
+
"license": "MIT",
|
| 1475 |
+
"engines": {
|
| 1476 |
+
"node": ">=0.10.0"
|
| 1477 |
+
}
|
| 1478 |
+
},
|
| 1479 |
+
"node_modules/object-assign": {
|
| 1480 |
+
"version": "4.1.1",
|
| 1481 |
+
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
| 1482 |
+
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
| 1483 |
+
"dev": true,
|
| 1484 |
+
"license": "MIT",
|
| 1485 |
+
"engines": {
|
| 1486 |
+
"node": ">=0.10.0"
|
| 1487 |
+
}
|
| 1488 |
+
},
|
| 1489 |
+
"node_modules/object-hash": {
|
| 1490 |
+
"version": "3.0.0",
|
| 1491 |
+
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
| 1492 |
+
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
| 1493 |
+
"dev": true,
|
| 1494 |
+
"license": "MIT",
|
| 1495 |
+
"engines": {
|
| 1496 |
+
"node": ">= 6"
|
| 1497 |
+
}
|
| 1498 |
+
},
|
| 1499 |
+
"node_modules/path-parse": {
|
| 1500 |
+
"version": "1.0.7",
|
| 1501 |
+
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
| 1502 |
+
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
| 1503 |
+
"dev": true,
|
| 1504 |
+
"license": "MIT"
|
| 1505 |
+
},
|
| 1506 |
+
"node_modules/picocolors": {
|
| 1507 |
+
"version": "1.1.1",
|
| 1508 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 1509 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 1510 |
+
"license": "ISC"
|
| 1511 |
+
},
|
| 1512 |
+
"node_modules/picomatch": {
|
| 1513 |
+
"version": "2.3.2",
|
| 1514 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
| 1515 |
+
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
| 1516 |
+
"dev": true,
|
| 1517 |
+
"license": "MIT",
|
| 1518 |
+
"engines": {
|
| 1519 |
+
"node": ">=8.6"
|
| 1520 |
+
},
|
| 1521 |
+
"funding": {
|
| 1522 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 1523 |
+
}
|
| 1524 |
+
},
|
| 1525 |
+
"node_modules/pify": {
|
| 1526 |
+
"version": "2.3.0",
|
| 1527 |
+
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
| 1528 |
+
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
| 1529 |
+
"dev": true,
|
| 1530 |
+
"license": "MIT",
|
| 1531 |
+
"engines": {
|
| 1532 |
+
"node": ">=0.10.0"
|
| 1533 |
+
}
|
| 1534 |
+
},
|
| 1535 |
+
"node_modules/pirates": {
|
| 1536 |
+
"version": "4.0.7",
|
| 1537 |
+
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
| 1538 |
+
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
| 1539 |
+
"dev": true,
|
| 1540 |
+
"license": "MIT",
|
| 1541 |
+
"engines": {
|
| 1542 |
+
"node": ">= 6"
|
| 1543 |
+
}
|
| 1544 |
+
},
|
| 1545 |
+
"node_modules/postcss": {
|
| 1546 |
+
"version": "8.5.3",
|
| 1547 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
| 1548 |
+
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
| 1549 |
+
"dev": true,
|
| 1550 |
+
"funding": [
|
| 1551 |
+
{
|
| 1552 |
+
"type": "opencollective",
|
| 1553 |
+
"url": "https://opencollective.com/postcss/"
|
| 1554 |
+
},
|
| 1555 |
+
{
|
| 1556 |
+
"type": "tidelift",
|
| 1557 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 1558 |
+
},
|
| 1559 |
+
{
|
| 1560 |
+
"type": "github",
|
| 1561 |
+
"url": "https://github.com/sponsors/ai"
|
| 1562 |
+
}
|
| 1563 |
+
],
|
| 1564 |
+
"license": "MIT",
|
| 1565 |
+
"dependencies": {
|
| 1566 |
+
"nanoid": "^3.3.8",
|
| 1567 |
+
"picocolors": "^1.1.1",
|
| 1568 |
+
"source-map-js": "^1.2.1"
|
| 1569 |
+
},
|
| 1570 |
+
"engines": {
|
| 1571 |
+
"node": "^10 || ^12 || >=14"
|
| 1572 |
+
}
|
| 1573 |
+
},
|
| 1574 |
+
"node_modules/postcss-import": {
|
| 1575 |
+
"version": "15.1.0",
|
| 1576 |
+
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
| 1577 |
+
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
| 1578 |
+
"dev": true,
|
| 1579 |
+
"license": "MIT",
|
| 1580 |
+
"dependencies": {
|
| 1581 |
+
"postcss-value-parser": "^4.0.0",
|
| 1582 |
+
"read-cache": "^1.0.0",
|
| 1583 |
+
"resolve": "^1.1.7"
|
| 1584 |
+
},
|
| 1585 |
+
"engines": {
|
| 1586 |
+
"node": ">=14.0.0"
|
| 1587 |
+
},
|
| 1588 |
+
"peerDependencies": {
|
| 1589 |
+
"postcss": "^8.0.0"
|
| 1590 |
+
}
|
| 1591 |
+
},
|
| 1592 |
+
"node_modules/postcss-js": {
|
| 1593 |
+
"version": "4.1.0",
|
| 1594 |
+
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
|
| 1595 |
+
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
|
| 1596 |
+
"dev": true,
|
| 1597 |
+
"funding": [
|
| 1598 |
+
{
|
| 1599 |
+
"type": "opencollective",
|
| 1600 |
+
"url": "https://opencollective.com/postcss/"
|
| 1601 |
+
},
|
| 1602 |
+
{
|
| 1603 |
+
"type": "github",
|
| 1604 |
+
"url": "https://github.com/sponsors/ai"
|
| 1605 |
+
}
|
| 1606 |
+
],
|
| 1607 |
+
"license": "MIT",
|
| 1608 |
+
"dependencies": {
|
| 1609 |
+
"camelcase-css": "^2.0.1"
|
| 1610 |
+
},
|
| 1611 |
+
"engines": {
|
| 1612 |
+
"node": "^12 || ^14 || >= 16"
|
| 1613 |
+
},
|
| 1614 |
+
"peerDependencies": {
|
| 1615 |
+
"postcss": "^8.4.21"
|
| 1616 |
+
}
|
| 1617 |
+
},
|
| 1618 |
+
"node_modules/postcss-nested": {
|
| 1619 |
+
"version": "6.2.0",
|
| 1620 |
+
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
| 1621 |
+
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
| 1622 |
+
"dev": true,
|
| 1623 |
+
"funding": [
|
| 1624 |
+
{
|
| 1625 |
+
"type": "opencollective",
|
| 1626 |
+
"url": "https://opencollective.com/postcss/"
|
| 1627 |
+
},
|
| 1628 |
+
{
|
| 1629 |
+
"type": "github",
|
| 1630 |
+
"url": "https://github.com/sponsors/ai"
|
| 1631 |
+
}
|
| 1632 |
+
],
|
| 1633 |
+
"license": "MIT",
|
| 1634 |
+
"dependencies": {
|
| 1635 |
+
"postcss-selector-parser": "^6.1.1"
|
| 1636 |
+
},
|
| 1637 |
+
"engines": {
|
| 1638 |
+
"node": ">=12.0"
|
| 1639 |
+
},
|
| 1640 |
+
"peerDependencies": {
|
| 1641 |
+
"postcss": "^8.2.14"
|
| 1642 |
+
}
|
| 1643 |
+
},
|
| 1644 |
+
"node_modules/postcss-selector-parser": {
|
| 1645 |
+
"version": "6.1.2",
|
| 1646 |
+
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
| 1647 |
+
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
| 1648 |
+
"dev": true,
|
| 1649 |
+
"license": "MIT",
|
| 1650 |
+
"dependencies": {
|
| 1651 |
+
"cssesc": "^3.0.0",
|
| 1652 |
+
"util-deprecate": "^1.0.2"
|
| 1653 |
+
},
|
| 1654 |
+
"engines": {
|
| 1655 |
+
"node": ">=4"
|
| 1656 |
+
}
|
| 1657 |
+
},
|
| 1658 |
+
"node_modules/postcss-value-parser": {
|
| 1659 |
+
"version": "4.2.0",
|
| 1660 |
+
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
| 1661 |
+
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
| 1662 |
+
"dev": true,
|
| 1663 |
+
"license": "MIT"
|
| 1664 |
+
},
|
| 1665 |
+
"node_modules/queue-microtask": {
|
| 1666 |
+
"version": "1.2.3",
|
| 1667 |
+
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
| 1668 |
+
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
| 1669 |
+
"dev": true,
|
| 1670 |
+
"funding": [
|
| 1671 |
+
{
|
| 1672 |
+
"type": "github",
|
| 1673 |
+
"url": "https://github.com/sponsors/feross"
|
| 1674 |
+
},
|
| 1675 |
+
{
|
| 1676 |
+
"type": "patreon",
|
| 1677 |
+
"url": "https://www.patreon.com/feross"
|
| 1678 |
+
},
|
| 1679 |
+
{
|
| 1680 |
+
"type": "consulting",
|
| 1681 |
+
"url": "https://feross.org/support"
|
| 1682 |
+
}
|
| 1683 |
+
],
|
| 1684 |
+
"license": "MIT"
|
| 1685 |
+
},
|
| 1686 |
+
"node_modules/react": {
|
| 1687 |
+
"version": "19.1.0",
|
| 1688 |
+
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
| 1689 |
+
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
| 1690 |
+
"license": "MIT",
|
| 1691 |
+
"engines": {
|
| 1692 |
+
"node": ">=0.10.0"
|
| 1693 |
+
}
|
| 1694 |
+
},
|
| 1695 |
+
"node_modules/react-dom": {
|
| 1696 |
+
"version": "19.1.0",
|
| 1697 |
+
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
| 1698 |
+
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
| 1699 |
+
"license": "MIT",
|
| 1700 |
+
"dependencies": {
|
| 1701 |
+
"scheduler": "^0.26.0"
|
| 1702 |
+
},
|
| 1703 |
+
"peerDependencies": {
|
| 1704 |
+
"react": "^19.1.0"
|
| 1705 |
+
}
|
| 1706 |
+
},
|
| 1707 |
+
"node_modules/read-cache": {
|
| 1708 |
+
"version": "1.0.0",
|
| 1709 |
+
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
| 1710 |
+
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
| 1711 |
+
"dev": true,
|
| 1712 |
+
"license": "MIT",
|
| 1713 |
+
"dependencies": {
|
| 1714 |
+
"pify": "^2.3.0"
|
| 1715 |
+
}
|
| 1716 |
+
},
|
| 1717 |
+
"node_modules/readdirp": {
|
| 1718 |
+
"version": "3.6.0",
|
| 1719 |
+
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
| 1720 |
+
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
| 1721 |
+
"dev": true,
|
| 1722 |
+
"license": "MIT",
|
| 1723 |
+
"dependencies": {
|
| 1724 |
+
"picomatch": "^2.2.1"
|
| 1725 |
+
},
|
| 1726 |
+
"engines": {
|
| 1727 |
+
"node": ">=8.10.0"
|
| 1728 |
+
}
|
| 1729 |
+
},
|
| 1730 |
+
"node_modules/resolve": {
|
| 1731 |
+
"version": "1.22.11",
|
| 1732 |
+
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
| 1733 |
+
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
| 1734 |
+
"dev": true,
|
| 1735 |
+
"license": "MIT",
|
| 1736 |
+
"dependencies": {
|
| 1737 |
+
"is-core-module": "^2.16.1",
|
| 1738 |
+
"path-parse": "^1.0.7",
|
| 1739 |
+
"supports-preserve-symlinks-flag": "^1.0.0"
|
| 1740 |
+
},
|
| 1741 |
+
"bin": {
|
| 1742 |
+
"resolve": "bin/resolve"
|
| 1743 |
+
},
|
| 1744 |
+
"engines": {
|
| 1745 |
+
"node": ">= 0.4"
|
| 1746 |
+
},
|
| 1747 |
+
"funding": {
|
| 1748 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1749 |
+
}
|
| 1750 |
+
},
|
| 1751 |
+
"node_modules/reusify": {
|
| 1752 |
+
"version": "1.1.0",
|
| 1753 |
+
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
| 1754 |
+
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
| 1755 |
+
"dev": true,
|
| 1756 |
+
"license": "MIT",
|
| 1757 |
+
"engines": {
|
| 1758 |
+
"iojs": ">=1.0.0",
|
| 1759 |
+
"node": ">=0.10.0"
|
| 1760 |
+
}
|
| 1761 |
+
},
|
| 1762 |
+
"node_modules/run-parallel": {
|
| 1763 |
+
"version": "1.2.0",
|
| 1764 |
+
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
| 1765 |
+
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
| 1766 |
+
"dev": true,
|
| 1767 |
+
"funding": [
|
| 1768 |
+
{
|
| 1769 |
+
"type": "github",
|
| 1770 |
+
"url": "https://github.com/sponsors/feross"
|
| 1771 |
+
},
|
| 1772 |
+
{
|
| 1773 |
+
"type": "patreon",
|
| 1774 |
+
"url": "https://www.patreon.com/feross"
|
| 1775 |
+
},
|
| 1776 |
+
{
|
| 1777 |
+
"type": "consulting",
|
| 1778 |
+
"url": "https://feross.org/support"
|
| 1779 |
+
}
|
| 1780 |
+
],
|
| 1781 |
+
"license": "MIT",
|
| 1782 |
+
"dependencies": {
|
| 1783 |
+
"queue-microtask": "^1.2.2"
|
| 1784 |
+
}
|
| 1785 |
+
},
|
| 1786 |
+
"node_modules/scheduler": {
|
| 1787 |
+
"version": "0.26.0",
|
| 1788 |
+
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
| 1789 |
+
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
| 1790 |
+
"license": "MIT"
|
| 1791 |
+
},
|
| 1792 |
+
"node_modules/semver": {
|
| 1793 |
+
"version": "7.7.4",
|
| 1794 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
| 1795 |
+
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
| 1796 |
+
"license": "ISC",
|
| 1797 |
+
"optional": true,
|
| 1798 |
+
"bin": {
|
| 1799 |
+
"semver": "bin/semver.js"
|
| 1800 |
+
},
|
| 1801 |
+
"engines": {
|
| 1802 |
+
"node": ">=10"
|
| 1803 |
+
}
|
| 1804 |
+
},
|
| 1805 |
+
"node_modules/sharp": {
|
| 1806 |
+
"version": "0.34.5",
|
| 1807 |
+
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
| 1808 |
+
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
| 1809 |
+
"hasInstallScript": true,
|
| 1810 |
+
"license": "Apache-2.0",
|
| 1811 |
+
"optional": true,
|
| 1812 |
+
"dependencies": {
|
| 1813 |
+
"@img/colour": "^1.0.0",
|
| 1814 |
+
"detect-libc": "^2.1.2",
|
| 1815 |
+
"semver": "^7.7.3"
|
| 1816 |
+
},
|
| 1817 |
+
"engines": {
|
| 1818 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 1819 |
+
},
|
| 1820 |
+
"funding": {
|
| 1821 |
+
"url": "https://opencollective.com/libvips"
|
| 1822 |
+
},
|
| 1823 |
+
"optionalDependencies": {
|
| 1824 |
+
"@img/sharp-darwin-arm64": "0.34.5",
|
| 1825 |
+
"@img/sharp-darwin-x64": "0.34.5",
|
| 1826 |
+
"@img/sharp-libvips-darwin-arm64": "1.2.4",
|
| 1827 |
+
"@img/sharp-libvips-darwin-x64": "1.2.4",
|
| 1828 |
+
"@img/sharp-libvips-linux-arm": "1.2.4",
|
| 1829 |
+
"@img/sharp-libvips-linux-arm64": "1.2.4",
|
| 1830 |
+
"@img/sharp-libvips-linux-ppc64": "1.2.4",
|
| 1831 |
+
"@img/sharp-libvips-linux-riscv64": "1.2.4",
|
| 1832 |
+
"@img/sharp-libvips-linux-s390x": "1.2.4",
|
| 1833 |
+
"@img/sharp-libvips-linux-x64": "1.2.4",
|
| 1834 |
+
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
|
| 1835 |
+
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
|
| 1836 |
+
"@img/sharp-linux-arm": "0.34.5",
|
| 1837 |
+
"@img/sharp-linux-arm64": "0.34.5",
|
| 1838 |
+
"@img/sharp-linux-ppc64": "0.34.5",
|
| 1839 |
+
"@img/sharp-linux-riscv64": "0.34.5",
|
| 1840 |
+
"@img/sharp-linux-s390x": "0.34.5",
|
| 1841 |
+
"@img/sharp-linux-x64": "0.34.5",
|
| 1842 |
+
"@img/sharp-linuxmusl-arm64": "0.34.5",
|
| 1843 |
+
"@img/sharp-linuxmusl-x64": "0.34.5",
|
| 1844 |
+
"@img/sharp-wasm32": "0.34.5",
|
| 1845 |
+
"@img/sharp-win32-arm64": "0.34.5",
|
| 1846 |
+
"@img/sharp-win32-ia32": "0.34.5",
|
| 1847 |
+
"@img/sharp-win32-x64": "0.34.5"
|
| 1848 |
+
}
|
| 1849 |
+
},
|
| 1850 |
+
"node_modules/source-map-js": {
|
| 1851 |
+
"version": "1.2.1",
|
| 1852 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 1853 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 1854 |
+
"license": "BSD-3-Clause",
|
| 1855 |
+
"engines": {
|
| 1856 |
+
"node": ">=0.10.0"
|
| 1857 |
+
}
|
| 1858 |
+
},
|
| 1859 |
+
"node_modules/styled-jsx": {
|
| 1860 |
+
"version": "5.1.6",
|
| 1861 |
+
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
| 1862 |
+
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
| 1863 |
+
"license": "MIT",
|
| 1864 |
+
"dependencies": {
|
| 1865 |
+
"client-only": "0.0.1"
|
| 1866 |
+
},
|
| 1867 |
+
"engines": {
|
| 1868 |
+
"node": ">= 12.0.0"
|
| 1869 |
+
},
|
| 1870 |
+
"peerDependencies": {
|
| 1871 |
+
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
|
| 1872 |
+
},
|
| 1873 |
+
"peerDependenciesMeta": {
|
| 1874 |
+
"@babel/core": {
|
| 1875 |
+
"optional": true
|
| 1876 |
+
},
|
| 1877 |
+
"babel-plugin-macros": {
|
| 1878 |
+
"optional": true
|
| 1879 |
+
}
|
| 1880 |
+
}
|
| 1881 |
+
},
|
| 1882 |
+
"node_modules/sucrase": {
|
| 1883 |
+
"version": "3.35.1",
|
| 1884 |
+
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
|
| 1885 |
+
"integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
|
| 1886 |
+
"dev": true,
|
| 1887 |
+
"license": "MIT",
|
| 1888 |
+
"dependencies": {
|
| 1889 |
+
"@jridgewell/gen-mapping": "^0.3.2",
|
| 1890 |
+
"commander": "^4.0.0",
|
| 1891 |
+
"lines-and-columns": "^1.1.6",
|
| 1892 |
+
"mz": "^2.7.0",
|
| 1893 |
+
"pirates": "^4.0.1",
|
| 1894 |
+
"tinyglobby": "^0.2.11",
|
| 1895 |
+
"ts-interface-checker": "^0.1.9"
|
| 1896 |
+
},
|
| 1897 |
+
"bin": {
|
| 1898 |
+
"sucrase": "bin/sucrase",
|
| 1899 |
+
"sucrase-node": "bin/sucrase-node"
|
| 1900 |
+
},
|
| 1901 |
+
"engines": {
|
| 1902 |
+
"node": ">=16 || 14 >=14.17"
|
| 1903 |
+
}
|
| 1904 |
+
},
|
| 1905 |
+
"node_modules/supports-preserve-symlinks-flag": {
|
| 1906 |
+
"version": "1.0.0",
|
| 1907 |
+
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
| 1908 |
+
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
| 1909 |
+
"dev": true,
|
| 1910 |
+
"license": "MIT",
|
| 1911 |
+
"engines": {
|
| 1912 |
+
"node": ">= 0.4"
|
| 1913 |
+
},
|
| 1914 |
+
"funding": {
|
| 1915 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1916 |
+
}
|
| 1917 |
+
},
|
| 1918 |
+
"node_modules/tailwindcss": {
|
| 1919 |
+
"version": "3.4.17",
|
| 1920 |
+
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
| 1921 |
+
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
| 1922 |
+
"dev": true,
|
| 1923 |
+
"license": "MIT",
|
| 1924 |
+
"dependencies": {
|
| 1925 |
+
"@alloc/quick-lru": "^5.2.0",
|
| 1926 |
+
"arg": "^5.0.2",
|
| 1927 |
+
"chokidar": "^3.6.0",
|
| 1928 |
+
"didyoumean": "^1.2.2",
|
| 1929 |
+
"dlv": "^1.1.3",
|
| 1930 |
+
"fast-glob": "^3.3.2",
|
| 1931 |
+
"glob-parent": "^6.0.2",
|
| 1932 |
+
"is-glob": "^4.0.3",
|
| 1933 |
+
"jiti": "^1.21.6",
|
| 1934 |
+
"lilconfig": "^3.1.3",
|
| 1935 |
+
"micromatch": "^4.0.8",
|
| 1936 |
+
"normalize-path": "^3.0.0",
|
| 1937 |
+
"object-hash": "^3.0.0",
|
| 1938 |
+
"picocolors": "^1.1.1",
|
| 1939 |
+
"postcss": "^8.4.47",
|
| 1940 |
+
"postcss-import": "^15.1.0",
|
| 1941 |
+
"postcss-js": "^4.0.1",
|
| 1942 |
+
"postcss-load-config": "^4.0.2",
|
| 1943 |
+
"postcss-nested": "^6.2.0",
|
| 1944 |
+
"postcss-selector-parser": "^6.1.2",
|
| 1945 |
+
"resolve": "^1.22.8",
|
| 1946 |
+
"sucrase": "^3.35.0"
|
| 1947 |
+
},
|
| 1948 |
+
"bin": {
|
| 1949 |
+
"tailwind": "lib/cli.js",
|
| 1950 |
+
"tailwindcss": "lib/cli.js"
|
| 1951 |
+
},
|
| 1952 |
+
"engines": {
|
| 1953 |
+
"node": ">=14.0.0"
|
| 1954 |
+
}
|
| 1955 |
+
},
|
| 1956 |
+
"node_modules/tailwindcss/node_modules/postcss-load-config": {
|
| 1957 |
+
"version": "4.0.2",
|
| 1958 |
+
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
| 1959 |
+
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
| 1960 |
+
"dev": true,
|
| 1961 |
+
"funding": [
|
| 1962 |
+
{
|
| 1963 |
+
"type": "opencollective",
|
| 1964 |
+
"url": "https://opencollective.com/postcss/"
|
| 1965 |
+
},
|
| 1966 |
+
{
|
| 1967 |
+
"type": "github",
|
| 1968 |
+
"url": "https://github.com/sponsors/ai"
|
| 1969 |
+
}
|
| 1970 |
+
],
|
| 1971 |
+
"license": "MIT",
|
| 1972 |
+
"dependencies": {
|
| 1973 |
+
"lilconfig": "^3.0.0",
|
| 1974 |
+
"yaml": "^2.3.4"
|
| 1975 |
+
},
|
| 1976 |
+
"engines": {
|
| 1977 |
+
"node": ">= 14"
|
| 1978 |
+
},
|
| 1979 |
+
"peerDependencies": {
|
| 1980 |
+
"postcss": ">=8.0.9",
|
| 1981 |
+
"ts-node": ">=9.0.0"
|
| 1982 |
+
},
|
| 1983 |
+
"peerDependenciesMeta": {
|
| 1984 |
+
"postcss": {
|
| 1985 |
+
"optional": true
|
| 1986 |
+
},
|
| 1987 |
+
"ts-node": {
|
| 1988 |
+
"optional": true
|
| 1989 |
+
}
|
| 1990 |
+
}
|
| 1991 |
+
},
|
| 1992 |
+
"node_modules/thenify": {
|
| 1993 |
+
"version": "3.3.1",
|
| 1994 |
+
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
| 1995 |
+
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
| 1996 |
+
"dev": true,
|
| 1997 |
+
"license": "MIT",
|
| 1998 |
+
"dependencies": {
|
| 1999 |
+
"any-promise": "^1.0.0"
|
| 2000 |
+
}
|
| 2001 |
+
},
|
| 2002 |
+
"node_modules/thenify-all": {
|
| 2003 |
+
"version": "1.6.0",
|
| 2004 |
+
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
| 2005 |
+
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
| 2006 |
+
"dev": true,
|
| 2007 |
+
"license": "MIT",
|
| 2008 |
+
"dependencies": {
|
| 2009 |
+
"thenify": ">= 3.1.0 < 4"
|
| 2010 |
+
},
|
| 2011 |
+
"engines": {
|
| 2012 |
+
"node": ">=0.8"
|
| 2013 |
+
}
|
| 2014 |
+
},
|
| 2015 |
+
"node_modules/tinyglobby": {
|
| 2016 |
+
"version": "0.2.15",
|
| 2017 |
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
| 2018 |
+
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
| 2019 |
+
"dev": true,
|
| 2020 |
+
"license": "MIT",
|
| 2021 |
+
"dependencies": {
|
| 2022 |
+
"fdir": "^6.5.0",
|
| 2023 |
+
"picomatch": "^4.0.3"
|
| 2024 |
+
},
|
| 2025 |
+
"engines": {
|
| 2026 |
+
"node": ">=12.0.0"
|
| 2027 |
+
},
|
| 2028 |
+
"funding": {
|
| 2029 |
+
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 2030 |
+
}
|
| 2031 |
+
},
|
| 2032 |
+
"node_modules/tinyglobby/node_modules/fdir": {
|
| 2033 |
+
"version": "6.5.0",
|
| 2034 |
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 2035 |
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 2036 |
+
"dev": true,
|
| 2037 |
+
"license": "MIT",
|
| 2038 |
+
"engines": {
|
| 2039 |
+
"node": ">=12.0.0"
|
| 2040 |
+
},
|
| 2041 |
+
"peerDependencies": {
|
| 2042 |
+
"picomatch": "^3 || ^4"
|
| 2043 |
+
},
|
| 2044 |
+
"peerDependenciesMeta": {
|
| 2045 |
+
"picomatch": {
|
| 2046 |
+
"optional": true
|
| 2047 |
+
}
|
| 2048 |
+
}
|
| 2049 |
+
},
|
| 2050 |
+
"node_modules/tinyglobby/node_modules/picomatch": {
|
| 2051 |
+
"version": "4.0.4",
|
| 2052 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
| 2053 |
+
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
| 2054 |
+
"dev": true,
|
| 2055 |
+
"license": "MIT",
|
| 2056 |
+
"engines": {
|
| 2057 |
+
"node": ">=12"
|
| 2058 |
+
},
|
| 2059 |
+
"funding": {
|
| 2060 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 2061 |
+
}
|
| 2062 |
+
},
|
| 2063 |
+
"node_modules/to-regex-range": {
|
| 2064 |
+
"version": "5.0.1",
|
| 2065 |
+
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
| 2066 |
+
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
| 2067 |
+
"dev": true,
|
| 2068 |
+
"license": "MIT",
|
| 2069 |
+
"dependencies": {
|
| 2070 |
+
"is-number": "^7.0.0"
|
| 2071 |
+
},
|
| 2072 |
+
"engines": {
|
| 2073 |
+
"node": ">=8.0"
|
| 2074 |
+
}
|
| 2075 |
+
},
|
| 2076 |
+
"node_modules/ts-interface-checker": {
|
| 2077 |
+
"version": "0.1.13",
|
| 2078 |
+
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
| 2079 |
+
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
| 2080 |
+
"dev": true,
|
| 2081 |
+
"license": "Apache-2.0"
|
| 2082 |
+
},
|
| 2083 |
+
"node_modules/tslib": {
|
| 2084 |
+
"version": "2.8.1",
|
| 2085 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 2086 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 2087 |
+
"license": "0BSD"
|
| 2088 |
+
},
|
| 2089 |
+
"node_modules/typescript": {
|
| 2090 |
+
"version": "5.8.3",
|
| 2091 |
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
| 2092 |
+
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
| 2093 |
+
"dev": true,
|
| 2094 |
+
"license": "Apache-2.0",
|
| 2095 |
+
"bin": {
|
| 2096 |
+
"tsc": "bin/tsc",
|
| 2097 |
+
"tsserver": "bin/tsserver"
|
| 2098 |
+
},
|
| 2099 |
+
"engines": {
|
| 2100 |
+
"node": ">=14.17"
|
| 2101 |
+
}
|
| 2102 |
+
},
|
| 2103 |
+
"node_modules/undici-types": {
|
| 2104 |
+
"version": "6.21.0",
|
| 2105 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
| 2106 |
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
| 2107 |
+
"dev": true,
|
| 2108 |
+
"license": "MIT"
|
| 2109 |
+
},
|
| 2110 |
+
"node_modules/update-browserslist-db": {
|
| 2111 |
+
"version": "1.2.3",
|
| 2112 |
+
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
| 2113 |
+
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
| 2114 |
+
"dev": true,
|
| 2115 |
+
"funding": [
|
| 2116 |
+
{
|
| 2117 |
+
"type": "opencollective",
|
| 2118 |
+
"url": "https://opencollective.com/browserslist"
|
| 2119 |
+
},
|
| 2120 |
+
{
|
| 2121 |
+
"type": "tidelift",
|
| 2122 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 2123 |
+
},
|
| 2124 |
+
{
|
| 2125 |
+
"type": "github",
|
| 2126 |
+
"url": "https://github.com/sponsors/ai"
|
| 2127 |
+
}
|
| 2128 |
+
],
|
| 2129 |
+
"license": "MIT",
|
| 2130 |
+
"dependencies": {
|
| 2131 |
+
"escalade": "^3.2.0",
|
| 2132 |
+
"picocolors": "^1.1.1"
|
| 2133 |
+
},
|
| 2134 |
+
"bin": {
|
| 2135 |
+
"update-browserslist-db": "cli.js"
|
| 2136 |
+
},
|
| 2137 |
+
"peerDependencies": {
|
| 2138 |
+
"browserslist": ">= 4.21.0"
|
| 2139 |
+
}
|
| 2140 |
+
},
|
| 2141 |
+
"node_modules/util-deprecate": {
|
| 2142 |
+
"version": "1.0.2",
|
| 2143 |
+
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
| 2144 |
+
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
| 2145 |
+
"dev": true,
|
| 2146 |
+
"license": "MIT"
|
| 2147 |
+
},
|
| 2148 |
+
"node_modules/yaml": {
|
| 2149 |
+
"version": "2.8.3",
|
| 2150 |
+
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
| 2151 |
+
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
|
| 2152 |
+
"dev": true,
|
| 2153 |
+
"license": "ISC",
|
| 2154 |
+
"bin": {
|
| 2155 |
+
"yaml": "bin.mjs"
|
| 2156 |
+
},
|
| 2157 |
+
"engines": {
|
| 2158 |
+
"node": ">= 14.6"
|
| 2159 |
+
},
|
| 2160 |
+
"funding": {
|
| 2161 |
+
"url": "https://github.com/sponsors/eemeli"
|
| 2162 |
+
}
|
| 2163 |
+
}
|
| 2164 |
+
}
|
| 2165 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "resumeai-pro",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"scripts": {
|
| 6 |
+
"dev": "next dev",
|
| 7 |
+
"build": "next build",
|
| 8 |
+
"start": "next start",
|
| 9 |
+
"lint": "next lint"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"next": "15.5.14",
|
| 13 |
+
"react": "19.1.0",
|
| 14 |
+
"react-dom": "19.1.0"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"@types/node": "22.14.1",
|
| 18 |
+
"@types/react": "19.1.1",
|
| 19 |
+
"@types/react-dom": "19.1.2",
|
| 20 |
+
"autoprefixer": "10.4.21",
|
| 21 |
+
"postcss": "8.5.3",
|
| 22 |
+
"tailwindcss": "3.4.17",
|
| 23 |
+
"typescript": "5.8.3"
|
| 24 |
+
}
|
| 25 |
+
}
|
postcss.config.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module.exports = {
|
| 2 |
+
plugins: {
|
| 3 |
+
tailwindcss: {},
|
| 4 |
+
autoprefixer: {},
|
| 5 |
+
},
|
| 6 |
+
};
|
scripts/start-prod.sh
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
set -eu
|
| 3 |
+
|
| 4 |
+
cd /app/backend
|
| 5 |
+
uvicorn app.main:app --host 127.0.0.1 --port 8000 &
|
| 6 |
+
BACKEND_PID=$!
|
| 7 |
+
|
| 8 |
+
cleanup() {
|
| 9 |
+
if kill -0 "$BACKEND_PID" 2>/dev/null; then
|
| 10 |
+
kill "$BACKEND_PID"
|
| 11 |
+
fi
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
trap cleanup EXIT INT TERM
|
| 15 |
+
|
| 16 |
+
cd /app/frontend
|
| 17 |
+
exec node server.js
|
tailwind.config.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Config } from "tailwindcss";
|
| 2 |
+
|
| 3 |
+
const config: Config = {
|
| 4 |
+
content: [
|
| 5 |
+
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
| 6 |
+
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
| 7 |
+
],
|
| 8 |
+
theme: {
|
| 9 |
+
extend: {
|
| 10 |
+
colors: {
|
| 11 |
+
ink: "#10121a",
|
| 12 |
+
sand: "#f8f3ea",
|
| 13 |
+
clay: "#d7a26c",
|
| 14 |
+
pine: "#21493f",
|
| 15 |
+
wine: "#7f3b3b",
|
| 16 |
+
mist: "#dde5db",
|
| 17 |
+
},
|
| 18 |
+
boxShadow: {
|
| 19 |
+
card: "0 18px 45px rgba(16, 18, 26, 0.08)",
|
| 20 |
+
},
|
| 21 |
+
},
|
| 22 |
+
},
|
| 23 |
+
plugins: [],
|
| 24 |
+
};
|
| 25 |
+
|
| 26 |
+
export default config;
|
tsconfig.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2017",
|
| 4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
| 5 |
+
"allowJs": false,
|
| 6 |
+
"skipLibCheck": true,
|
| 7 |
+
"strict": true,
|
| 8 |
+
"noEmit": true,
|
| 9 |
+
"esModuleInterop": true,
|
| 10 |
+
"module": "esnext",
|
| 11 |
+
"moduleResolution": "bundler",
|
| 12 |
+
"resolveJsonModule": true,
|
| 13 |
+
"isolatedModules": true,
|
| 14 |
+
"jsx": "preserve",
|
| 15 |
+
"incremental": true,
|
| 16 |
+
"baseUrl": ".",
|
| 17 |
+
"paths": {
|
| 18 |
+
"@/*": ["./*"]
|
| 19 |
+
},
|
| 20 |
+
"plugins": [
|
| 21 |
+
{
|
| 22 |
+
"name": "next"
|
| 23 |
+
}
|
| 24 |
+
]
|
| 25 |
+
},
|
| 26 |
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
| 27 |
+
"exclude": ["node_modules"]
|
| 28 |
+
}
|