Spaces:
Sleeping
Sleeping
Richard Lai
commited on
Commit
·
4024eae
1
Parent(s):
9010fb4
first commit
Browse files- .dockerignore +78 -0
- .gitignore +118 -0
- Dockerfile +69 -0
- README.md +234 -9
- README_HF.md +31 -0
- app.py +39 -0
- backend/main.py +1173 -0
- frontend/.gitignore +24 -0
- frontend/.nvmrc +1 -0
- frontend/.pnpmfile.cjs +15 -0
- frontend/README.md +69 -0
- frontend/eslint.config.js +23 -0
- frontend/index.html +13 -0
- frontend/package.json +40 -0
- frontend/pnpm-lock.yaml +2780 -0
- frontend/postcss.config.js +6 -0
- frontend/public/logo.svg +68 -0
- frontend/public/vite.svg +1 -0
- frontend/src/App.css +66 -0
- frontend/src/App.tsx +594 -0
- frontend/src/assets/react.svg +1 -0
- frontend/src/components/EditorModal.tsx +68 -0
- frontend/src/components/FeedPicker.tsx +67 -0
- frontend/src/components/TweetCards.tsx +248 -0
- frontend/src/index.css +3 -0
- frontend/src/main.tsx +10 -0
- frontend/src/vite-env.d.ts +1 -0
- frontend/tailwind.config.js +11 -0
- frontend/tsconfig.app.json +27 -0
- frontend/tsconfig.json +7 -0
- frontend/tsconfig.node.json +25 -0
- frontend/vite.config.ts +7 -0
- pyproject.toml +20 -0
- requirements.txt +79 -0
- uv.lock +399 -0
.dockerignore
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Git
|
| 2 |
+
.git
|
| 3 |
+
.gitignore
|
| 4 |
+
|
| 5 |
+
# Documentation
|
| 6 |
+
README.md
|
| 7 |
+
*.md
|
| 8 |
+
|
| 9 |
+
# Environment files
|
| 10 |
+
.env
|
| 11 |
+
.env.local
|
| 12 |
+
.env.example
|
| 13 |
+
|
| 14 |
+
# Python
|
| 15 |
+
__pycache__/
|
| 16 |
+
*.py[cod]
|
| 17 |
+
*$py.class
|
| 18 |
+
*.so
|
| 19 |
+
.Python
|
| 20 |
+
env/
|
| 21 |
+
venv/
|
| 22 |
+
.venv/
|
| 23 |
+
.coverage
|
| 24 |
+
.pytest_cache/
|
| 25 |
+
|
| 26 |
+
# Node.js
|
| 27 |
+
node_modules/
|
| 28 |
+
npm-debug.log*
|
| 29 |
+
yarn-debug.log*
|
| 30 |
+
yarn-error.log*
|
| 31 |
+
.npm
|
| 32 |
+
.yarn/
|
| 33 |
+
.pnpm-store/
|
| 34 |
+
|
| 35 |
+
# Frontend build artifacts (will be copied from build stage)
|
| 36 |
+
frontend/dist/
|
| 37 |
+
frontend/.vite/
|
| 38 |
+
frontend/coverage/
|
| 39 |
+
|
| 40 |
+
# IDE and editor files
|
| 41 |
+
.vscode/
|
| 42 |
+
.idea/
|
| 43 |
+
*.swp
|
| 44 |
+
*.swo
|
| 45 |
+
*~
|
| 46 |
+
|
| 47 |
+
# OS generated files
|
| 48 |
+
.DS_Store
|
| 49 |
+
.DS_Store?
|
| 50 |
+
._*
|
| 51 |
+
.Spotlight-V100
|
| 52 |
+
.Trashes
|
| 53 |
+
ehthumbs.db
|
| 54 |
+
Thumbs.db
|
| 55 |
+
|
| 56 |
+
# Logs
|
| 57 |
+
logs/
|
| 58 |
+
*.log
|
| 59 |
+
|
| 60 |
+
# Temporary files
|
| 61 |
+
tmp/
|
| 62 |
+
temp/
|
| 63 |
+
|
| 64 |
+
# Old project files
|
| 65 |
+
AI-NewsLetter-old/
|
| 66 |
+
|
| 67 |
+
# Deployment files
|
| 68 |
+
.vercel/
|
| 69 |
+
.railway/
|
| 70 |
+
|
| 71 |
+
# Package manager lock files (except the ones we need)
|
| 72 |
+
package-lock.json
|
| 73 |
+
yarn.lock
|
| 74 |
+
|
| 75 |
+
# Test files
|
| 76 |
+
*.test.js
|
| 77 |
+
*.test.ts
|
| 78 |
+
*.test.tsx
|
.gitignore
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
| 2 |
+
|
| 3 |
+
# dependencies
|
| 4 |
+
/node_modules
|
| 5 |
+
/.pnp
|
| 6 |
+
.pnp.js
|
| 7 |
+
|
| 8 |
+
# testing
|
| 9 |
+
/coverage
|
| 10 |
+
|
| 11 |
+
# next.js
|
| 12 |
+
/.next/
|
| 13 |
+
/out/
|
| 14 |
+
|
| 15 |
+
# production
|
| 16 |
+
/build
|
| 17 |
+
|
| 18 |
+
# misc
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.pem
|
| 21 |
+
|
| 22 |
+
# debug
|
| 23 |
+
npm-debug.log*
|
| 24 |
+
yarn-debug.log*
|
| 25 |
+
yarn-error.log*
|
| 26 |
+
|
| 27 |
+
# local env files
|
| 28 |
+
.env*.local
|
| 29 |
+
.env
|
| 30 |
+
|
| 31 |
+
# vercel
|
| 32 |
+
.vercel
|
| 33 |
+
|
| 34 |
+
# typescript
|
| 35 |
+
*.tsbuildinfo
|
| 36 |
+
next-env.d.ts
|
| 37 |
+
|
| 38 |
+
# Python
|
| 39 |
+
__pycache__/
|
| 40 |
+
*.py[cod]
|
| 41 |
+
*$py.class
|
| 42 |
+
*.pyo
|
| 43 |
+
*.pyd
|
| 44 |
+
*.pyc
|
| 45 |
+
|
| 46 |
+
# Distribution / packaging
|
| 47 |
+
.Python
|
| 48 |
+
build/
|
| 49 |
+
develop-eggs/
|
| 50 |
+
dist/
|
| 51 |
+
downloads/
|
| 52 |
+
eggs/
|
| 53 |
+
.eggs/
|
| 54 |
+
lib/
|
| 55 |
+
lib64/
|
| 56 |
+
parts/
|
| 57 |
+
sdist/
|
| 58 |
+
var/
|
| 59 |
+
wheels/
|
| 60 |
+
pip-wheel-metadata/
|
| 61 |
+
share/python-wheels/
|
| 62 |
+
*.egg-info/
|
| 63 |
+
.installed.cfg
|
| 64 |
+
*.egg
|
| 65 |
+
MANIFEST
|
| 66 |
+
|
| 67 |
+
# PyInstaller
|
| 68 |
+
# Usually these files are written by a python script from a template
|
| 69 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 70 |
+
*.manifest
|
| 71 |
+
*.spec
|
| 72 |
+
|
| 73 |
+
# Installer logs
|
| 74 |
+
pip-log.txt
|
| 75 |
+
pip-delete-this-directory.txt
|
| 76 |
+
|
| 77 |
+
# Unit test / coverage reports
|
| 78 |
+
htmlcov/
|
| 79 |
+
.tox/
|
| 80 |
+
.nox/
|
| 81 |
+
.coverage
|
| 82 |
+
.coverage.*
|
| 83 |
+
.cache
|
| 84 |
+
nosetests.xml
|
| 85 |
+
coverage.xml
|
| 86 |
+
*.cover
|
| 87 |
+
.hypothesis/
|
| 88 |
+
.pytest_cache/
|
| 89 |
+
|
| 90 |
+
# Jupyter Notebook
|
| 91 |
+
.ipynb_checkpoints
|
| 92 |
+
|
| 93 |
+
# pyenv
|
| 94 |
+
.python-version
|
| 95 |
+
|
| 96 |
+
# mypy
|
| 97 |
+
.mypy_cache/
|
| 98 |
+
.dmypy.json
|
| 99 |
+
dmypy.json
|
| 100 |
+
|
| 101 |
+
# Pyre type checker
|
| 102 |
+
.pyre/
|
| 103 |
+
|
| 104 |
+
# pytype
|
| 105 |
+
.pytype/
|
| 106 |
+
|
| 107 |
+
# Cython debug symbols
|
| 108 |
+
cython_debug/
|
| 109 |
+
|
| 110 |
+
# Virtual environment
|
| 111 |
+
.venv/*
|
| 112 |
+
|
| 113 |
+
# vscode
|
| 114 |
+
.vscode/*
|
| 115 |
+
|
| 116 |
+
# cursor
|
| 117 |
+
.cursor/*
|
| 118 |
+
|
Dockerfile
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multi-stage build for AI Newsletter Generator
|
| 2 |
+
# Optimized for Hugging Face Spaces deployment
|
| 3 |
+
|
| 4 |
+
# Stage 1: Build frontend
|
| 5 |
+
FROM node:20-slim as frontend-builder
|
| 6 |
+
|
| 7 |
+
# Install pnpm
|
| 8 |
+
RUN npm install -g pnpm
|
| 9 |
+
|
| 10 |
+
# Set working directory
|
| 11 |
+
WORKDIR /app/frontend
|
| 12 |
+
|
| 13 |
+
# Copy frontend package files
|
| 14 |
+
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
| 15 |
+
|
| 16 |
+
# Install frontend dependencies
|
| 17 |
+
RUN pnpm install --frozen-lockfile
|
| 18 |
+
|
| 19 |
+
# Copy frontend source
|
| 20 |
+
COPY frontend/ .
|
| 21 |
+
|
| 22 |
+
# Build frontend for production
|
| 23 |
+
RUN pnpm build
|
| 24 |
+
|
| 25 |
+
# Stage 2: Python backend with built frontend
|
| 26 |
+
FROM python:3.12-slim
|
| 27 |
+
|
| 28 |
+
# Set environment variables
|
| 29 |
+
ENV PYTHONUNBUFFERED=1
|
| 30 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 31 |
+
ENV PORT=7860
|
| 32 |
+
|
| 33 |
+
# Install system dependencies
|
| 34 |
+
RUN apt-get update && apt-get install -y \
|
| 35 |
+
curl \
|
| 36 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 37 |
+
|
| 38 |
+
# Install uv for fast Python package management
|
| 39 |
+
RUN pip install uv
|
| 40 |
+
|
| 41 |
+
# Set working directory
|
| 42 |
+
WORKDIR /app
|
| 43 |
+
|
| 44 |
+
# Copy Python configuration
|
| 45 |
+
COPY pyproject.toml uv.lock ./
|
| 46 |
+
|
| 47 |
+
# Install Python dependencies
|
| 48 |
+
RUN uv sync --no-dev
|
| 49 |
+
|
| 50 |
+
# Copy backend source
|
| 51 |
+
COPY backend/ ./backend/
|
| 52 |
+
|
| 53 |
+
# Copy built frontend from previous stage
|
| 54 |
+
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
|
| 55 |
+
|
| 56 |
+
# Create non-root user for security
|
| 57 |
+
RUN useradd --create-home --shell /bin/bash app
|
| 58 |
+
RUN chown -R app:app /app
|
| 59 |
+
USER app
|
| 60 |
+
|
| 61 |
+
# Expose port (Hugging Face Spaces uses 7860)
|
| 62 |
+
EXPOSE 7860
|
| 63 |
+
|
| 64 |
+
# Health check
|
| 65 |
+
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
|
| 66 |
+
CMD curl -f http://localhost:7860/api/health || exit 1
|
| 67 |
+
|
| 68 |
+
# Start command for Hugging Face Spaces
|
| 69 |
+
CMD ["uv", "run", "uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,11 +1,236 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
---
|
| 10 |
|
| 11 |
-
|
|
|
|
| 1 |
+
# AI Newsletter Generator
|
| 2 |
+
|
| 3 |
+
A full-stack AI-powered newsletter generator that creates engaging newsletters from RSS feeds with intelligent article summarization, tweet generation, and AI-assisted editing.
|
| 4 |
+
|
| 5 |
+
## ✨ Features
|
| 6 |
+
|
| 7 |
+
- 🤖 **AI-Enhanced Article Summaries**: LLM-generated engaging abstracts for better readability
|
| 8 |
+
- 📰 **RSS Feed Aggregation**: Curate content from multiple AI/tech news sources
|
| 9 |
+
- 🔍 **Smart Article Selection**: Interactive interface to choose articles for processing
|
| 10 |
+
- 📝 **Deep Article Summarization**: AI-powered detailed summaries of selected articles
|
| 11 |
+
- 🐦 **Social Media Content**: Generate Twitter/X posts with AI editing capabilities
|
| 12 |
+
- 📧 **Professional Newsletters**: Create polished HTML newsletters
|
| 13 |
+
- ✨ **Interactive AI Editing**: Real-time AI assistance for content refinement
|
| 14 |
+
- 🎨 **Modern UI**: Beautiful React interface with gradient backgrounds and smooth interactions
|
| 15 |
+
- ⚡ **Fast Performance**: Vite-powered frontend with hot reload
|
| 16 |
+
- 🔒 **Environment Security**: Secure API key management
|
| 17 |
+
|
| 18 |
+
## 🏗️ Architecture
|
| 19 |
+
|
| 20 |
+
### Project Structure
|
| 21 |
+
```
|
| 22 |
+
AI-NewsLetter/
|
| 23 |
+
├── backend/ # FastAPI backend
|
| 24 |
+
│ └── main.py # API endpoints + static file serving
|
| 25 |
+
├── frontend/ # React + Vite + Tailwind frontend
|
| 26 |
+
│ ├── src/
|
| 27 |
+
│ │ ├── components/ # React components
|
| 28 |
+
│ │ │ ├── FeedPicker.tsx
|
| 29 |
+
│ │ │ ├── TweetCards.tsx
|
| 30 |
+
│ │ │ └── EditorModal.tsx
|
| 31 |
+
│ │ ├── App.tsx # Main application
|
| 32 |
+
│ │ └── index.css # Tailwind styles
|
| 33 |
+
│ └── dist/ # Built frontend (served by backend)
|
| 34 |
+
├── pyproject.toml # Python dependencies (managed by uv)
|
| 35 |
+
├── .env # Environment variables
|
| 36 |
+
└── README.md
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
### Technology Stack
|
| 40 |
+
|
| 41 |
+
**Backend:**
|
| 42 |
+
- **FastAPI** - Modern Python web framework with automatic API docs
|
| 43 |
+
- **OpenAI API** - GPT-4o-mini for content generation and enhancement
|
| 44 |
+
- **httpx** - Async HTTP client for web scraping
|
| 45 |
+
- **feedparser** - RSS/Atom feed parsing
|
| 46 |
+
- **uvicorn** - High-performance ASGI server
|
| 47 |
+
|
| 48 |
+
**Frontend:**
|
| 49 |
+
- **React 19** - Latest React with modern hooks
|
| 50 |
+
- **TypeScript** - Type safety and better developer experience
|
| 51 |
+
- **Vite** - Lightning-fast build tool and dev server
|
| 52 |
+
- **Tailwind CSS v3** - Utility-first styling with custom components
|
| 53 |
+
- **pnpm** - Fast, disk-efficient package manager
|
| 54 |
+
|
| 55 |
+
**Development Tools:**
|
| 56 |
+
- **uv** - Ultra-fast Python package manager
|
| 57 |
+
- **ESLint** - Code linting and formatting
|
| 58 |
+
- **PostCSS** - CSS processing with Tailwind
|
| 59 |
+
|
| 60 |
+
## 🚀 Quick Start
|
| 61 |
+
|
| 62 |
+
### Prerequisites
|
| 63 |
+
|
| 64 |
+
- **Python 3.12+** with [uv](https://docs.astral.sh/uv/) installed
|
| 65 |
+
- **Node.js 18+**
|
| 66 |
+
- **pnpm** (recommended) or npm
|
| 67 |
+
- **OpenAI API Key** - Get one from [OpenAI Platform](https://platform.openai.com/account/api-keys)
|
| 68 |
+
|
| 69 |
+
### 1. Environment Setup
|
| 70 |
+
|
| 71 |
+
Create a `.env` file in the project root:
|
| 72 |
+
```bash
|
| 73 |
+
OPENAI_API_KEY=sk-your-actual-openai-api-key-here
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
### 2. Backend Setup
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
# Install Python dependencies
|
| 80 |
+
uv sync
|
| 81 |
+
|
| 82 |
+
# Start the FastAPI server (serves both API and frontend)
|
| 83 |
+
uv run uvicorn backend.main:app --host 127.0.0.1 --port 8000 --reload
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
### 3. Frontend Setup
|
| 87 |
+
|
| 88 |
+
```bash
|
| 89 |
+
cd frontend
|
| 90 |
+
|
| 91 |
+
# Install dependencies
|
| 92 |
+
pnpm install
|
| 93 |
+
|
| 94 |
+
# Build for production
|
| 95 |
+
pnpm build
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
### 4. Access the Application
|
| 99 |
+
|
| 100 |
+
Open your browser to **http://127.0.0.1:8000**
|
| 101 |
+
|
| 102 |
+
The backend serves both the API endpoints and the built React frontend from a single port.
|
| 103 |
+
|
| 104 |
+
## 📖 User Guide
|
| 105 |
+
|
| 106 |
+
### Workflow
|
| 107 |
+
|
| 108 |
+
1. **Select Sources**: Choose from curated AI/tech RSS feeds
|
| 109 |
+
2. **Get Highlights**: Fetch articles and generate initial AI summary
|
| 110 |
+
3. **Select Articles**: Review articles with AI-enhanced abstracts
|
| 111 |
+
4. **Get Summaries**: Generate detailed summaries for selected articles (max 5)
|
| 112 |
+
5. **Generate Tweets**: Create social media content with AI editing
|
| 113 |
+
6. **Create Newsletter**: Build professional HTML newsletter
|
| 114 |
+
7. **Download**: Export your newsletter
|
| 115 |
+
|
| 116 |
+
### Key Features Explained
|
| 117 |
+
|
| 118 |
+
**AI-Enhanced Abstracts**: When you click "Get Highlights", the system not only fetches articles but uses GPT to create engaging 2-3 sentence summaries for each article, making them much more readable and compelling than raw RSS descriptions.
|
| 119 |
+
|
| 120 |
+
**Smart Article Selection**: The interface shows checkboxes for each article with enhanced summaries, publication dates, and sources. You can easily select which articles to dive deeper into.
|
| 121 |
+
|
| 122 |
+
**Detailed Summarization**: The "Get Summaries" feature scrapes full article content and creates comprehensive summaries using AI, perfect for busy readers who want key insights.
|
| 123 |
+
|
| 124 |
+
**Interactive AI Editing**: Both tweets and newsletter content can be edited with AI assistance through natural language commands.
|
| 125 |
+
|
| 126 |
+
## 🔧 Development
|
| 127 |
+
|
| 128 |
+
### Full-Stack Development (Recommended)
|
| 129 |
+
```bash
|
| 130 |
+
# Terminal 1: Start backend
|
| 131 |
+
uv run uvicorn backend.main:app --host 127.0.0.1 --port 8000 --reload
|
| 132 |
+
|
| 133 |
+
# Terminal 2: Build frontend after changes
|
| 134 |
+
cd frontend && pnpm build
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
### Frontend-Only Development
|
| 138 |
+
For rapid UI development with hot reload:
|
| 139 |
+
```bash
|
| 140 |
+
# Terminal 1: Backend
|
| 141 |
+
uv run uvicorn backend.main:app --host 127.0.0.1 --port 8000 --reload
|
| 142 |
+
|
| 143 |
+
# Terminal 2: Frontend dev server
|
| 144 |
+
cd frontend && pnpm dev --port 3002
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
Then open http://127.0.0.1:3002 for development or http://127.0.0.1:8000 for production.
|
| 148 |
+
|
| 149 |
+
### Available Scripts
|
| 150 |
+
|
| 151 |
+
**Backend:**
|
| 152 |
+
```bash
|
| 153 |
+
uv sync # Install dependencies
|
| 154 |
+
uv run uvicorn backend.main:app --reload # Start server
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
**Frontend:**
|
| 158 |
+
```bash
|
| 159 |
+
pnpm install # Install dependencies
|
| 160 |
+
pnpm build # Build for production
|
| 161 |
+
pnpm dev # Development server
|
| 162 |
+
pnpm type-check # TypeScript checking
|
| 163 |
+
pnpm clean # Clean build artifacts
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
## 🌐 API Reference
|
| 167 |
+
|
| 168 |
+
### Core Endpoints
|
| 169 |
+
|
| 170 |
+
- `GET /` - Serves React frontend application
|
| 171 |
+
- `GET /api/health` - API health check
|
| 172 |
+
- `GET /api/defaults` - Get default RSS feed sources
|
| 173 |
+
|
| 174 |
+
### Content Generation
|
| 175 |
+
|
| 176 |
+
- `POST /api/aggregate` - Fetch articles from RSS feeds with AI-enhanced summaries
|
| 177 |
+
- `POST /api/highlights` - Generate weekly highlights summary
|
| 178 |
+
- `POST /api/summaries_selected` - Create detailed summaries for selected articles
|
| 179 |
+
- `POST /api/tweets` - Generate social media posts from summaries
|
| 180 |
+
- `POST /api/newsletter` - Create HTML newsletter
|
| 181 |
+
- `POST /api/edit_tweet` - AI-powered tweet editing
|
| 182 |
+
|
| 183 |
+
### Example API Usage
|
| 184 |
+
|
| 185 |
+
```bash
|
| 186 |
+
# Get enhanced articles with AI summaries
|
| 187 |
+
curl -X POST "http://127.0.0.1:8000/api/aggregate" \
|
| 188 |
+
-H "Content-Type: application/json" \
|
| 189 |
+
-d '{"sources": ["https://huggingface.co/blog/feed.xml"]}'
|
| 190 |
+
|
| 191 |
+
# Generate detailed summaries
|
| 192 |
+
curl -X POST "http://127.0.0.1:8000/api/summaries_selected" \
|
| 193 |
+
-H "Content-Type: application/json" \
|
| 194 |
+
-d '{"articles": [...]}'
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
## 🐳 Deployment
|
| 198 |
+
|
| 199 |
+
### Hugging Face Spaces
|
| 200 |
+
|
| 201 |
+
This project includes a Dockerfile optimized for Hugging Face Spaces deployment:
|
| 202 |
+
|
| 203 |
+
1. Push your code to a Hugging Face repository
|
| 204 |
+
2. Set your `OPENAI_API_KEY` in the Space settings
|
| 205 |
+
3. The Dockerfile will handle the rest!
|
| 206 |
+
|
| 207 |
+
### Other Platforms
|
| 208 |
+
|
| 209 |
+
The application can be deployed on any platform that supports Docker containers:
|
| 210 |
+
- Railway
|
| 211 |
+
- Render
|
| 212 |
+
- DigitalOcean App Platform
|
| 213 |
+
- AWS ECS
|
| 214 |
+
- Google Cloud Run
|
| 215 |
+
|
| 216 |
+
## 🤝 Contributing
|
| 217 |
+
|
| 218 |
+
Contributions are welcome! This project uses:
|
| 219 |
+
- **Python**: Black formatting, type hints encouraged
|
| 220 |
+
- **TypeScript**: Strict mode, ESLint configuration
|
| 221 |
+
- **Git**: Conventional commit messages preferred
|
| 222 |
+
|
| 223 |
+
## 📄 License
|
| 224 |
+
|
| 225 |
+
This project is open source and available under the MIT License.
|
| 226 |
+
|
| 227 |
+
## 🙋♂️ Support
|
| 228 |
+
|
| 229 |
+
Having issues?
|
| 230 |
+
1. Check that your OpenAI API key is correctly set in `.env`
|
| 231 |
+
2. Ensure all dependencies are installed (`uv sync` and `pnpm install`)
|
| 232 |
+
3. Verify the frontend is built (`pnpm build`) before accessing the full-stack app
|
| 233 |
+
|
| 234 |
---
|
| 235 |
|
| 236 |
+
Built with ❤️ using modern web technologies and AI.
|
README_HF.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI Newsletter Generator - Hugging Face Space
|
| 2 |
+
|
| 3 |
+
This Space runs an AI-powered newsletter generator that creates engaging newsletters from RSS feeds.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- 🤖 AI-enhanced article summaries using GPT-4o-mini
|
| 8 |
+
- 📰 RSS feed aggregation from top AI/tech sources
|
| 9 |
+
- 🐦 Social media content generation
|
| 10 |
+
- 📧 Professional HTML newsletter creation
|
| 11 |
+
- ✨ Interactive AI-powered editing
|
| 12 |
+
|
| 13 |
+
## Usage
|
| 14 |
+
|
| 15 |
+
1. **Select Sources**: Choose from curated RSS feeds
|
| 16 |
+
2. **Get Highlights**: Fetch articles with AI-enhanced summaries
|
| 17 |
+
3. **Select Articles**: Pick articles for detailed processing
|
| 18 |
+
4. **Generate Content**: Create summaries, tweets, and newsletters
|
| 19 |
+
5. **Download**: Export your newsletter
|
| 20 |
+
|
| 21 |
+
## Requirements
|
| 22 |
+
|
| 23 |
+
This Space requires an OpenAI API key to function. Set your `OPENAI_API_KEY` in the Space settings.
|
| 24 |
+
|
| 25 |
+
## Tech Stack
|
| 26 |
+
|
| 27 |
+
- **Backend**: FastAPI + OpenAI API
|
| 28 |
+
- **Frontend**: React + Vite + Tailwind CSS
|
| 29 |
+
- **Deployment**: Docker multi-stage build
|
| 30 |
+
|
| 31 |
+
Built with modern web technologies and AI for content creators and tech enthusiasts.
|
app.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
AI Newsletter Generator - Hugging Face Spaces Entry Point
|
| 4 |
+
|
| 5 |
+
This file serves as an alternative entry point for Hugging Face Spaces.
|
| 6 |
+
The main application is in backend/main.py and runs via uvicorn.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import subprocess
|
| 10 |
+
import sys
|
| 11 |
+
import os
|
| 12 |
+
|
| 13 |
+
def main():
|
| 14 |
+
"""Start the FastAPI application for Hugging Face Spaces"""
|
| 15 |
+
|
| 16 |
+
# Set default port for Hugging Face Spaces
|
| 17 |
+
port = os.getenv("PORT", "7860")
|
| 18 |
+
|
| 19 |
+
# Command to start the FastAPI app
|
| 20 |
+
cmd = [
|
| 21 |
+
sys.executable, "-m", "uvicorn",
|
| 22 |
+
"backend.main:app",
|
| 23 |
+
"--host", "0.0.0.0",
|
| 24 |
+
"--port", port
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
print(f"Starting AI Newsletter Generator on port {port}...")
|
| 28 |
+
print(f"Command: {' '.join(cmd)}")
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
subprocess.run(cmd, check=True)
|
| 32 |
+
except KeyboardInterrupt:
|
| 33 |
+
print("\nShutting down...")
|
| 34 |
+
except subprocess.CalledProcessError as e:
|
| 35 |
+
print(f"Error starting application: {e}")
|
| 36 |
+
sys.exit(1)
|
| 37 |
+
|
| 38 |
+
if __name__ == "__main__":
|
| 39 |
+
main()
|
backend/main.py
ADDED
|
@@ -0,0 +1,1173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import io
|
| 3 |
+
import json
|
| 4 |
+
from datetime import datetime, timedelta, timezone
|
| 5 |
+
import re
|
| 6 |
+
from html import unescape
|
| 7 |
+
import httpx
|
| 8 |
+
from typing import List, Optional, Dict, Any
|
| 9 |
+
|
| 10 |
+
import feedparser
|
| 11 |
+
from fastapi import FastAPI, HTTPException, Response, Request
|
| 12 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 13 |
+
from fastapi.staticfiles import StaticFiles
|
| 14 |
+
from fastapi.responses import FileResponse
|
| 15 |
+
from pydantic import BaseModel, Field, AnyHttpUrl
|
| 16 |
+
from dateutil import parser as dateparser
|
| 17 |
+
from openai import OpenAI
|
| 18 |
+
from dotenv import load_dotenv
|
| 19 |
+
|
| 20 |
+
# Load environment variables from .env file (if it exists)
|
| 21 |
+
try:
|
| 22 |
+
load_dotenv()
|
| 23 |
+
except Exception:
|
| 24 |
+
pass # Ignore if .env file doesn't exist (like in Railway)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# ASGI app for Vercel Python function: export `app`
|
| 28 |
+
app = FastAPI(title="AI Newsletter Generator API", version="1.0.0")
|
| 29 |
+
|
| 30 |
+
# CORS (same-origin on Vercel, but allow localhost for dev)
|
| 31 |
+
allowed_origins = [
|
| 32 |
+
os.getenv("ALLOWED_ORIGIN", "*"),
|
| 33 |
+
"http://localhost:3000",
|
| 34 |
+
"https://localhost:3000",
|
| 35 |
+
"http://localhost:3001",
|
| 36 |
+
"https://localhost:3001",
|
| 37 |
+
"http://localhost:3010",
|
| 38 |
+
"https://localhost:3010",
|
| 39 |
+
"http://localhost:3002",
|
| 40 |
+
"https://localhost:3002",
|
| 41 |
+
]
|
| 42 |
+
app.add_middleware(
|
| 43 |
+
CORSMiddleware,
|
| 44 |
+
allow_origins=allowed_origins,
|
| 45 |
+
allow_credentials=True,
|
| 46 |
+
allow_methods=["*"]
|
| 47 |
+
,
|
| 48 |
+
allow_headers=["*"]
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
# ----- Memory Store (ephemeral in serverless) -----
|
| 53 |
+
class ConversationTurn(BaseModel):
|
| 54 |
+
role: str
|
| 55 |
+
content: str
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class SessionMemory(BaseModel):
|
| 59 |
+
session_id: str
|
| 60 |
+
history: List[ConversationTurn] = Field(default_factory=list)
|
| 61 |
+
last_newsletter_html: Optional[str] = None
|
| 62 |
+
last_summary: Optional[str] = None
|
| 63 |
+
last_tweets: Optional[List[str]] = None
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
memory_store: Dict[str, SessionMemory] = {}
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def get_memory(session_id: str) -> SessionMemory:
|
| 70 |
+
if session_id not in memory_store:
|
| 71 |
+
memory_store[session_id] = SessionMemory(session_id=session_id)
|
| 72 |
+
return memory_store[session_id]
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
# ----- Default RSS Feeds (AI-focused) -----
|
| 76 |
+
DEFAULT_FEEDS: Dict[str, str] = {
|
| 77 |
+
# Working RSS feeds verified as of 2025
|
| 78 |
+
"Hugging Face Blog": "https://huggingface.co/blog/feed.xml",
|
| 79 |
+
"The Gradient": "https://thegradient.pub/rss/",
|
| 80 |
+
"MIT Technology Review AI": "https://www.technologyreview.com/tag/artificial-intelligence/feed/",
|
| 81 |
+
"VentureBeat AI": "https://venturebeat.com/ai/feed/",
|
| 82 |
+
"AI News": "https://artificialintelligence-news.com/feed/",
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# ----- Models -----
|
| 87 |
+
class AggregateRequest(BaseModel):
|
| 88 |
+
sources: Optional[List[AnyHttpUrl]] = None
|
| 89 |
+
since_days: int = Field(default=7, ge=1, le=31)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class Article(BaseModel):
|
| 93 |
+
title: str
|
| 94 |
+
link: AnyHttpUrl
|
| 95 |
+
summary: Optional[str] = None
|
| 96 |
+
published: Optional[str] = None
|
| 97 |
+
source: Optional[str] = None
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
class AggregateResponse(BaseModel):
|
| 101 |
+
articles: List[Article]
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class SummarizeRequest(BaseModel):
|
| 105 |
+
session_id: str
|
| 106 |
+
articles: List[Article]
|
| 107 |
+
instructions: Optional[str] = Field(
|
| 108 |
+
default=(
|
| 109 |
+
"Summarize the week's most important AI developments for a technical but busy audience. "
|
| 110 |
+
"Be concise, structured with headings and bullet points, and include source attributions."
|
| 111 |
+
)
|
| 112 |
+
)
|
| 113 |
+
prior_history: Optional[List[ConversationTurn]] = None
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class SummarizeResponse(BaseModel):
|
| 117 |
+
summary_markdown: str
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
# ----- Per-Article Summaries (Highlights) -----
|
| 121 |
+
class HighlightItem(BaseModel):
|
| 122 |
+
title: str
|
| 123 |
+
link: AnyHttpUrl
|
| 124 |
+
source: Optional[str] = None
|
| 125 |
+
summary: str
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
class TweetsRequest(BaseModel):
|
| 129 |
+
session_id: str
|
| 130 |
+
summaries: List[HighlightItem] # Changed to use individual summaries
|
| 131 |
+
prior_history: Optional[List[ConversationTurn]] = None
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
class Tweet(BaseModel):
|
| 135 |
+
id: str
|
| 136 |
+
content: str
|
| 137 |
+
summary_title: str
|
| 138 |
+
summary_link: str
|
| 139 |
+
summary_source: str
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class TweetsResponse(BaseModel):
|
| 143 |
+
tweets: List[Tweet]
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
class NewsletterRequest(BaseModel):
|
| 147 |
+
session_id: str
|
| 148 |
+
summary_markdown: str
|
| 149 |
+
articles: List[Article]
|
| 150 |
+
prior_history: Optional[List[ConversationTurn]] = None
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
class NewsletterResponse(BaseModel):
|
| 154 |
+
html: str
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
class EditRequest(BaseModel):
|
| 158 |
+
session_id: str
|
| 159 |
+
text: str
|
| 160 |
+
instruction: str
|
| 161 |
+
prior_history: Optional[List[ConversationTurn]] = None
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
class SummariesSelectedRequest(BaseModel):
|
| 165 |
+
articles: List[Article]
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
class EditResponse(BaseModel):
|
| 169 |
+
edited_text: str
|
| 170 |
+
history: List[ConversationTurn]
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
class TweetEditRequest(BaseModel):
|
| 174 |
+
session_id: str
|
| 175 |
+
tweet_id: str
|
| 176 |
+
current_tweet: str
|
| 177 |
+
original_summary: str
|
| 178 |
+
user_message: str
|
| 179 |
+
conversation_history: Optional[List[ConversationTurn]] = None
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
class TweetEditResponse(BaseModel):
|
| 183 |
+
new_tweet: str
|
| 184 |
+
ai_response: str
|
| 185 |
+
conversation_history: List[ConversationTurn]
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
# Initialize OpenAI client with error handling
|
| 189 |
+
try:
|
| 190 |
+
api_key = os.getenv("OPENAI_API_KEY")
|
| 191 |
+
if not api_key:
|
| 192 |
+
print("WARNING: OPENAI_API_KEY not found in environment variables")
|
| 193 |
+
openai_client = None
|
| 194 |
+
else:
|
| 195 |
+
openai_client = OpenAI(api_key=api_key)
|
| 196 |
+
MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
|
| 197 |
+
except Exception as e:
|
| 198 |
+
print(f"ERROR initializing OpenAI client: {e}")
|
| 199 |
+
openai_client = None
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def _parse_date(dt_str: Optional[str]) -> Optional[datetime]:
|
| 203 |
+
if not dt_str:
|
| 204 |
+
return None
|
| 205 |
+
try:
|
| 206 |
+
return dateparser.parse(dt_str)
|
| 207 |
+
except Exception:
|
| 208 |
+
return None
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
# Mount static files for serving React build
|
| 212 |
+
static_dir = os.path.join(os.path.dirname(__file__), "..", "frontend", "dist")
|
| 213 |
+
if os.path.exists(static_dir):
|
| 214 |
+
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
| 215 |
+
|
| 216 |
+
@app.get("/api/health")
|
| 217 |
+
def api_health():
|
| 218 |
+
"""API health check endpoint"""
|
| 219 |
+
return {"status": "ok", "message": "AI Newsletter API is running"}
|
| 220 |
+
|
| 221 |
+
@app.get("/")
|
| 222 |
+
def serve_frontend():
|
| 223 |
+
"""Serve React frontend from dist folder"""
|
| 224 |
+
static_dir = os.path.join(os.path.dirname(__file__), "..", "frontend", "dist")
|
| 225 |
+
index_file = os.path.join(static_dir, "index.html")
|
| 226 |
+
|
| 227 |
+
# If frontend build exists, serve it
|
| 228 |
+
if os.path.exists(index_file):
|
| 229 |
+
return FileResponse(index_file)
|
| 230 |
+
|
| 231 |
+
# Fallback to API health check if frontend not built
|
| 232 |
+
return {"status": "ok", "message": "AI Newsletter API is running", "note": "Frontend not built yet"}
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
@app.get("/api/defaults", response_model=Dict[str, str])
|
| 238 |
+
def get_defaults() -> Dict[str, str]:
|
| 239 |
+
"""Get default RSS feed sources"""
|
| 240 |
+
try:
|
| 241 |
+
return DEFAULT_FEEDS
|
| 242 |
+
except Exception as e:
|
| 243 |
+
print(f"Error in get_defaults: {e}")
|
| 244 |
+
raise HTTPException(status_code=500, detail=f"Server error: {str(e)}")
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def _generate_engaging_summaries(articles: List[Article]) -> List[Article]:
|
| 248 |
+
"""Generate engaging, short summaries for articles using LLM"""
|
| 249 |
+
if not openai_client:
|
| 250 |
+
return articles # Return unchanged if no OpenAI client
|
| 251 |
+
|
| 252 |
+
enhanced_articles = []
|
| 253 |
+
|
| 254 |
+
for article in articles:
|
| 255 |
+
try:
|
| 256 |
+
# Create a prompt to generate an engaging summary
|
| 257 |
+
if article.summary:
|
| 258 |
+
# Improve existing summary
|
| 259 |
+
prompt = f"""
|
| 260 |
+
Create an engaging, concise summary (2-3 sentences, ~50-80 words) for this article:
|
| 261 |
+
|
| 262 |
+
Title: {article.title}
|
| 263 |
+
Source: {article.source}
|
| 264 |
+
Current Summary: {article.summary}
|
| 265 |
+
|
| 266 |
+
Make it more engaging and accessible while keeping the key information. Focus on why readers should care.
|
| 267 |
+
"""
|
| 268 |
+
else:
|
| 269 |
+
# Generate summary from title only
|
| 270 |
+
prompt = f"""
|
| 271 |
+
Create an engaging, concise summary (2-3 sentences, ~50-80 words) for this article based on its title:
|
| 272 |
+
|
| 273 |
+
Title: {article.title}
|
| 274 |
+
Source: {article.source}
|
| 275 |
+
|
| 276 |
+
Make it intriguing and accessible while staying true to what the title suggests. Focus on why readers should care about this topic.
|
| 277 |
+
"""
|
| 278 |
+
|
| 279 |
+
enhanced_summary = _chat([
|
| 280 |
+
{"role": "system", "content": "You are an expert content writer who creates engaging, accessible summaries for busy readers interested in AI and technology."},
|
| 281 |
+
{"role": "user", "content": prompt}
|
| 282 |
+
], temperature=0.7)
|
| 283 |
+
|
| 284 |
+
# Create new article with enhanced summary
|
| 285 |
+
enhanced_articles.append(Article(
|
| 286 |
+
title=article.title,
|
| 287 |
+
link=article.link,
|
| 288 |
+
summary=enhanced_summary.strip(),
|
| 289 |
+
published=article.published,
|
| 290 |
+
source=article.source
|
| 291 |
+
))
|
| 292 |
+
|
| 293 |
+
except Exception as e:
|
| 294 |
+
# If LLM fails, keep original article
|
| 295 |
+
print(f"Failed to enhance summary for {article.title}: {e}")
|
| 296 |
+
enhanced_articles.append(article)
|
| 297 |
+
|
| 298 |
+
return enhanced_articles
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
@app.post("/api/aggregate", response_model=AggregateResponse)
|
| 302 |
+
def aggregate(req: AggregateRequest) -> AggregateResponse:
|
| 303 |
+
# Only retrieve from explicitly selected sources. If none provided, return empty.
|
| 304 |
+
sources = req.sources or []
|
| 305 |
+
cutoff = datetime.now(timezone.utc) - timedelta(days=req.since_days)
|
| 306 |
+
|
| 307 |
+
collected: List[Article] = []
|
| 308 |
+
for src in sources:
|
| 309 |
+
feed = feedparser.parse(str(src))
|
| 310 |
+
source_title = getattr(feed.feed, "title", None) or "Unknown Source"
|
| 311 |
+
for entry in feed.entries[:50]:
|
| 312 |
+
published = None
|
| 313 |
+
published_dt: Optional[datetime] = None
|
| 314 |
+
if hasattr(entry, "published"):
|
| 315 |
+
published = entry.published
|
| 316 |
+
published_dt = _parse_date(published)
|
| 317 |
+
elif hasattr(entry, "updated"):
|
| 318 |
+
published = entry.updated
|
| 319 |
+
published_dt = _parse_date(published)
|
| 320 |
+
|
| 321 |
+
# Filter by recency if date available
|
| 322 |
+
if published_dt and published_dt.tzinfo is None:
|
| 323 |
+
published_dt = published_dt.replace(tzinfo=timezone.utc)
|
| 324 |
+
if published_dt and published_dt < cutoff:
|
| 325 |
+
continue
|
| 326 |
+
|
| 327 |
+
summary = getattr(entry, "summary", None)
|
| 328 |
+
if summary:
|
| 329 |
+
# Decode HTML entities and clean up
|
| 330 |
+
summary = unescape(summary.strip())
|
| 331 |
+
link = getattr(entry, "link", None)
|
| 332 |
+
title = getattr(entry, "title", None)
|
| 333 |
+
if not (title and link):
|
| 334 |
+
continue
|
| 335 |
+
|
| 336 |
+
collected.append(
|
| 337 |
+
Article(
|
| 338 |
+
title=title,
|
| 339 |
+
link=link,
|
| 340 |
+
summary=summary,
|
| 341 |
+
published=published,
|
| 342 |
+
source=source_title,
|
| 343 |
+
)
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
# Generate engaging summaries for articles that don't have them or improve existing ones
|
| 347 |
+
if openai_client and collected:
|
| 348 |
+
enhanced_articles = _generate_engaging_summaries(collected[:10]) # Limit to first 10 for performance
|
| 349 |
+
# Update the collected articles with enhanced summaries
|
| 350 |
+
for i, enhanced in enumerate(enhanced_articles):
|
| 351 |
+
if i < len(collected):
|
| 352 |
+
collected[i] = enhanced
|
| 353 |
+
|
| 354 |
+
return AggregateResponse(articles=collected)
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
# ----- Simple Web Scraper (no external heavy deps) -----
|
| 358 |
+
class ScrapeRequest(BaseModel):
|
| 359 |
+
url: AnyHttpUrl
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
class ScrapeResponse(BaseModel):
|
| 363 |
+
content_text: str
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def _extract_main_text(html: str) -> str:
|
| 367 |
+
# Try to focus on <article> or <main> blocks first
|
| 368 |
+
try:
|
| 369 |
+
article_match = re.search(r"<article[\s\S]*?</article>", html, flags=re.IGNORECASE)
|
| 370 |
+
main_match = re.search(r"<main[\s\S]*?</main>", html, flags=re.IGNORECASE)
|
| 371 |
+
snippet = None
|
| 372 |
+
if article_match:
|
| 373 |
+
snippet = article_match.group(0)
|
| 374 |
+
elif main_match:
|
| 375 |
+
snippet = main_match.group(0)
|
| 376 |
+
else:
|
| 377 |
+
snippet = html
|
| 378 |
+
# Remove scripts/styles
|
| 379 |
+
snippet = re.sub(r"<script[\s\S]*?</script>", " ", snippet, flags=re.IGNORECASE)
|
| 380 |
+
snippet = re.sub(r"<style[\s\S]*?</style>", " ", snippet, flags=re.IGNORECASE)
|
| 381 |
+
# Strip tags
|
| 382 |
+
text = re.sub(r"<[^>]+>", " ", snippet)
|
| 383 |
+
text = unescape(text)
|
| 384 |
+
# Collapse whitespace
|
| 385 |
+
text = re.sub(r"\s+", " ", text).strip()
|
| 386 |
+
return text
|
| 387 |
+
except Exception:
|
| 388 |
+
return ""
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
@app.post("/api/scrape", response_model=ScrapeResponse)
|
| 392 |
+
def scrape(req: ScrapeRequest) -> ScrapeResponse:
|
| 393 |
+
try:
|
| 394 |
+
with httpx.Client(timeout=10.0, follow_redirects=True, headers={"User-Agent": "Mozilla/5.0 (compatible; AI-Newsletter/1.0)"}) as client:
|
| 395 |
+
resp = client.get(str(req.url))
|
| 396 |
+
resp.raise_for_status()
|
| 397 |
+
text = _extract_main_text(resp.text)
|
| 398 |
+
# Limit to a safe size for LLM context
|
| 399 |
+
if len(text) > 8000:
|
| 400 |
+
text = text[:8000]
|
| 401 |
+
return ScrapeResponse(content_text=text)
|
| 402 |
+
except Exception:
|
| 403 |
+
return ScrapeResponse(content_text="")
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
class HighlightsRequest(BaseModel):
|
| 407 |
+
sources: List[AnyHttpUrl]
|
| 408 |
+
since_days: int = Field(default=7, ge=1, le=31)
|
| 409 |
+
max_articles: int = Field(default=8, ge=1, le=20)
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
class HighlightsResponse(BaseModel):
|
| 413 |
+
items: List[HighlightItem]
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
@app.post("/api/summaries", response_model=HighlightsResponse)
|
| 417 |
+
def summaries(req: HighlightsRequest) -> HighlightsResponse:
|
| 418 |
+
# Enforce selection: if no sources, return empty list
|
| 419 |
+
if not req.sources:
|
| 420 |
+
return HighlightsResponse(items=[])
|
| 421 |
+
|
| 422 |
+
articles_resp = aggregate(AggregateRequest(sources=req.sources, since_days=req.since_days))
|
| 423 |
+
items: List[HighlightItem] = []
|
| 424 |
+
|
| 425 |
+
# Use configurable limit (default 8, max 20)
|
| 426 |
+
limited_articles = articles_resp.articles[:req.max_articles]
|
| 427 |
+
|
| 428 |
+
for a in limited_articles:
|
| 429 |
+
# Scrape content with shorter timeout
|
| 430 |
+
content_text = ""
|
| 431 |
+
try:
|
| 432 |
+
with httpx.Client(timeout=5.0, follow_redirects=True, headers={"User-Agent": "Mozilla/5.0 (compatible; AI-Newsletter/1.0)"}) as client:
|
| 433 |
+
resp = client.get(str(a.link))
|
| 434 |
+
resp.raise_for_status()
|
| 435 |
+
raw_html = resp.text
|
| 436 |
+
content_text = _extract_main_text(raw_html)
|
| 437 |
+
except Exception:
|
| 438 |
+
# Fallback to RSS summary if scraping fails
|
| 439 |
+
content_text = a.summary or ""
|
| 440 |
+
|
| 441 |
+
if len(content_text) > 4000: # Reduced from 8000 for faster processing
|
| 442 |
+
content_text = content_text[:4000]
|
| 443 |
+
|
| 444 |
+
# If no content available, use title and RSS summary
|
| 445 |
+
if not content_text.strip():
|
| 446 |
+
content_text = f"Title: {a.title}\nRSS Summary: {a.summary or 'No summary available'}"
|
| 447 |
+
|
| 448 |
+
# Summarize the single article's content
|
| 449 |
+
system = (
|
| 450 |
+
"You are an expert AI news editor. Summarize the article content for a busy technical audience. "
|
| 451 |
+
"Be concise (3-5 bullet points), capture key findings. If content is limited, work with what's available."
|
| 452 |
+
)
|
| 453 |
+
user = (
|
| 454 |
+
f"Title: {a.title}\nSource: {a.source or ''}\nURL: {a.link}\n\n"
|
| 455 |
+
f"Content:\n{content_text}\n\n"
|
| 456 |
+
"Write a clear, concise summary."
|
| 457 |
+
)
|
| 458 |
+
|
| 459 |
+
try:
|
| 460 |
+
summary_text = _chat([
|
| 461 |
+
{"role": "system", "content": system},
|
| 462 |
+
{"role": "user", "content": user},
|
| 463 |
+
], temperature=0.3)
|
| 464 |
+
except Exception:
|
| 465 |
+
# Fallback if OpenAI fails
|
| 466 |
+
summary_text = a.summary or f"Unable to generate summary for: {a.title}"
|
| 467 |
+
|
| 468 |
+
items.append(HighlightItem(title=a.title, link=a.link, source=a.source, summary=summary_text.strip()))
|
| 469 |
+
|
| 470 |
+
return HighlightsResponse(items=items)
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
@app.post("/api/summaries_selected", response_model=HighlightsResponse)
|
| 474 |
+
def summaries_selected(req: SummariesSelectedRequest) -> HighlightsResponse:
|
| 475 |
+
"""Process summaries for only selected articles (no RSS aggregation needed)"""
|
| 476 |
+
items: List[HighlightItem] = []
|
| 477 |
+
|
| 478 |
+
for a in req.articles[:5]: # Limit to 5 articles max for performance
|
| 479 |
+
# Scrape content with shorter timeout
|
| 480 |
+
content_text = ""
|
| 481 |
+
try:
|
| 482 |
+
with httpx.Client(timeout=5.0, follow_redirects=True, headers={"User-Agent": "Mozilla/5.0 (compatible; AI-Newsletter/1.0)"}) as client:
|
| 483 |
+
resp = client.get(str(a.link))
|
| 484 |
+
resp.raise_for_status()
|
| 485 |
+
raw_html = resp.text
|
| 486 |
+
content_text = _extract_main_text(raw_html)
|
| 487 |
+
except Exception:
|
| 488 |
+
# Fallback to RSS summary if scraping fails
|
| 489 |
+
content_text = a.summary or ""
|
| 490 |
+
|
| 491 |
+
if len(content_text) > 4000: # Reduced for faster processing
|
| 492 |
+
content_text = content_text[:4000]
|
| 493 |
+
|
| 494 |
+
# If no content available, use title and RSS summary
|
| 495 |
+
if not content_text.strip():
|
| 496 |
+
content_text = f"Title: {a.title}\nRSS Summary: {a.summary or 'No summary available'}"
|
| 497 |
+
|
| 498 |
+
# Summarize the single article's content
|
| 499 |
+
system = (
|
| 500 |
+
"You are an expert AI news editor. Summarize the article content for a busy technical audience. "
|
| 501 |
+
"Be concise (3-5 bullet points), capture key findings. If content is limited, work with what's available."
|
| 502 |
+
)
|
| 503 |
+
user = (
|
| 504 |
+
f"Title: {a.title}\nSource: {a.source or ''}\nURL: {a.link}\n\n"
|
| 505 |
+
f"Content:\n{content_text}\n\n"
|
| 506 |
+
"Write a clear, concise summary."
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
try:
|
| 510 |
+
summary_text = _chat([
|
| 511 |
+
{"role": "system", "content": system},
|
| 512 |
+
{"role": "user", "content": user},
|
| 513 |
+
], temperature=0.3)
|
| 514 |
+
except Exception:
|
| 515 |
+
# Fallback if OpenAI fails
|
| 516 |
+
summary_text = a.summary or f"Unable to generate summary for: {a.title}"
|
| 517 |
+
|
| 518 |
+
items.append(HighlightItem(title=a.title, link=a.link, source=a.source, summary=summary_text.strip()))
|
| 519 |
+
|
| 520 |
+
return HighlightsResponse(items=items)
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
def _chat(messages: List[Dict[str, str]], temperature: float = 0.4) -> str:
|
| 524 |
+
if not openai_client:
|
| 525 |
+
raise Exception("OpenAI client not initialized - API key missing")
|
| 526 |
+
|
| 527 |
+
completion = openai_client.chat.completions.create(
|
| 528 |
+
model=MODEL,
|
| 529 |
+
messages=messages,
|
| 530 |
+
temperature=temperature,
|
| 531 |
+
)
|
| 532 |
+
return completion.choices[0].message.content or ""
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
@app.post("/api/highlights", response_model=SummarizeResponse)
|
| 536 |
+
def highlights_endpoint(req: SummarizeRequest) -> SummarizeResponse:
|
| 537 |
+
if not os.getenv("OPENAI_API_KEY"):
|
| 538 |
+
raise HTTPException(status_code=500, detail="OPENAI_API_KEY not set")
|
| 539 |
+
|
| 540 |
+
memory = get_memory(req.session_id)
|
| 541 |
+
if req.prior_history:
|
| 542 |
+
memory.history.extend(req.prior_history[-8:])
|
| 543 |
+
# Build context from articles
|
| 544 |
+
articles_text = "\n".join(
|
| 545 |
+
[
|
| 546 |
+
f"- {a.title} ({a.source}) — {a.link}\n{a.summary or ''}"
|
| 547 |
+
for a in req.articles[:20]
|
| 548 |
+
]
|
| 549 |
+
)
|
| 550 |
+
|
| 551 |
+
# Anchor summary to the current week to avoid stale dates from the model
|
| 552 |
+
now_local = datetime.now()
|
| 553 |
+
week_start = now_local - timedelta(days=now_local.weekday()) # Monday
|
| 554 |
+
week_of = week_start.strftime("%b %d, %Y")
|
| 555 |
+
|
| 556 |
+
system = (
|
| 557 |
+
"You are an expert AI news editor. Create a crisp weekly summary for a technical audience. "
|
| 558 |
+
"Use clear section headings, bullet points, and callouts. Include hyperlinks when relevant. "
|
| 559 |
+
f"Always label the summary with a top heading 'Week of {week_of}'."
|
| 560 |
+
)
|
| 561 |
+
user = (
|
| 562 |
+
f"Write a weekly highlights summary based on these items:\n\n{articles_text}\n\n"
|
| 563 |
+
f"Instructions: {req.instructions}"
|
| 564 |
+
)
|
| 565 |
+
|
| 566 |
+
messages: List[Dict[str, str]] = (
|
| 567 |
+
[
|
| 568 |
+
{"role": "system", "content": system},
|
| 569 |
+
]
|
| 570 |
+
+ [{"role": t.role, "content": t.content} for t in memory.history[-6:]]
|
| 571 |
+
+ [
|
| 572 |
+
{"role": "user", "content": user},
|
| 573 |
+
]
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
content = _chat(messages, temperature=0.3)
|
| 577 |
+
# Ensure the summary includes the correct 'Week of' label without duplication
|
| 578 |
+
content_clean = content.strip()
|
| 579 |
+
if not content_clean.lower().startswith(("week of", "# week of", "## week of")):
|
| 580 |
+
content = f"## Week of {week_of}\n\n" + content_clean
|
| 581 |
+
else:
|
| 582 |
+
content = content_clean
|
| 583 |
+
memory.last_summary = content
|
| 584 |
+
memory.history.append(ConversationTurn(role="user", content=user))
|
| 585 |
+
memory.history.append(ConversationTurn(role="assistant", content=content))
|
| 586 |
+
return SummarizeResponse(summary_markdown=content)
|
| 587 |
+
|
| 588 |
+
|
| 589 |
+
@app.post("/api/tweets", response_model=TweetsResponse)
|
| 590 |
+
def generate_tweets(req: TweetsRequest) -> TweetsResponse:
|
| 591 |
+
memory = get_memory(req.session_id)
|
| 592 |
+
if req.prior_history:
|
| 593 |
+
memory.history.extend(req.prior_history[-8:])
|
| 594 |
+
|
| 595 |
+
tweets: List[Tweet] = []
|
| 596 |
+
|
| 597 |
+
for i, summary in enumerate(req.summaries):
|
| 598 |
+
system = (
|
| 599 |
+
"You write engaging, factual, and concise Twitter posts (X). "
|
| 600 |
+
"Create ONE tweet about this specific AI news article."
|
| 601 |
+
)
|
| 602 |
+
user = (
|
| 603 |
+
f"Create a single engaging tweet about this AI news article:\n\n"
|
| 604 |
+
f"Title: {summary.title}\n"
|
| 605 |
+
f"Source: {summary.source}\n"
|
| 606 |
+
f"Summary: {summary.summary}\n\n"
|
| 607 |
+
"Include 1-2 relevant emojis and 1-2 hashtags. Keep under 280 characters. "
|
| 608 |
+
"Return only the tweet text, no JSON formatting."
|
| 609 |
+
)
|
| 610 |
+
|
| 611 |
+
messages = (
|
| 612 |
+
[{"role": "system", "content": system}]
|
| 613 |
+
+ [{"role": t.role, "content": t.content} for t in memory.history[-4:]]
|
| 614 |
+
+ [{"role": "user", "content": user}]
|
| 615 |
+
)
|
| 616 |
+
|
| 617 |
+
try:
|
| 618 |
+
tweet_content = _chat(messages, temperature=0.7)
|
| 619 |
+
# Clean up the response
|
| 620 |
+
tweet_content = tweet_content.strip().strip('"').strip("'")
|
| 621 |
+
|
| 622 |
+
tweet = Tweet(
|
| 623 |
+
id=f"tweet_{i}_{summary.title[:20].replace(' ', '_')}",
|
| 624 |
+
content=tweet_content,
|
| 625 |
+
summary_title=summary.title,
|
| 626 |
+
summary_link=str(summary.link),
|
| 627 |
+
summary_source=summary.source or "Unknown"
|
| 628 |
+
)
|
| 629 |
+
tweets.append(tweet)
|
| 630 |
+
|
| 631 |
+
except Exception:
|
| 632 |
+
# Fallback tweet if AI generation fails
|
| 633 |
+
fallback_content = f"🤖 {summary.title[:200]}... #AI #Tech"
|
| 634 |
+
tweet = Tweet(
|
| 635 |
+
id=f"tweet_{i}_{summary.title[:20].replace(' ', '_')}",
|
| 636 |
+
content=fallback_content,
|
| 637 |
+
summary_title=summary.title,
|
| 638 |
+
summary_link=str(summary.link),
|
| 639 |
+
summary_source=summary.source or "Unknown"
|
| 640 |
+
)
|
| 641 |
+
tweets.append(tweet)
|
| 642 |
+
|
| 643 |
+
# Store conversation context
|
| 644 |
+
turn_user = ConversationTurn(role="user", content=f"Generated {len(tweets)} tweets from summaries")
|
| 645 |
+
turn_assistant = ConversationTurn(role="assistant", content="Tweets generated successfully")
|
| 646 |
+
memory.history.append(turn_user)
|
| 647 |
+
memory.history.append(turn_assistant)
|
| 648 |
+
|
| 649 |
+
memory.last_tweets = [t.content for t in tweets] # Store for backward compatibility
|
| 650 |
+
return TweetsResponse(tweets=tweets)
|
| 651 |
+
|
| 652 |
+
|
| 653 |
+
def _build_newsletter_html(summary_md: str, articles: List[Article]) -> str:
|
| 654 |
+
# Select featured article (first article with good content)
|
| 655 |
+
featured_article = None
|
| 656 |
+
remaining_articles = []
|
| 657 |
+
|
| 658 |
+
for article in articles[:8]: # Use first 8 articles
|
| 659 |
+
if not featured_article and article.summary and len(article.summary) > 100:
|
| 660 |
+
featured_article = article
|
| 661 |
+
else:
|
| 662 |
+
remaining_articles.append(article)
|
| 663 |
+
|
| 664 |
+
# If no good featured article found, use the first one
|
| 665 |
+
if not featured_article and articles:
|
| 666 |
+
featured_article = articles[0]
|
| 667 |
+
remaining_articles = articles[1:8]
|
| 668 |
+
|
| 669 |
+
# Build news grid items (max 6 items, 2x3 grid)
|
| 670 |
+
news_items = ""
|
| 671 |
+
for i, article in enumerate(remaining_articles[:6]):
|
| 672 |
+
news_items += f"""
|
| 673 |
+
<div class="news-item">
|
| 674 |
+
<h4>{article.title}</h4>
|
| 675 |
+
<p>{(article.summary or 'Click to read more about this story.')[:150]}{'...' if len(article.summary or '') > 150 else ''}</p>
|
| 676 |
+
<a href="{article.link}" class="read-more">Read more →</a>
|
| 677 |
+
</div>
|
| 678 |
+
"""
|
| 679 |
+
|
| 680 |
+
now = datetime.now().strftime("%B %d, %Y")
|
| 681 |
+
|
| 682 |
+
# Format featured article
|
| 683 |
+
featured_title = featured_article.title if featured_article else "AI Weekly Highlights"
|
| 684 |
+
featured_summary = (featured_article.summary or "This week brings exciting developments in AI and technology.")[:200] + "..." if featured_article and len(featured_article.summary or "") > 200 else (featured_article.summary if featured_article else "This week brings exciting developments in AI and technology.")
|
| 685 |
+
featured_link = featured_article.link if featured_article else "#"
|
| 686 |
+
|
| 687 |
+
return f"""<!DOCTYPE html>
|
| 688 |
+
<html lang="en">
|
| 689 |
+
<head>
|
| 690 |
+
<meta charset="UTF-8">
|
| 691 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 692 |
+
<title>AI Weekly - Newsletter</title>
|
| 693 |
+
<style>
|
| 694 |
+
* {{
|
| 695 |
+
margin: 0;
|
| 696 |
+
padding: 0;
|
| 697 |
+
box-sizing: border-box;
|
| 698 |
+
}}
|
| 699 |
+
|
| 700 |
+
body {{
|
| 701 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 702 |
+
line-height: 1.6;
|
| 703 |
+
background-color: #f4f4f4;
|
| 704 |
+
color: #333;
|
| 705 |
+
}}
|
| 706 |
+
|
| 707 |
+
.container {{
|
| 708 |
+
max-width: 600px;
|
| 709 |
+
margin: 20px auto;
|
| 710 |
+
background-color: white;
|
| 711 |
+
box-shadow: 0 0 20px rgba(0,0,0,0.1);
|
| 712 |
+
}}
|
| 713 |
+
|
| 714 |
+
.header {{
|
| 715 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 716 |
+
color: white;
|
| 717 |
+
padding: 30px 20px;
|
| 718 |
+
text-align: center;
|
| 719 |
+
}}
|
| 720 |
+
|
| 721 |
+
.logo {{
|
| 722 |
+
font-size: 28px;
|
| 723 |
+
font-weight: bold;
|
| 724 |
+
margin-bottom: 10px;
|
| 725 |
+
}}
|
| 726 |
+
|
| 727 |
+
.tagline {{
|
| 728 |
+
font-size: 14px;
|
| 729 |
+
opacity: 0.9;
|
| 730 |
+
}}
|
| 731 |
+
|
| 732 |
+
.content {{
|
| 733 |
+
padding: 30px 20px;
|
| 734 |
+
}}
|
| 735 |
+
|
| 736 |
+
.section {{
|
| 737 |
+
margin-bottom: 30px;
|
| 738 |
+
border-bottom: 1px solid #eee;
|
| 739 |
+
padding-bottom: 30px;
|
| 740 |
+
}}
|
| 741 |
+
|
| 742 |
+
.section:last-child {{
|
| 743 |
+
border-bottom: none;
|
| 744 |
+
margin-bottom: 0;
|
| 745 |
+
padding-bottom: 0;
|
| 746 |
+
}}
|
| 747 |
+
|
| 748 |
+
.section h2 {{
|
| 749 |
+
color: #667eea;
|
| 750 |
+
font-size: 22px;
|
| 751 |
+
margin-bottom: 15px;
|
| 752 |
+
border-left: 4px solid #667eea;
|
| 753 |
+
padding-left: 15px;
|
| 754 |
+
}}
|
| 755 |
+
|
| 756 |
+
.featured-article {{
|
| 757 |
+
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
| 758 |
+
color: white;
|
| 759 |
+
padding: 25px;
|
| 760 |
+
border-radius: 10px;
|
| 761 |
+
margin-bottom: 20px;
|
| 762 |
+
}}
|
| 763 |
+
|
| 764 |
+
.featured-article h3 {{
|
| 765 |
+
font-size: 20px;
|
| 766 |
+
margin-bottom: 10px;
|
| 767 |
+
}}
|
| 768 |
+
|
| 769 |
+
.featured-article p {{
|
| 770 |
+
margin-bottom: 15px;
|
| 771 |
+
opacity: 0.95;
|
| 772 |
+
}}
|
| 773 |
+
|
| 774 |
+
.btn {{
|
| 775 |
+
display: inline-block;
|
| 776 |
+
background-color: white;
|
| 777 |
+
color: #f5576c;
|
| 778 |
+
padding: 12px 25px;
|
| 779 |
+
text-decoration: none;
|
| 780 |
+
border-radius: 25px;
|
| 781 |
+
font-weight: bold;
|
| 782 |
+
transition: transform 0.3s ease;
|
| 783 |
+
}}
|
| 784 |
+
|
| 785 |
+
.btn:hover {{
|
| 786 |
+
transform: translateY(-2px);
|
| 787 |
+
}}
|
| 788 |
+
|
| 789 |
+
.news-grid {{
|
| 790 |
+
display: grid;
|
| 791 |
+
grid-template-columns: 1fr 1fr;
|
| 792 |
+
gap: 20px;
|
| 793 |
+
margin-top: 20px;
|
| 794 |
+
}}
|
| 795 |
+
|
| 796 |
+
.news-item {{
|
| 797 |
+
border: 1px solid #eee;
|
| 798 |
+
border-radius: 8px;
|
| 799 |
+
padding: 20px;
|
| 800 |
+
transition: box-shadow 0.3s ease;
|
| 801 |
+
}}
|
| 802 |
+
|
| 803 |
+
.news-item:hover {{
|
| 804 |
+
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
| 805 |
+
}}
|
| 806 |
+
|
| 807 |
+
.news-item h4 {{
|
| 808 |
+
color: #333;
|
| 809 |
+
margin-bottom: 10px;
|
| 810 |
+
font-size: 16px;
|
| 811 |
+
}}
|
| 812 |
+
|
| 813 |
+
.news-item p {{
|
| 814 |
+
font-size: 14px;
|
| 815 |
+
color: #666;
|
| 816 |
+
margin-bottom: 10px;
|
| 817 |
+
}}
|
| 818 |
+
|
| 819 |
+
.read-more {{
|
| 820 |
+
color: #667eea;
|
| 821 |
+
text-decoration: none;
|
| 822 |
+
font-size: 14px;
|
| 823 |
+
font-weight: bold;
|
| 824 |
+
}}
|
| 825 |
+
|
| 826 |
+
.cta-section {{
|
| 827 |
+
background-color: #f8f9fa;
|
| 828 |
+
padding: 30px;
|
| 829 |
+
text-align: center;
|
| 830 |
+
border-radius: 10px;
|
| 831 |
+
}}
|
| 832 |
+
|
| 833 |
+
.cta-section h3 {{
|
| 834 |
+
color: #333;
|
| 835 |
+
margin-bottom: 15px;
|
| 836 |
+
}}
|
| 837 |
+
|
| 838 |
+
.cta-btn {{
|
| 839 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 840 |
+
color: white;
|
| 841 |
+
padding: 15px 30px;
|
| 842 |
+
text-decoration: none;
|
| 843 |
+
border-radius: 30px;
|
| 844 |
+
font-weight: bold;
|
| 845 |
+
display: inline-block;
|
| 846 |
+
margin-top: 10px;
|
| 847 |
+
}}
|
| 848 |
+
|
| 849 |
+
.social-links {{
|
| 850 |
+
text-align: center;
|
| 851 |
+
margin-top: 30px;
|
| 852 |
+
}}
|
| 853 |
+
|
| 854 |
+
.social-links a {{
|
| 855 |
+
display: inline-block;
|
| 856 |
+
margin: 0 10px;
|
| 857 |
+
width: 40px;
|
| 858 |
+
height: 40px;
|
| 859 |
+
background-color: #667eea;
|
| 860 |
+
color: white;
|
| 861 |
+
text-decoration: none;
|
| 862 |
+
border-radius: 50%;
|
| 863 |
+
line-height: 40px;
|
| 864 |
+
transition: background-color 0.3s ease;
|
| 865 |
+
}}
|
| 866 |
+
|
| 867 |
+
.social-links a:hover {{
|
| 868 |
+
background-color: #764ba2;
|
| 869 |
+
}}
|
| 870 |
+
|
| 871 |
+
.footer {{
|
| 872 |
+
background-color: #333;
|
| 873 |
+
color: white;
|
| 874 |
+
padding: 30px 20px;
|
| 875 |
+
text-align: center;
|
| 876 |
+
}}
|
| 877 |
+
|
| 878 |
+
.footer p {{
|
| 879 |
+
margin-bottom: 10px;
|
| 880 |
+
font-size: 14px;
|
| 881 |
+
}}
|
| 882 |
+
|
| 883 |
+
.footer a {{
|
| 884 |
+
color: #667eea;
|
| 885 |
+
text-decoration: none;
|
| 886 |
+
}}
|
| 887 |
+
|
| 888 |
+
@media (max-width: 600px) {{
|
| 889 |
+
.news-grid {{
|
| 890 |
+
grid-template-columns: 1fr;
|
| 891 |
+
}}
|
| 892 |
+
|
| 893 |
+
.container {{
|
| 894 |
+
margin: 10px;
|
| 895 |
+
}}
|
| 896 |
+
|
| 897 |
+
.content {{
|
| 898 |
+
padding: 20px 15px;
|
| 899 |
+
}}
|
| 900 |
+
}}
|
| 901 |
+
</style>
|
| 902 |
+
</head>
|
| 903 |
+
<body>
|
| 904 |
+
<div class="container">
|
| 905 |
+
<!-- Header -->
|
| 906 |
+
<div class="header">
|
| 907 |
+
<div class="logo" style="display:flex; align-items:center; justify-content:center; gap:10px;">
|
| 908 |
+
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcz4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibG9nb0dyYWQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjNjY3ZWVhIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iNTAlIiBzdG9wLWNvbG9yPSIjNzY0YmEyIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2YwOTNmYiIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICA8L2RlZnM+CiAgCiAgPCEtLSBNYWluIGNvbnRhaW5lciBjaXJjbGUgLS0+CiAgPGNpcmNsZSBjeD0iMzIiIGN5PSIzMiIgcj0iMjgiIGZpbGw9InVybCgjbG9nb0dyYWQpIi8+CiAgCiAgPCEtLSBOZXVyYWwgbmV0d29yayBub2RlcyAtLT4KICA8ZyBmaWxsPSIjZmZmZmZmIiBvcGFjaXR5PSIwLjkiPgogICAgPCEtLSBJbnB1dCBsYXllciAtLT4KICAgIDxjaXJjbGUgY3g9IjE4IiBjeT0iMjQiIHI9IjIuNSIvPgogICAgPGNpcmNsZSBjeD0iMTgiIGN5PSIzMiIgcj0iMi41Ii8+CiAgICA8Y2lyY2xlIGN4PSIxOCIgY3k9IjQwIiByPSIyLjUiLz4KICAgIAogICAgPCEtLSBIaWRkZW4gbGF5ZXIgLS0+CiAgICA8Y2lyY2xlIGN4PSIzMiIgY3k9IjIwIiByPSIyIi8+CiAgICA8Y2lyY2xlIGN4PSIzMiIgY3k9IjI4IiByPSIyIi8+CiAgICA8Y2lyY2xlIGN4PSIzMiIgY3k9IjM2IiByPSIyIi8+CiAgICA8Y2lyY2xlIGN4PSIzMiIgY3k9IjQ0IiByPSIyIi8+CiAgICAKICAgIDwhLS0gT3V0cHV0IGxheWVyIC0tPgogICAgPGNpcmNsZSBjeD0iNDYiIGN5PSIyOCIgcj0iMi41Ii8+CiAgICA8Y2lyY2xlIGN4PSI0NiIgY3k9IjM2IiByPSIyLjUiLz4KICA8L2c+CiAgCiAgPCEtLSBOZXVyYWwgbmV0d29yayBjb25uZWN0aW9ucyAtLT4KICA8ZyBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMSIgb3BhY2l0eT0iMC42IiBmaWxsPSJub25lIj4KICAgIDwhLS0gSW5wdXQgdG8gaGlkZGVuIGNvbm5lY3Rpb25zIC0tPgogICAgPGxpbmUgeDE9IjIwLjUiIHkxPSIyNCIgeDI9IjMwIiB5Mj0iMjAiLz4KICAgIDxsaW5lIHgxPSIyMC41IiB5MT0iMjQiIHgyPSIzMCIgeTI9IjI4Ii8+CiAgICA8bGluZSB4MT0iMjAuNSIgeTE9IjMyIiB4Mj0iMzAiIHkyPSIyOCIvPgogICAgPGxpbmUgeDE9IjIwLjUiIHkxPSIzMiIgeDI9IjMwIiB5Mj0iMzYiLz4KICAgIDxsaW5lIHgxPSIyMC41IiB5MT0iNDAiIHgyPSIzMCIgeTI9IjM2Ii8+CiAgICA8bGluZSB4MT0iMjAuNSIgeTE9IjQwIiB4Mj0iMzAiIHkyPSI0NCIvPgogICAgCiAgICA8IS0tIEhpZGRlbiB0byBvdXRwdXQgY29ubmVjdGlvbnMgLS0+CiAgICA8bGluZSB4MT0iMzQiIHkxPSIyMCIgeDI9IjQzLjUiIHkyPSIyOCIvPgogICAgPGxpbmUgeDE9IjM0IiB5MT0iMjgiIHgyPSI0My41IiB5Mj0iMjgiLz4KICAgIDxsaW5lIHgxPSIzNCIgeTE9IjM2IiB4Mj0iNDMuNSIgeTI9IjM2Ii8+CiAgICA8bGluZSB4MT0iMzQiIHkxPSI0NCIgeDI9IjQzLjUiIHkyPSIzNiIvPgogIDwvZz4KICAKICA8IS0tIEFJIHN5bWJvbCBpbiBjZW50ZXIgLS0+CiAgPGcgZmlsbD0iI2ZmZmZmZiIgb3BhY2l0eT0iMC44Ij4KICAgIDx0ZXh0IHg9IjMyIiB5PSIxNiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjgiIGZvbnQtd2VpZ2h0PSJib2xkIj5BSTwvdGV4dD4KICA8L2c+CiAgCiAgPCEtLSBOZXdzbGV0dGVyL2RvY3VtZW50IGljb24gLS0+CiAgPGcgZmlsbD0iI2ZmZmZmZiIgb3BhY2l0eT0iMC43Ij4KICAgIDxyZWN0IHg9IjI2IiB5PSI0OCIgd2lkdGg9IjEyIiBoZWlnaHQ9IjgiIHJ4PSIxIi8+CiAgICA8bGluZSB4MT0iMjgiIHkxPSI1MCIgeDI9IjM2IiB5Mj0iNTAiIHN0cm9rZT0iIzY2N2VlYSIgc3Ryb2tlLXdpZHRoPSIwLjgiLz4KICAgIDxsaW5lIHgxPSIyOCIgeTE9IjUyIiB4Mj0iMzQiIHkyPSI1MiIgc3Ryb2tlPSIjNjY3ZWVhIiBzdHJva2Utd2lkdGg9IjAuOCIvPgogICAgPGxpbmUgeDE9IjI4IiB5MT0iNTQiIHgyPSIzNiIgeTI9IjU0IiBzdHJva2U9IiM2NjdlZWEiIHN0cm9rZS13aWR0aD0iMC44Ii8+CiAgPC9nPgo8L3N2Zz4K" alt="AI Weekly" width="28" height="28" style="display:inline-block; vertical-align:middle;" />
|
| 909 |
+
<span>AI Weekly</span>
|
| 910 |
+
</div>
|
| 911 |
+
<div class="tagline">Your weekly dose of AI insights • {now}</div>
|
| 912 |
+
</div>
|
| 913 |
+
|
| 914 |
+
<!-- Main Content -->
|
| 915 |
+
<div class="content">
|
| 916 |
+
<!-- Welcome Section -->
|
| 917 |
+
<div class="section">
|
| 918 |
+
<h2>📧 This Week's Highlights</h2>
|
| 919 |
+
<p>Hello AI Tech Enthusiasts! Welcome to another edition of AI Weekly. This week, we're diving deep into the latest AI developments, breakthrough innovations, and emerging technologies that are shaping our digital future.</p>
|
| 920 |
+
</div>
|
| 921 |
+
|
| 922 |
+
<!-- Featured Article -->
|
| 923 |
+
<div class="section">
|
| 924 |
+
<h2>🌟 Featured Story</h2>
|
| 925 |
+
<div class="featured-article">
|
| 926 |
+
<h3>{featured_title}</h3>
|
| 927 |
+
<p>{featured_summary}</p>
|
| 928 |
+
<a href="{featured_link}" class="btn">Read Full Article</a>
|
| 929 |
+
</div>
|
| 930 |
+
</div>
|
| 931 |
+
|
| 932 |
+
<!-- News Section -->
|
| 933 |
+
<div class="section">
|
| 934 |
+
<h2>📰 Latest AI News</h2>
|
| 935 |
+
<div class="news-grid">
|
| 936 |
+
{news_items}
|
| 937 |
+
</div>
|
| 938 |
+
</div>
|
| 939 |
+
|
| 940 |
+
<!-- CTA Section -->
|
| 941 |
+
<div class="section">
|
| 942 |
+
<div class="cta-section">
|
| 943 |
+
<h3>🤖 Stay Connected</h3>
|
| 944 |
+
<p>Join thousands of AI enthusiasts getting the latest insights delivered weekly.</p>
|
| 945 |
+
<a href="#" class="cta-btn">Subscribe for Updates</a>
|
| 946 |
+
</div>
|
| 947 |
+
</div>
|
| 948 |
+
|
| 949 |
+
<!-- Social Links -->
|
| 950 |
+
<div class="social-links">
|
| 951 |
+
<a href="#">🐦</a>
|
| 952 |
+
<a href="#">📘</a>
|
| 953 |
+
<a href="#">💼</a>
|
| 954 |
+
<a href="#">📧</a>
|
| 955 |
+
</div>
|
| 956 |
+
</div>
|
| 957 |
+
|
| 958 |
+
<!-- Footer -->
|
| 959 |
+
<div class="footer">
|
| 960 |
+
<p><strong>AI Weekly</strong></p>
|
| 961 |
+
<p>Curated with ❤️ by AI Newsletter</p>
|
| 962 |
+
<p>© 2025 AI Weekly. All rights reserved.</p>
|
| 963 |
+
<p style="margin-top: 20px;">
|
| 964 |
+
<a href="#">Unsubscribe</a> |
|
| 965 |
+
<a href="#">Update Preferences</a> |
|
| 966 |
+
<a href="#">Privacy Policy</a>
|
| 967 |
+
</p>
|
| 968 |
+
</div>
|
| 969 |
+
</div>
|
| 970 |
+
</body>
|
| 971 |
+
</html>"""
|
| 972 |
+
|
| 973 |
+
|
| 974 |
+
@app.post("/api/newsletter", response_model=NewsletterResponse)
|
| 975 |
+
def newsletter(req: NewsletterRequest) -> NewsletterResponse:
|
| 976 |
+
memory = get_memory(req.session_id)
|
| 977 |
+
if req.prior_history:
|
| 978 |
+
memory.history.extend(req.prior_history[-8:])
|
| 979 |
+
html = _build_newsletter_html(req.summary_markdown, req.articles)
|
| 980 |
+
memory.last_newsletter_html = html
|
| 981 |
+
return NewsletterResponse(html=html)
|
| 982 |
+
|
| 983 |
+
|
| 984 |
+
@app.post("/api/edit", response_model=EditResponse)
|
| 985 |
+
def edit(req: EditRequest) -> EditResponse:
|
| 986 |
+
memory = get_memory(req.session_id)
|
| 987 |
+
if req.prior_history:
|
| 988 |
+
# Allow client to supply recent context from local storage when serverless memory resets
|
| 989 |
+
memory.history.extend(req.prior_history[-8:])
|
| 990 |
+
|
| 991 |
+
system = (
|
| 992 |
+
"You are a helpful writing assistant. Edit the provided text according to the instruction, "
|
| 993 |
+
"preserving facts and links. Return only the edited text."
|
| 994 |
+
)
|
| 995 |
+
user = f"Instruction: {req.instruction}\n\nText to edit:\n{req.text}"
|
| 996 |
+
messages = (
|
| 997 |
+
[{"role": "system", "content": system}]
|
| 998 |
+
+ [{"role": t.role, "content": t.content} for t in memory.history[-8:]]
|
| 999 |
+
+ [{"role": "user", "content": user}]
|
| 1000 |
+
)
|
| 1001 |
+
content = _chat(messages, temperature=0.4)
|
| 1002 |
+
turn_user = ConversationTurn(role="user", content=user)
|
| 1003 |
+
turn_assistant = ConversationTurn(role="assistant", content=content)
|
| 1004 |
+
memory.history.append(turn_user)
|
| 1005 |
+
memory.history.append(turn_assistant)
|
| 1006 |
+
return EditResponse(edited_text=content, history=memory.history[-10:])
|
| 1007 |
+
|
| 1008 |
+
|
| 1009 |
+
@app.post("/api/edit_tweet", response_model=TweetEditResponse)
|
| 1010 |
+
def edit_tweet(req: TweetEditRequest) -> TweetEditResponse:
|
| 1011 |
+
# Get or create conversation history for this specific tweet
|
| 1012 |
+
conversation_key = f"{req.session_id}_tweet_{req.tweet_id}"
|
| 1013 |
+
if conversation_key not in memory_store:
|
| 1014 |
+
memory_store[conversation_key] = SessionMemory(session_id=conversation_key)
|
| 1015 |
+
|
| 1016 |
+
tweet_memory = memory_store[conversation_key]
|
| 1017 |
+
|
| 1018 |
+
# Add any provided conversation history
|
| 1019 |
+
if req.conversation_history:
|
| 1020 |
+
tweet_memory.history.extend(req.conversation_history)
|
| 1021 |
+
|
| 1022 |
+
system = (
|
| 1023 |
+
"You are an AI assistant helping to edit and improve Twitter/X posts. "
|
| 1024 |
+
"You have context about the original article summary and the current tweet. "
|
| 1025 |
+
"Help the user modify the tweet based on their requests while keeping it STRICTLY under 280 characters. "
|
| 1026 |
+
"CRITICAL: Count characters carefully - if adding hashtags would exceed 280 chars, shorten the main text to make room. "
|
| 1027 |
+
"IMPORTANT: Always structure your response as follows:\n"
|
| 1028 |
+
"1. A brief conversational response to the user\n"
|
| 1029 |
+
"2. Then on a new line, write 'UPDATED TWEET:' followed by the new tweet content\n"
|
| 1030 |
+
"Example format:\n"
|
| 1031 |
+
"Sure! I'll add more hashtags and shorten the text to fit.\n\n"
|
| 1032 |
+
"UPDATED TWEET: Your concise tweet content with #hashtags #AI #Tech"
|
| 1033 |
+
)
|
| 1034 |
+
|
| 1035 |
+
context = (
|
| 1036 |
+
f"Original Article Summary: {req.original_summary}\n"
|
| 1037 |
+
f"Current Tweet: {req.current_tweet}\n"
|
| 1038 |
+
f"User Request: {req.user_message}"
|
| 1039 |
+
)
|
| 1040 |
+
|
| 1041 |
+
messages = (
|
| 1042 |
+
[{"role": "system", "content": system}]
|
| 1043 |
+
+ [{"role": t.role, "content": t.content} for t in tweet_memory.history[-6:]]
|
| 1044 |
+
+ [{"role": "user", "content": context}]
|
| 1045 |
+
)
|
| 1046 |
+
|
| 1047 |
+
ai_response = _chat(messages, temperature=0.7)
|
| 1048 |
+
|
| 1049 |
+
# Extract the new tweet and AI message using the structured format
|
| 1050 |
+
new_tweet = req.current_tweet # Fallback to current tweet
|
| 1051 |
+
ai_message = ai_response
|
| 1052 |
+
|
| 1053 |
+
# Look for "UPDATED TWEET:" pattern
|
| 1054 |
+
if "UPDATED TWEET:" in ai_response:
|
| 1055 |
+
parts = ai_response.split("UPDATED TWEET:", 1)
|
| 1056 |
+
if len(parts) == 2:
|
| 1057 |
+
ai_message = parts[0].strip()
|
| 1058 |
+
new_tweet = parts[1].strip()
|
| 1059 |
+
|
| 1060 |
+
# Clean up the new tweet (remove any quotes or extra formatting)
|
| 1061 |
+
new_tweet = new_tweet.strip('"').strip("'").strip()
|
| 1062 |
+
|
| 1063 |
+
# Validate tweet length and truncate smartly
|
| 1064 |
+
if len(new_tweet) > 280:
|
| 1065 |
+
# Try to truncate at word boundaries to avoid cutting hashtags
|
| 1066 |
+
words = new_tweet.split(' ')
|
| 1067 |
+
truncated = ""
|
| 1068 |
+
for word in words:
|
| 1069 |
+
if len(truncated + " " + word) <= 280:
|
| 1070 |
+
if truncated:
|
| 1071 |
+
truncated += " " + word
|
| 1072 |
+
else:
|
| 1073 |
+
truncated = word
|
| 1074 |
+
else:
|
| 1075 |
+
break
|
| 1076 |
+
new_tweet = truncated if truncated else new_tweet[:280]
|
| 1077 |
+
|
| 1078 |
+
if not ai_message:
|
| 1079 |
+
ai_message = "I've updated your tweet based on your request!"
|
| 1080 |
+
else:
|
| 1081 |
+
# Fallback: if the structured format wasn't followed, try to extract tweet-like content
|
| 1082 |
+
lines = ai_response.split('\n')
|
| 1083 |
+
for line in lines:
|
| 1084 |
+
line = line.strip()
|
| 1085 |
+
if len(line) > 20 and len(line) <= 280 and ('#' in line or '@' in line or any(emoji in line for emoji in ['🔥', '🚀', '💡', '🤖', '⚡'])):
|
| 1086 |
+
new_tweet = line
|
| 1087 |
+
ai_message = ai_response.replace(new_tweet, "").strip()
|
| 1088 |
+
if not ai_message:
|
| 1089 |
+
ai_message = "I've updated your tweet based on your request!"
|
| 1090 |
+
break
|
| 1091 |
+
|
| 1092 |
+
# Store conversation
|
| 1093 |
+
turn_user = ConversationTurn(role="user", content=req.user_message)
|
| 1094 |
+
turn_assistant = ConversationTurn(role="assistant", content=ai_response)
|
| 1095 |
+
tweet_memory.history.append(turn_user)
|
| 1096 |
+
tweet_memory.history.append(turn_assistant)
|
| 1097 |
+
|
| 1098 |
+
return TweetEditResponse(
|
| 1099 |
+
new_tweet=new_tweet,
|
| 1100 |
+
ai_response=ai_message,
|
| 1101 |
+
conversation_history=tweet_memory.history[-10:]
|
| 1102 |
+
)
|
| 1103 |
+
|
| 1104 |
+
|
| 1105 |
+
# Provide a synchronous alternative endpoint with explicit model
|
| 1106 |
+
class DownloadRequest(BaseModel):
|
| 1107 |
+
session_id: Optional[str] = None
|
| 1108 |
+
html: Optional[str] = None
|
| 1109 |
+
|
| 1110 |
+
|
| 1111 |
+
@app.post("/api/download_html")
|
| 1112 |
+
def download_html(req: DownloadRequest):
|
| 1113 |
+
html = req.html
|
| 1114 |
+
if not html and req.session_id:
|
| 1115 |
+
mem = get_memory(req.session_id)
|
| 1116 |
+
html = mem.last_newsletter_html
|
| 1117 |
+
if not html:
|
| 1118 |
+
raise HTTPException(status_code=400, detail="No HTML provided or found for session")
|
| 1119 |
+
buffer = io.BytesIO(html.encode("utf-8"))
|
| 1120 |
+
headers = {
|
| 1121 |
+
"Content-Disposition": "attachment; filename=ai_weekly.html"
|
| 1122 |
+
}
|
| 1123 |
+
return Response(content=buffer.getvalue(), headers=headers, media_type="text/html")
|
| 1124 |
+
|
| 1125 |
+
|
| 1126 |
+
# Catch-all route for SPA routing - MUST be at the very end
|
| 1127 |
+
@app.get("/{path:path}")
|
| 1128 |
+
def catch_all(path: str):
|
| 1129 |
+
"""Catch-all route to serve React app for client-side routing"""
|
| 1130 |
+
# Don't intercept API routes
|
| 1131 |
+
if path.startswith("api/"):
|
| 1132 |
+
raise HTTPException(status_code=404, detail="API endpoint not found")
|
| 1133 |
+
|
| 1134 |
+
static_dir = os.path.join(os.path.dirname(__file__), "..", "frontend", "dist")
|
| 1135 |
+
index_file = os.path.join(static_dir, "index.html")
|
| 1136 |
+
|
| 1137 |
+
# Serve static files if they exist
|
| 1138 |
+
file_path = os.path.join(static_dir, path)
|
| 1139 |
+
if os.path.isfile(file_path):
|
| 1140 |
+
return FileResponse(file_path)
|
| 1141 |
+
|
| 1142 |
+
# Otherwise serve index.html for SPA routing
|
| 1143 |
+
if os.path.exists(index_file):
|
| 1144 |
+
return FileResponse(index_file)
|
| 1145 |
+
|
| 1146 |
+
# Fallback if no frontend built
|
| 1147 |
+
return {"status": "error", "message": "Frontend not available"}
|
| 1148 |
+
|
| 1149 |
+
|
| 1150 |
+
# Lambda handler for AWS
|
| 1151 |
+
def handler(event, context):
|
| 1152 |
+
"""AWS Lambda handler for FastAPI - Version 2.0"""
|
| 1153 |
+
print(f"[DEBUG v2.0] Lambda handler called with event: {event.get('httpMethod', 'unknown')}")
|
| 1154 |
+
print(f"[DEBUG v2.0] Event keys: {list(event.keys())}")
|
| 1155 |
+
try:
|
| 1156 |
+
from mangum import Mangum
|
| 1157 |
+
print("[DEBUG v2.0] Mangum imported successfully")
|
| 1158 |
+
asgi_handler = Mangum(app)
|
| 1159 |
+
print("[DEBUG v2.0] Mangum handler created")
|
| 1160 |
+
result = asgi_handler(event, context)
|
| 1161 |
+
print(f"[DEBUG v2.0] Handler result type: {type(result)}")
|
| 1162 |
+
return result
|
| 1163 |
+
except Exception as e:
|
| 1164 |
+
print(f"[ERROR v2.0] Handler failed: {str(e)}")
|
| 1165 |
+
import traceback
|
| 1166 |
+
traceback.print_exc()
|
| 1167 |
+
raise
|
| 1168 |
+
|
| 1169 |
+
|
| 1170 |
+
# Export for Vercel - app is automatically detected
|
| 1171 |
+
if __name__ == "__main__":
|
| 1172 |
+
import uvicorn
|
| 1173 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
frontend/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
logs
|
| 3 |
+
*.log
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
| 7 |
+
pnpm-debug.log*
|
| 8 |
+
lerna-debug.log*
|
| 9 |
+
|
| 10 |
+
node_modules
|
| 11 |
+
dist
|
| 12 |
+
dist-ssr
|
| 13 |
+
*.local
|
| 14 |
+
|
| 15 |
+
# Editor directories and files
|
| 16 |
+
.vscode/*
|
| 17 |
+
!.vscode/extensions.json
|
| 18 |
+
.idea
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.suo
|
| 21 |
+
*.ntvs*
|
| 22 |
+
*.njsproj
|
| 23 |
+
*.sln
|
| 24 |
+
*.sw?
|
frontend/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
20.11.1
|
frontend/.pnpmfile.cjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// pnpm configuration for the AI Newsletter frontend
|
| 2 |
+
module.exports = {
|
| 3 |
+
hooks: {
|
| 4 |
+
readPackage(pkg) {
|
| 5 |
+
// Ensure React types compatibility
|
| 6 |
+
if (pkg.name === '@types/react') {
|
| 7 |
+
pkg.peerDependencies = {
|
| 8 |
+
...pkg.peerDependencies,
|
| 9 |
+
'react': '*'
|
| 10 |
+
}
|
| 11 |
+
}
|
| 12 |
+
return pkg
|
| 13 |
+
}
|
| 14 |
+
}
|
| 15 |
+
}
|
frontend/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# React + TypeScript + Vite
|
| 2 |
+
|
| 3 |
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
| 4 |
+
|
| 5 |
+
Currently, two official plugins are available:
|
| 6 |
+
|
| 7 |
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
| 8 |
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
| 9 |
+
|
| 10 |
+
## Expanding the ESLint configuration
|
| 11 |
+
|
| 12 |
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
| 13 |
+
|
| 14 |
+
```js
|
| 15 |
+
export default tseslint.config([
|
| 16 |
+
globalIgnores(['dist']),
|
| 17 |
+
{
|
| 18 |
+
files: ['**/*.{ts,tsx}'],
|
| 19 |
+
extends: [
|
| 20 |
+
// Other configs...
|
| 21 |
+
|
| 22 |
+
// Remove tseslint.configs.recommended and replace with this
|
| 23 |
+
...tseslint.configs.recommendedTypeChecked,
|
| 24 |
+
// Alternatively, use this for stricter rules
|
| 25 |
+
...tseslint.configs.strictTypeChecked,
|
| 26 |
+
// Optionally, add this for stylistic rules
|
| 27 |
+
...tseslint.configs.stylisticTypeChecked,
|
| 28 |
+
|
| 29 |
+
// Other configs...
|
| 30 |
+
],
|
| 31 |
+
languageOptions: {
|
| 32 |
+
parserOptions: {
|
| 33 |
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
| 34 |
+
tsconfigRootDir: import.meta.dirname,
|
| 35 |
+
},
|
| 36 |
+
// other options...
|
| 37 |
+
},
|
| 38 |
+
},
|
| 39 |
+
])
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
| 43 |
+
|
| 44 |
+
```js
|
| 45 |
+
// eslint.config.js
|
| 46 |
+
import reactX from 'eslint-plugin-react-x'
|
| 47 |
+
import reactDom from 'eslint-plugin-react-dom'
|
| 48 |
+
|
| 49 |
+
export default tseslint.config([
|
| 50 |
+
globalIgnores(['dist']),
|
| 51 |
+
{
|
| 52 |
+
files: ['**/*.{ts,tsx}'],
|
| 53 |
+
extends: [
|
| 54 |
+
// Other configs...
|
| 55 |
+
// Enable lint rules for React
|
| 56 |
+
reactX.configs['recommended-typescript'],
|
| 57 |
+
// Enable lint rules for React DOM
|
| 58 |
+
reactDom.configs.recommended,
|
| 59 |
+
],
|
| 60 |
+
languageOptions: {
|
| 61 |
+
parserOptions: {
|
| 62 |
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
| 63 |
+
tsconfigRootDir: import.meta.dirname,
|
| 64 |
+
},
|
| 65 |
+
// other options...
|
| 66 |
+
},
|
| 67 |
+
},
|
| 68 |
+
])
|
| 69 |
+
```
|
frontend/eslint.config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import js from '@eslint/js'
|
| 2 |
+
import globals from 'globals'
|
| 3 |
+
import reactHooks from 'eslint-plugin-react-hooks'
|
| 4 |
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
| 5 |
+
import tseslint from 'typescript-eslint'
|
| 6 |
+
import { globalIgnores } from 'eslint/config'
|
| 7 |
+
|
| 8 |
+
export default tseslint.config([
|
| 9 |
+
globalIgnores(['dist']),
|
| 10 |
+
{
|
| 11 |
+
files: ['**/*.{ts,tsx}'],
|
| 12 |
+
extends: [
|
| 13 |
+
js.configs.recommended,
|
| 14 |
+
tseslint.configs.recommended,
|
| 15 |
+
reactHooks.configs['recommended-latest'],
|
| 16 |
+
reactRefresh.configs.vite,
|
| 17 |
+
],
|
| 18 |
+
languageOptions: {
|
| 19 |
+
ecmaVersion: 2020,
|
| 20 |
+
globals: globals.browser,
|
| 21 |
+
},
|
| 22 |
+
},
|
| 23 |
+
])
|
frontend/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>Vite + React + TS</title>
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div id="root"></div>
|
| 11 |
+
<script type="module" src="/src/main.tsx"></script>
|
| 12 |
+
</body>
|
| 13 |
+
</html>
|
frontend/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite --port 3002",
|
| 8 |
+
"build": "tsc -b && vite build",
|
| 9 |
+
"lint": "eslint .",
|
| 10 |
+
"preview": "vite preview",
|
| 11 |
+
"clean": "rm -rf dist node_modules/.vite",
|
| 12 |
+
"type-check": "tsc --noEmit"
|
| 13 |
+
},
|
| 14 |
+
"dependencies": {
|
| 15 |
+
"react": "^19.1.1",
|
| 16 |
+
"react-dom": "^19.1.1"
|
| 17 |
+
},
|
| 18 |
+
"devDependencies": {
|
| 19 |
+
"@eslint/js": "^9.32.0",
|
| 20 |
+
"@types/react": "^19.1.9",
|
| 21 |
+
"@types/react-dom": "^19.1.7",
|
| 22 |
+
"@vitejs/plugin-react": "^4.7.0",
|
| 23 |
+
"autoprefixer": "^10.4.21",
|
| 24 |
+
"classnames": "^2.5.1",
|
| 25 |
+
"eslint": "^9.32.0",
|
| 26 |
+
"eslint-plugin-react-hooks": "^5.2.0",
|
| 27 |
+
"eslint-plugin-react-refresh": "^0.4.20",
|
| 28 |
+
"globals": "^16.3.0",
|
| 29 |
+
"postcss": "^8.5.6",
|
| 30 |
+
"tailwindcss": "^3.4.17",
|
| 31 |
+
"typescript": "~5.8.3",
|
| 32 |
+
"typescript-eslint": "^8.39.0",
|
| 33 |
+
"vite": "^7.1.0"
|
| 34 |
+
},
|
| 35 |
+
"packageManager": "pnpm@10.12.3",
|
| 36 |
+
"engines": {
|
| 37 |
+
"node": ">=18.0.0",
|
| 38 |
+
"pnpm": ">=8.0.0"
|
| 39 |
+
}
|
| 40 |
+
}
|
frontend/pnpm-lock.yaml
ADDED
|
@@ -0,0 +1,2780 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
lockfileVersion: '9.0'
|
| 2 |
+
|
| 3 |
+
settings:
|
| 4 |
+
autoInstallPeers: true
|
| 5 |
+
excludeLinksFromLockfile: false
|
| 6 |
+
|
| 7 |
+
pnpmfileChecksum: sha256-gvDTDqUZJL4m3Sr1Vuxqtbv0pSJ33/voxpVpvmgK1ic=
|
| 8 |
+
|
| 9 |
+
importers:
|
| 10 |
+
|
| 11 |
+
.:
|
| 12 |
+
dependencies:
|
| 13 |
+
react:
|
| 14 |
+
specifier: ^19.1.1
|
| 15 |
+
version: 19.1.1
|
| 16 |
+
react-dom:
|
| 17 |
+
specifier: ^19.1.1
|
| 18 |
+
version: 19.1.1(react@19.1.1)
|
| 19 |
+
devDependencies:
|
| 20 |
+
'@eslint/js':
|
| 21 |
+
specifier: ^9.32.0
|
| 22 |
+
version: 9.33.0
|
| 23 |
+
'@types/react':
|
| 24 |
+
specifier: ^19.1.9
|
| 25 |
+
version: 19.1.9(react@19.1.1)
|
| 26 |
+
'@types/react-dom':
|
| 27 |
+
specifier: ^19.1.7
|
| 28 |
+
version: 19.1.7(@types/react@19.1.9(react@19.1.1))
|
| 29 |
+
'@vitejs/plugin-react':
|
| 30 |
+
specifier: ^4.7.0
|
| 31 |
+
version: 4.7.0(vite@7.1.1(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1))
|
| 32 |
+
autoprefixer:
|
| 33 |
+
specifier: ^10.4.21
|
| 34 |
+
version: 10.4.21(postcss@8.5.6)
|
| 35 |
+
classnames:
|
| 36 |
+
specifier: ^2.5.1
|
| 37 |
+
version: 2.5.1
|
| 38 |
+
eslint:
|
| 39 |
+
specifier: ^9.32.0
|
| 40 |
+
version: 9.33.0(jiti@2.5.1)
|
| 41 |
+
eslint-plugin-react-hooks:
|
| 42 |
+
specifier: ^5.2.0
|
| 43 |
+
version: 5.2.0(eslint@9.33.0(jiti@2.5.1))
|
| 44 |
+
eslint-plugin-react-refresh:
|
| 45 |
+
specifier: ^0.4.20
|
| 46 |
+
version: 0.4.20(eslint@9.33.0(jiti@2.5.1))
|
| 47 |
+
globals:
|
| 48 |
+
specifier: ^16.3.0
|
| 49 |
+
version: 16.3.0
|
| 50 |
+
postcss:
|
| 51 |
+
specifier: ^8.5.6
|
| 52 |
+
version: 8.5.6
|
| 53 |
+
tailwindcss:
|
| 54 |
+
specifier: ^3.4.17
|
| 55 |
+
version: 3.4.17
|
| 56 |
+
typescript:
|
| 57 |
+
specifier: ~5.8.3
|
| 58 |
+
version: 5.8.3
|
| 59 |
+
typescript-eslint:
|
| 60 |
+
specifier: ^8.39.0
|
| 61 |
+
version: 8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)
|
| 62 |
+
vite:
|
| 63 |
+
specifier: ^7.1.0
|
| 64 |
+
version: 7.1.1(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)
|
| 65 |
+
|
| 66 |
+
packages:
|
| 67 |
+
|
| 68 |
+
'@alloc/quick-lru@5.2.0':
|
| 69 |
+
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
| 70 |
+
engines: {node: '>=10'}
|
| 71 |
+
|
| 72 |
+
'@ampproject/remapping@2.3.0':
|
| 73 |
+
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
| 74 |
+
engines: {node: '>=6.0.0'}
|
| 75 |
+
|
| 76 |
+
'@babel/code-frame@7.27.1':
|
| 77 |
+
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
| 78 |
+
engines: {node: '>=6.9.0'}
|
| 79 |
+
|
| 80 |
+
'@babel/compat-data@7.28.0':
|
| 81 |
+
resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==}
|
| 82 |
+
engines: {node: '>=6.9.0'}
|
| 83 |
+
|
| 84 |
+
'@babel/core@7.28.0':
|
| 85 |
+
resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==}
|
| 86 |
+
engines: {node: '>=6.9.0'}
|
| 87 |
+
|
| 88 |
+
'@babel/generator@7.28.0':
|
| 89 |
+
resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==}
|
| 90 |
+
engines: {node: '>=6.9.0'}
|
| 91 |
+
|
| 92 |
+
'@babel/helper-compilation-targets@7.27.2':
|
| 93 |
+
resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
|
| 94 |
+
engines: {node: '>=6.9.0'}
|
| 95 |
+
|
| 96 |
+
'@babel/helper-globals@7.28.0':
|
| 97 |
+
resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
|
| 98 |
+
engines: {node: '>=6.9.0'}
|
| 99 |
+
|
| 100 |
+
'@babel/helper-module-imports@7.27.1':
|
| 101 |
+
resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
|
| 102 |
+
engines: {node: '>=6.9.0'}
|
| 103 |
+
|
| 104 |
+
'@babel/helper-module-transforms@7.27.3':
|
| 105 |
+
resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==}
|
| 106 |
+
engines: {node: '>=6.9.0'}
|
| 107 |
+
peerDependencies:
|
| 108 |
+
'@babel/core': ^7.0.0
|
| 109 |
+
|
| 110 |
+
'@babel/helper-plugin-utils@7.27.1':
|
| 111 |
+
resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
|
| 112 |
+
engines: {node: '>=6.9.0'}
|
| 113 |
+
|
| 114 |
+
'@babel/helper-string-parser@7.27.1':
|
| 115 |
+
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
|
| 116 |
+
engines: {node: '>=6.9.0'}
|
| 117 |
+
|
| 118 |
+
'@babel/helper-validator-identifier@7.27.1':
|
| 119 |
+
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
|
| 120 |
+
engines: {node: '>=6.9.0'}
|
| 121 |
+
|
| 122 |
+
'@babel/helper-validator-option@7.27.1':
|
| 123 |
+
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
|
| 124 |
+
engines: {node: '>=6.9.0'}
|
| 125 |
+
|
| 126 |
+
'@babel/helpers@7.28.2':
|
| 127 |
+
resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==}
|
| 128 |
+
engines: {node: '>=6.9.0'}
|
| 129 |
+
|
| 130 |
+
'@babel/parser@7.28.0':
|
| 131 |
+
resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==}
|
| 132 |
+
engines: {node: '>=6.0.0'}
|
| 133 |
+
hasBin: true
|
| 134 |
+
|
| 135 |
+
'@babel/plugin-transform-react-jsx-self@7.27.1':
|
| 136 |
+
resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
|
| 137 |
+
engines: {node: '>=6.9.0'}
|
| 138 |
+
peerDependencies:
|
| 139 |
+
'@babel/core': ^7.0.0-0
|
| 140 |
+
|
| 141 |
+
'@babel/plugin-transform-react-jsx-source@7.27.1':
|
| 142 |
+
resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
|
| 143 |
+
engines: {node: '>=6.9.0'}
|
| 144 |
+
peerDependencies:
|
| 145 |
+
'@babel/core': ^7.0.0-0
|
| 146 |
+
|
| 147 |
+
'@babel/template@7.27.2':
|
| 148 |
+
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
| 149 |
+
engines: {node: '>=6.9.0'}
|
| 150 |
+
|
| 151 |
+
'@babel/traverse@7.28.0':
|
| 152 |
+
resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==}
|
| 153 |
+
engines: {node: '>=6.9.0'}
|
| 154 |
+
|
| 155 |
+
'@babel/types@7.28.2':
|
| 156 |
+
resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
|
| 157 |
+
engines: {node: '>=6.9.0'}
|
| 158 |
+
|
| 159 |
+
'@esbuild/aix-ppc64@0.25.8':
|
| 160 |
+
resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==}
|
| 161 |
+
engines: {node: '>=18'}
|
| 162 |
+
cpu: [ppc64]
|
| 163 |
+
os: [aix]
|
| 164 |
+
|
| 165 |
+
'@esbuild/android-arm64@0.25.8':
|
| 166 |
+
resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==}
|
| 167 |
+
engines: {node: '>=18'}
|
| 168 |
+
cpu: [arm64]
|
| 169 |
+
os: [android]
|
| 170 |
+
|
| 171 |
+
'@esbuild/android-arm@0.25.8':
|
| 172 |
+
resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==}
|
| 173 |
+
engines: {node: '>=18'}
|
| 174 |
+
cpu: [arm]
|
| 175 |
+
os: [android]
|
| 176 |
+
|
| 177 |
+
'@esbuild/android-x64@0.25.8':
|
| 178 |
+
resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==}
|
| 179 |
+
engines: {node: '>=18'}
|
| 180 |
+
cpu: [x64]
|
| 181 |
+
os: [android]
|
| 182 |
+
|
| 183 |
+
'@esbuild/darwin-arm64@0.25.8':
|
| 184 |
+
resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==}
|
| 185 |
+
engines: {node: '>=18'}
|
| 186 |
+
cpu: [arm64]
|
| 187 |
+
os: [darwin]
|
| 188 |
+
|
| 189 |
+
'@esbuild/darwin-x64@0.25.8':
|
| 190 |
+
resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==}
|
| 191 |
+
engines: {node: '>=18'}
|
| 192 |
+
cpu: [x64]
|
| 193 |
+
os: [darwin]
|
| 194 |
+
|
| 195 |
+
'@esbuild/freebsd-arm64@0.25.8':
|
| 196 |
+
resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==}
|
| 197 |
+
engines: {node: '>=18'}
|
| 198 |
+
cpu: [arm64]
|
| 199 |
+
os: [freebsd]
|
| 200 |
+
|
| 201 |
+
'@esbuild/freebsd-x64@0.25.8':
|
| 202 |
+
resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==}
|
| 203 |
+
engines: {node: '>=18'}
|
| 204 |
+
cpu: [x64]
|
| 205 |
+
os: [freebsd]
|
| 206 |
+
|
| 207 |
+
'@esbuild/linux-arm64@0.25.8':
|
| 208 |
+
resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==}
|
| 209 |
+
engines: {node: '>=18'}
|
| 210 |
+
cpu: [arm64]
|
| 211 |
+
os: [linux]
|
| 212 |
+
|
| 213 |
+
'@esbuild/linux-arm@0.25.8':
|
| 214 |
+
resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==}
|
| 215 |
+
engines: {node: '>=18'}
|
| 216 |
+
cpu: [arm]
|
| 217 |
+
os: [linux]
|
| 218 |
+
|
| 219 |
+
'@esbuild/linux-ia32@0.25.8':
|
| 220 |
+
resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==}
|
| 221 |
+
engines: {node: '>=18'}
|
| 222 |
+
cpu: [ia32]
|
| 223 |
+
os: [linux]
|
| 224 |
+
|
| 225 |
+
'@esbuild/linux-loong64@0.25.8':
|
| 226 |
+
resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==}
|
| 227 |
+
engines: {node: '>=18'}
|
| 228 |
+
cpu: [loong64]
|
| 229 |
+
os: [linux]
|
| 230 |
+
|
| 231 |
+
'@esbuild/linux-mips64el@0.25.8':
|
| 232 |
+
resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==}
|
| 233 |
+
engines: {node: '>=18'}
|
| 234 |
+
cpu: [mips64el]
|
| 235 |
+
os: [linux]
|
| 236 |
+
|
| 237 |
+
'@esbuild/linux-ppc64@0.25.8':
|
| 238 |
+
resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==}
|
| 239 |
+
engines: {node: '>=18'}
|
| 240 |
+
cpu: [ppc64]
|
| 241 |
+
os: [linux]
|
| 242 |
+
|
| 243 |
+
'@esbuild/linux-riscv64@0.25.8':
|
| 244 |
+
resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==}
|
| 245 |
+
engines: {node: '>=18'}
|
| 246 |
+
cpu: [riscv64]
|
| 247 |
+
os: [linux]
|
| 248 |
+
|
| 249 |
+
'@esbuild/linux-s390x@0.25.8':
|
| 250 |
+
resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==}
|
| 251 |
+
engines: {node: '>=18'}
|
| 252 |
+
cpu: [s390x]
|
| 253 |
+
os: [linux]
|
| 254 |
+
|
| 255 |
+
'@esbuild/linux-x64@0.25.8':
|
| 256 |
+
resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==}
|
| 257 |
+
engines: {node: '>=18'}
|
| 258 |
+
cpu: [x64]
|
| 259 |
+
os: [linux]
|
| 260 |
+
|
| 261 |
+
'@esbuild/netbsd-arm64@0.25.8':
|
| 262 |
+
resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==}
|
| 263 |
+
engines: {node: '>=18'}
|
| 264 |
+
cpu: [arm64]
|
| 265 |
+
os: [netbsd]
|
| 266 |
+
|
| 267 |
+
'@esbuild/netbsd-x64@0.25.8':
|
| 268 |
+
resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==}
|
| 269 |
+
engines: {node: '>=18'}
|
| 270 |
+
cpu: [x64]
|
| 271 |
+
os: [netbsd]
|
| 272 |
+
|
| 273 |
+
'@esbuild/openbsd-arm64@0.25.8':
|
| 274 |
+
resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==}
|
| 275 |
+
engines: {node: '>=18'}
|
| 276 |
+
cpu: [arm64]
|
| 277 |
+
os: [openbsd]
|
| 278 |
+
|
| 279 |
+
'@esbuild/openbsd-x64@0.25.8':
|
| 280 |
+
resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==}
|
| 281 |
+
engines: {node: '>=18'}
|
| 282 |
+
cpu: [x64]
|
| 283 |
+
os: [openbsd]
|
| 284 |
+
|
| 285 |
+
'@esbuild/openharmony-arm64@0.25.8':
|
| 286 |
+
resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==}
|
| 287 |
+
engines: {node: '>=18'}
|
| 288 |
+
cpu: [arm64]
|
| 289 |
+
os: [openharmony]
|
| 290 |
+
|
| 291 |
+
'@esbuild/sunos-x64@0.25.8':
|
| 292 |
+
resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==}
|
| 293 |
+
engines: {node: '>=18'}
|
| 294 |
+
cpu: [x64]
|
| 295 |
+
os: [sunos]
|
| 296 |
+
|
| 297 |
+
'@esbuild/win32-arm64@0.25.8':
|
| 298 |
+
resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==}
|
| 299 |
+
engines: {node: '>=18'}
|
| 300 |
+
cpu: [arm64]
|
| 301 |
+
os: [win32]
|
| 302 |
+
|
| 303 |
+
'@esbuild/win32-ia32@0.25.8':
|
| 304 |
+
resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==}
|
| 305 |
+
engines: {node: '>=18'}
|
| 306 |
+
cpu: [ia32]
|
| 307 |
+
os: [win32]
|
| 308 |
+
|
| 309 |
+
'@esbuild/win32-x64@0.25.8':
|
| 310 |
+
resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==}
|
| 311 |
+
engines: {node: '>=18'}
|
| 312 |
+
cpu: [x64]
|
| 313 |
+
os: [win32]
|
| 314 |
+
|
| 315 |
+
'@eslint-community/eslint-utils@4.7.0':
|
| 316 |
+
resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
|
| 317 |
+
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
| 318 |
+
peerDependencies:
|
| 319 |
+
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
| 320 |
+
|
| 321 |
+
'@eslint-community/regexpp@4.12.1':
|
| 322 |
+
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
|
| 323 |
+
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
| 324 |
+
|
| 325 |
+
'@eslint/config-array@0.21.0':
|
| 326 |
+
resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==}
|
| 327 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 328 |
+
|
| 329 |
+
'@eslint/config-helpers@0.3.1':
|
| 330 |
+
resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==}
|
| 331 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 332 |
+
|
| 333 |
+
'@eslint/core@0.15.2':
|
| 334 |
+
resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==}
|
| 335 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 336 |
+
|
| 337 |
+
'@eslint/eslintrc@3.3.1':
|
| 338 |
+
resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
|
| 339 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 340 |
+
|
| 341 |
+
'@eslint/js@9.33.0':
|
| 342 |
+
resolution: {integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==}
|
| 343 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 344 |
+
|
| 345 |
+
'@eslint/object-schema@2.1.6':
|
| 346 |
+
resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
|
| 347 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 348 |
+
|
| 349 |
+
'@eslint/plugin-kit@0.3.5':
|
| 350 |
+
resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
|
| 351 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 352 |
+
|
| 353 |
+
'@humanfs/core@0.19.1':
|
| 354 |
+
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
| 355 |
+
engines: {node: '>=18.18.0'}
|
| 356 |
+
|
| 357 |
+
'@humanfs/node@0.16.6':
|
| 358 |
+
resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
|
| 359 |
+
engines: {node: '>=18.18.0'}
|
| 360 |
+
|
| 361 |
+
'@humanwhocodes/module-importer@1.0.1':
|
| 362 |
+
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
|
| 363 |
+
engines: {node: '>=12.22'}
|
| 364 |
+
|
| 365 |
+
'@humanwhocodes/retry@0.3.1':
|
| 366 |
+
resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
|
| 367 |
+
engines: {node: '>=18.18'}
|
| 368 |
+
|
| 369 |
+
'@humanwhocodes/retry@0.4.3':
|
| 370 |
+
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
|
| 371 |
+
engines: {node: '>=18.18'}
|
| 372 |
+
|
| 373 |
+
'@isaacs/cliui@8.0.2':
|
| 374 |
+
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
| 375 |
+
engines: {node: '>=12'}
|
| 376 |
+
|
| 377 |
+
'@jridgewell/gen-mapping@0.3.12':
|
| 378 |
+
resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==}
|
| 379 |
+
|
| 380 |
+
'@jridgewell/resolve-uri@3.1.2':
|
| 381 |
+
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
| 382 |
+
engines: {node: '>=6.0.0'}
|
| 383 |
+
|
| 384 |
+
'@jridgewell/sourcemap-codec@1.5.4':
|
| 385 |
+
resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==}
|
| 386 |
+
|
| 387 |
+
'@jridgewell/trace-mapping@0.3.29':
|
| 388 |
+
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
|
| 389 |
+
|
| 390 |
+
'@nodelib/fs.scandir@2.1.5':
|
| 391 |
+
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
| 392 |
+
engines: {node: '>= 8'}
|
| 393 |
+
|
| 394 |
+
'@nodelib/fs.stat@2.0.5':
|
| 395 |
+
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
|
| 396 |
+
engines: {node: '>= 8'}
|
| 397 |
+
|
| 398 |
+
'@nodelib/fs.walk@1.2.8':
|
| 399 |
+
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
| 400 |
+
engines: {node: '>= 8'}
|
| 401 |
+
|
| 402 |
+
'@pkgjs/parseargs@0.11.0':
|
| 403 |
+
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
| 404 |
+
engines: {node: '>=14'}
|
| 405 |
+
|
| 406 |
+
'@rolldown/pluginutils@1.0.0-beta.27':
|
| 407 |
+
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
|
| 408 |
+
|
| 409 |
+
'@rollup/rollup-android-arm-eabi@4.46.2':
|
| 410 |
+
resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==}
|
| 411 |
+
cpu: [arm]
|
| 412 |
+
os: [android]
|
| 413 |
+
|
| 414 |
+
'@rollup/rollup-android-arm64@4.46.2':
|
| 415 |
+
resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==}
|
| 416 |
+
cpu: [arm64]
|
| 417 |
+
os: [android]
|
| 418 |
+
|
| 419 |
+
'@rollup/rollup-darwin-arm64@4.46.2':
|
| 420 |
+
resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==}
|
| 421 |
+
cpu: [arm64]
|
| 422 |
+
os: [darwin]
|
| 423 |
+
|
| 424 |
+
'@rollup/rollup-darwin-x64@4.46.2':
|
| 425 |
+
resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==}
|
| 426 |
+
cpu: [x64]
|
| 427 |
+
os: [darwin]
|
| 428 |
+
|
| 429 |
+
'@rollup/rollup-freebsd-arm64@4.46.2':
|
| 430 |
+
resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==}
|
| 431 |
+
cpu: [arm64]
|
| 432 |
+
os: [freebsd]
|
| 433 |
+
|
| 434 |
+
'@rollup/rollup-freebsd-x64@4.46.2':
|
| 435 |
+
resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==}
|
| 436 |
+
cpu: [x64]
|
| 437 |
+
os: [freebsd]
|
| 438 |
+
|
| 439 |
+
'@rollup/rollup-linux-arm-gnueabihf@4.46.2':
|
| 440 |
+
resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==}
|
| 441 |
+
cpu: [arm]
|
| 442 |
+
os: [linux]
|
| 443 |
+
|
| 444 |
+
'@rollup/rollup-linux-arm-musleabihf@4.46.2':
|
| 445 |
+
resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==}
|
| 446 |
+
cpu: [arm]
|
| 447 |
+
os: [linux]
|
| 448 |
+
|
| 449 |
+
'@rollup/rollup-linux-arm64-gnu@4.46.2':
|
| 450 |
+
resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==}
|
| 451 |
+
cpu: [arm64]
|
| 452 |
+
os: [linux]
|
| 453 |
+
|
| 454 |
+
'@rollup/rollup-linux-arm64-musl@4.46.2':
|
| 455 |
+
resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==}
|
| 456 |
+
cpu: [arm64]
|
| 457 |
+
os: [linux]
|
| 458 |
+
|
| 459 |
+
'@rollup/rollup-linux-loongarch64-gnu@4.46.2':
|
| 460 |
+
resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==}
|
| 461 |
+
cpu: [loong64]
|
| 462 |
+
os: [linux]
|
| 463 |
+
|
| 464 |
+
'@rollup/rollup-linux-ppc64-gnu@4.46.2':
|
| 465 |
+
resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==}
|
| 466 |
+
cpu: [ppc64]
|
| 467 |
+
os: [linux]
|
| 468 |
+
|
| 469 |
+
'@rollup/rollup-linux-riscv64-gnu@4.46.2':
|
| 470 |
+
resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==}
|
| 471 |
+
cpu: [riscv64]
|
| 472 |
+
os: [linux]
|
| 473 |
+
|
| 474 |
+
'@rollup/rollup-linux-riscv64-musl@4.46.2':
|
| 475 |
+
resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==}
|
| 476 |
+
cpu: [riscv64]
|
| 477 |
+
os: [linux]
|
| 478 |
+
|
| 479 |
+
'@rollup/rollup-linux-s390x-gnu@4.46.2':
|
| 480 |
+
resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==}
|
| 481 |
+
cpu: [s390x]
|
| 482 |
+
os: [linux]
|
| 483 |
+
|
| 484 |
+
'@rollup/rollup-linux-x64-gnu@4.46.2':
|
| 485 |
+
resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==}
|
| 486 |
+
cpu: [x64]
|
| 487 |
+
os: [linux]
|
| 488 |
+
|
| 489 |
+
'@rollup/rollup-linux-x64-musl@4.46.2':
|
| 490 |
+
resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==}
|
| 491 |
+
cpu: [x64]
|
| 492 |
+
os: [linux]
|
| 493 |
+
|
| 494 |
+
'@rollup/rollup-win32-arm64-msvc@4.46.2':
|
| 495 |
+
resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==}
|
| 496 |
+
cpu: [arm64]
|
| 497 |
+
os: [win32]
|
| 498 |
+
|
| 499 |
+
'@rollup/rollup-win32-ia32-msvc@4.46.2':
|
| 500 |
+
resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==}
|
| 501 |
+
cpu: [ia32]
|
| 502 |
+
os: [win32]
|
| 503 |
+
|
| 504 |
+
'@rollup/rollup-win32-x64-msvc@4.46.2':
|
| 505 |
+
resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==}
|
| 506 |
+
cpu: [x64]
|
| 507 |
+
os: [win32]
|
| 508 |
+
|
| 509 |
+
'@types/babel__core@7.20.5':
|
| 510 |
+
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
| 511 |
+
|
| 512 |
+
'@types/babel__generator@7.27.0':
|
| 513 |
+
resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
|
| 514 |
+
|
| 515 |
+
'@types/babel__template@7.4.4':
|
| 516 |
+
resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
|
| 517 |
+
|
| 518 |
+
'@types/babel__traverse@7.28.0':
|
| 519 |
+
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
| 520 |
+
|
| 521 |
+
'@types/estree@1.0.8':
|
| 522 |
+
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
| 523 |
+
|
| 524 |
+
'@types/json-schema@7.0.15':
|
| 525 |
+
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
| 526 |
+
|
| 527 |
+
'@types/react-dom@19.1.7':
|
| 528 |
+
resolution: {integrity: sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==}
|
| 529 |
+
peerDependencies:
|
| 530 |
+
'@types/react': ^19.0.0
|
| 531 |
+
|
| 532 |
+
'@types/react@19.1.9':
|
| 533 |
+
resolution: {integrity: sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==}
|
| 534 |
+
peerDependencies:
|
| 535 |
+
react: '*'
|
| 536 |
+
|
| 537 |
+
'@typescript-eslint/eslint-plugin@8.39.0':
|
| 538 |
+
resolution: {integrity: sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==}
|
| 539 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 540 |
+
peerDependencies:
|
| 541 |
+
'@typescript-eslint/parser': ^8.39.0
|
| 542 |
+
eslint: ^8.57.0 || ^9.0.0
|
| 543 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 544 |
+
|
| 545 |
+
'@typescript-eslint/parser@8.39.0':
|
| 546 |
+
resolution: {integrity: sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==}
|
| 547 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 548 |
+
peerDependencies:
|
| 549 |
+
eslint: ^8.57.0 || ^9.0.0
|
| 550 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 551 |
+
|
| 552 |
+
'@typescript-eslint/project-service@8.39.0':
|
| 553 |
+
resolution: {integrity: sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==}
|
| 554 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 555 |
+
peerDependencies:
|
| 556 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 557 |
+
|
| 558 |
+
'@typescript-eslint/scope-manager@8.39.0':
|
| 559 |
+
resolution: {integrity: sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==}
|
| 560 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 561 |
+
|
| 562 |
+
'@typescript-eslint/tsconfig-utils@8.39.0':
|
| 563 |
+
resolution: {integrity: sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==}
|
| 564 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 565 |
+
peerDependencies:
|
| 566 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 567 |
+
|
| 568 |
+
'@typescript-eslint/type-utils@8.39.0':
|
| 569 |
+
resolution: {integrity: sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==}
|
| 570 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 571 |
+
peerDependencies:
|
| 572 |
+
eslint: ^8.57.0 || ^9.0.0
|
| 573 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 574 |
+
|
| 575 |
+
'@typescript-eslint/types@8.39.0':
|
| 576 |
+
resolution: {integrity: sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==}
|
| 577 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 578 |
+
|
| 579 |
+
'@typescript-eslint/typescript-estree@8.39.0':
|
| 580 |
+
resolution: {integrity: sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==}
|
| 581 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 582 |
+
peerDependencies:
|
| 583 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 584 |
+
|
| 585 |
+
'@typescript-eslint/utils@8.39.0':
|
| 586 |
+
resolution: {integrity: sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==}
|
| 587 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 588 |
+
peerDependencies:
|
| 589 |
+
eslint: ^8.57.0 || ^9.0.0
|
| 590 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 591 |
+
|
| 592 |
+
'@typescript-eslint/visitor-keys@8.39.0':
|
| 593 |
+
resolution: {integrity: sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==}
|
| 594 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 595 |
+
|
| 596 |
+
'@vitejs/plugin-react@4.7.0':
|
| 597 |
+
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
|
| 598 |
+
engines: {node: ^14.18.0 || >=16.0.0}
|
| 599 |
+
peerDependencies:
|
| 600 |
+
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
| 601 |
+
|
| 602 |
+
acorn-jsx@5.3.2:
|
| 603 |
+
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
| 604 |
+
peerDependencies:
|
| 605 |
+
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
| 606 |
+
|
| 607 |
+
acorn@8.15.0:
|
| 608 |
+
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
| 609 |
+
engines: {node: '>=0.4.0'}
|
| 610 |
+
hasBin: true
|
| 611 |
+
|
| 612 |
+
ajv@6.12.6:
|
| 613 |
+
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
| 614 |
+
|
| 615 |
+
ansi-regex@5.0.1:
|
| 616 |
+
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
| 617 |
+
engines: {node: '>=8'}
|
| 618 |
+
|
| 619 |
+
ansi-regex@6.1.0:
|
| 620 |
+
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
|
| 621 |
+
engines: {node: '>=12'}
|
| 622 |
+
|
| 623 |
+
ansi-styles@4.3.0:
|
| 624 |
+
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
| 625 |
+
engines: {node: '>=8'}
|
| 626 |
+
|
| 627 |
+
ansi-styles@6.2.1:
|
| 628 |
+
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
| 629 |
+
engines: {node: '>=12'}
|
| 630 |
+
|
| 631 |
+
any-promise@1.3.0:
|
| 632 |
+
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
| 633 |
+
|
| 634 |
+
anymatch@3.1.3:
|
| 635 |
+
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
| 636 |
+
engines: {node: '>= 8'}
|
| 637 |
+
|
| 638 |
+
arg@5.0.2:
|
| 639 |
+
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
| 640 |
+
|
| 641 |
+
argparse@2.0.1:
|
| 642 |
+
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
| 643 |
+
|
| 644 |
+
autoprefixer@10.4.21:
|
| 645 |
+
resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
|
| 646 |
+
engines: {node: ^10 || ^12 || >=14}
|
| 647 |
+
hasBin: true
|
| 648 |
+
peerDependencies:
|
| 649 |
+
postcss: ^8.1.0
|
| 650 |
+
|
| 651 |
+
balanced-match@1.0.2:
|
| 652 |
+
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
| 653 |
+
|
| 654 |
+
binary-extensions@2.3.0:
|
| 655 |
+
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
| 656 |
+
engines: {node: '>=8'}
|
| 657 |
+
|
| 658 |
+
brace-expansion@1.1.12:
|
| 659 |
+
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
| 660 |
+
|
| 661 |
+
brace-expansion@2.0.2:
|
| 662 |
+
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
| 663 |
+
|
| 664 |
+
braces@3.0.3:
|
| 665 |
+
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
| 666 |
+
engines: {node: '>=8'}
|
| 667 |
+
|
| 668 |
+
browserslist@4.25.1:
|
| 669 |
+
resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==}
|
| 670 |
+
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
| 671 |
+
hasBin: true
|
| 672 |
+
|
| 673 |
+
callsites@3.1.0:
|
| 674 |
+
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
| 675 |
+
engines: {node: '>=6'}
|
| 676 |
+
|
| 677 |
+
camelcase-css@2.0.1:
|
| 678 |
+
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
| 679 |
+
engines: {node: '>= 6'}
|
| 680 |
+
|
| 681 |
+
caniuse-lite@1.0.30001733:
|
| 682 |
+
resolution: {integrity: sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==}
|
| 683 |
+
|
| 684 |
+
chalk@4.1.2:
|
| 685 |
+
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
| 686 |
+
engines: {node: '>=10'}
|
| 687 |
+
|
| 688 |
+
chokidar@3.6.0:
|
| 689 |
+
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
| 690 |
+
engines: {node: '>= 8.10.0'}
|
| 691 |
+
|
| 692 |
+
classnames@2.5.1:
|
| 693 |
+
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
| 694 |
+
|
| 695 |
+
color-convert@2.0.1:
|
| 696 |
+
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
| 697 |
+
engines: {node: '>=7.0.0'}
|
| 698 |
+
|
| 699 |
+
color-name@1.1.4:
|
| 700 |
+
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
| 701 |
+
|
| 702 |
+
commander@4.1.1:
|
| 703 |
+
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
| 704 |
+
engines: {node: '>= 6'}
|
| 705 |
+
|
| 706 |
+
concat-map@0.0.1:
|
| 707 |
+
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
| 708 |
+
|
| 709 |
+
convert-source-map@2.0.0:
|
| 710 |
+
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
| 711 |
+
|
| 712 |
+
cross-spawn@7.0.6:
|
| 713 |
+
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
| 714 |
+
engines: {node: '>= 8'}
|
| 715 |
+
|
| 716 |
+
cssesc@3.0.0:
|
| 717 |
+
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
| 718 |
+
engines: {node: '>=4'}
|
| 719 |
+
hasBin: true
|
| 720 |
+
|
| 721 |
+
csstype@3.1.3:
|
| 722 |
+
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
| 723 |
+
|
| 724 |
+
debug@4.4.1:
|
| 725 |
+
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
| 726 |
+
engines: {node: '>=6.0'}
|
| 727 |
+
peerDependencies:
|
| 728 |
+
supports-color: '*'
|
| 729 |
+
peerDependenciesMeta:
|
| 730 |
+
supports-color:
|
| 731 |
+
optional: true
|
| 732 |
+
|
| 733 |
+
deep-is@0.1.4:
|
| 734 |
+
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
| 735 |
+
|
| 736 |
+
detect-libc@2.0.4:
|
| 737 |
+
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
| 738 |
+
engines: {node: '>=8'}
|
| 739 |
+
|
| 740 |
+
didyoumean@1.2.2:
|
| 741 |
+
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
| 742 |
+
|
| 743 |
+
dlv@1.1.3:
|
| 744 |
+
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
| 745 |
+
|
| 746 |
+
eastasianwidth@0.2.0:
|
| 747 |
+
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
| 748 |
+
|
| 749 |
+
electron-to-chromium@1.5.199:
|
| 750 |
+
resolution: {integrity: sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==}
|
| 751 |
+
|
| 752 |
+
emoji-regex@8.0.0:
|
| 753 |
+
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
| 754 |
+
|
| 755 |
+
emoji-regex@9.2.2:
|
| 756 |
+
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
| 757 |
+
|
| 758 |
+
esbuild@0.25.8:
|
| 759 |
+
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
|
| 760 |
+
engines: {node: '>=18'}
|
| 761 |
+
hasBin: true
|
| 762 |
+
|
| 763 |
+
escalade@3.2.0:
|
| 764 |
+
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
| 765 |
+
engines: {node: '>=6'}
|
| 766 |
+
|
| 767 |
+
escape-string-regexp@4.0.0:
|
| 768 |
+
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
| 769 |
+
engines: {node: '>=10'}
|
| 770 |
+
|
| 771 |
+
eslint-plugin-react-hooks@5.2.0:
|
| 772 |
+
resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==}
|
| 773 |
+
engines: {node: '>=10'}
|
| 774 |
+
peerDependencies:
|
| 775 |
+
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
|
| 776 |
+
|
| 777 |
+
eslint-plugin-react-refresh@0.4.20:
|
| 778 |
+
resolution: {integrity: sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==}
|
| 779 |
+
peerDependencies:
|
| 780 |
+
eslint: '>=8.40'
|
| 781 |
+
|
| 782 |
+
eslint-scope@8.4.0:
|
| 783 |
+
resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
|
| 784 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 785 |
+
|
| 786 |
+
eslint-visitor-keys@3.4.3:
|
| 787 |
+
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
|
| 788 |
+
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
| 789 |
+
|
| 790 |
+
eslint-visitor-keys@4.2.1:
|
| 791 |
+
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
|
| 792 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 793 |
+
|
| 794 |
+
eslint@9.33.0:
|
| 795 |
+
resolution: {integrity: sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==}
|
| 796 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 797 |
+
hasBin: true
|
| 798 |
+
peerDependencies:
|
| 799 |
+
jiti: '*'
|
| 800 |
+
peerDependenciesMeta:
|
| 801 |
+
jiti:
|
| 802 |
+
optional: true
|
| 803 |
+
|
| 804 |
+
espree@10.4.0:
|
| 805 |
+
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
|
| 806 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 807 |
+
|
| 808 |
+
esquery@1.6.0:
|
| 809 |
+
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
|
| 810 |
+
engines: {node: '>=0.10'}
|
| 811 |
+
|
| 812 |
+
esrecurse@4.3.0:
|
| 813 |
+
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
|
| 814 |
+
engines: {node: '>=4.0'}
|
| 815 |
+
|
| 816 |
+
estraverse@5.3.0:
|
| 817 |
+
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
| 818 |
+
engines: {node: '>=4.0'}
|
| 819 |
+
|
| 820 |
+
esutils@2.0.3:
|
| 821 |
+
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
| 822 |
+
engines: {node: '>=0.10.0'}
|
| 823 |
+
|
| 824 |
+
fast-deep-equal@3.1.3:
|
| 825 |
+
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
| 826 |
+
|
| 827 |
+
fast-glob@3.3.3:
|
| 828 |
+
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
|
| 829 |
+
engines: {node: '>=8.6.0'}
|
| 830 |
+
|
| 831 |
+
fast-json-stable-stringify@2.1.0:
|
| 832 |
+
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
| 833 |
+
|
| 834 |
+
fast-levenshtein@2.0.6:
|
| 835 |
+
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
| 836 |
+
|
| 837 |
+
fastq@1.19.1:
|
| 838 |
+
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
| 839 |
+
|
| 840 |
+
fdir@6.4.6:
|
| 841 |
+
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
|
| 842 |
+
peerDependencies:
|
| 843 |
+
picomatch: ^3 || ^4
|
| 844 |
+
peerDependenciesMeta:
|
| 845 |
+
picomatch:
|
| 846 |
+
optional: true
|
| 847 |
+
|
| 848 |
+
file-entry-cache@8.0.0:
|
| 849 |
+
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
| 850 |
+
engines: {node: '>=16.0.0'}
|
| 851 |
+
|
| 852 |
+
fill-range@7.1.1:
|
| 853 |
+
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
| 854 |
+
engines: {node: '>=8'}
|
| 855 |
+
|
| 856 |
+
find-up@5.0.0:
|
| 857 |
+
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
| 858 |
+
engines: {node: '>=10'}
|
| 859 |
+
|
| 860 |
+
flat-cache@4.0.1:
|
| 861 |
+
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
| 862 |
+
engines: {node: '>=16'}
|
| 863 |
+
|
| 864 |
+
flatted@3.3.3:
|
| 865 |
+
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
| 866 |
+
|
| 867 |
+
foreground-child@3.3.1:
|
| 868 |
+
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
| 869 |
+
engines: {node: '>=14'}
|
| 870 |
+
|
| 871 |
+
fraction.js@4.3.7:
|
| 872 |
+
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
| 873 |
+
|
| 874 |
+
fsevents@2.3.3:
|
| 875 |
+
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
| 876 |
+
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
| 877 |
+
os: [darwin]
|
| 878 |
+
|
| 879 |
+
function-bind@1.1.2:
|
| 880 |
+
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
| 881 |
+
|
| 882 |
+
gensync@1.0.0-beta.2:
|
| 883 |
+
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
| 884 |
+
engines: {node: '>=6.9.0'}
|
| 885 |
+
|
| 886 |
+
glob-parent@5.1.2:
|
| 887 |
+
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
| 888 |
+
engines: {node: '>= 6'}
|
| 889 |
+
|
| 890 |
+
glob-parent@6.0.2:
|
| 891 |
+
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
| 892 |
+
engines: {node: '>=10.13.0'}
|
| 893 |
+
|
| 894 |
+
glob@10.4.5:
|
| 895 |
+
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
| 896 |
+
hasBin: true
|
| 897 |
+
|
| 898 |
+
globals@14.0.0:
|
| 899 |
+
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
| 900 |
+
engines: {node: '>=18'}
|
| 901 |
+
|
| 902 |
+
globals@16.3.0:
|
| 903 |
+
resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==}
|
| 904 |
+
engines: {node: '>=18'}
|
| 905 |
+
|
| 906 |
+
graphemer@1.4.0:
|
| 907 |
+
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
| 908 |
+
|
| 909 |
+
has-flag@4.0.0:
|
| 910 |
+
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
| 911 |
+
engines: {node: '>=8'}
|
| 912 |
+
|
| 913 |
+
hasown@2.0.2:
|
| 914 |
+
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
| 915 |
+
engines: {node: '>= 0.4'}
|
| 916 |
+
|
| 917 |
+
ignore@5.3.2:
|
| 918 |
+
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
| 919 |
+
engines: {node: '>= 4'}
|
| 920 |
+
|
| 921 |
+
ignore@7.0.5:
|
| 922 |
+
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
|
| 923 |
+
engines: {node: '>= 4'}
|
| 924 |
+
|
| 925 |
+
import-fresh@3.3.1:
|
| 926 |
+
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
| 927 |
+
engines: {node: '>=6'}
|
| 928 |
+
|
| 929 |
+
imurmurhash@0.1.4:
|
| 930 |
+
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
| 931 |
+
engines: {node: '>=0.8.19'}
|
| 932 |
+
|
| 933 |
+
is-binary-path@2.1.0:
|
| 934 |
+
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
| 935 |
+
engines: {node: '>=8'}
|
| 936 |
+
|
| 937 |
+
is-core-module@2.16.1:
|
| 938 |
+
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
| 939 |
+
engines: {node: '>= 0.4'}
|
| 940 |
+
|
| 941 |
+
is-extglob@2.1.1:
|
| 942 |
+
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
| 943 |
+
engines: {node: '>=0.10.0'}
|
| 944 |
+
|
| 945 |
+
is-fullwidth-code-point@3.0.0:
|
| 946 |
+
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
| 947 |
+
engines: {node: '>=8'}
|
| 948 |
+
|
| 949 |
+
is-glob@4.0.3:
|
| 950 |
+
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
| 951 |
+
engines: {node: '>=0.10.0'}
|
| 952 |
+
|
| 953 |
+
is-number@7.0.0:
|
| 954 |
+
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
| 955 |
+
engines: {node: '>=0.12.0'}
|
| 956 |
+
|
| 957 |
+
isexe@2.0.0:
|
| 958 |
+
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
| 959 |
+
|
| 960 |
+
jackspeak@3.4.3:
|
| 961 |
+
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
| 962 |
+
|
| 963 |
+
jiti@1.21.7:
|
| 964 |
+
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
| 965 |
+
hasBin: true
|
| 966 |
+
|
| 967 |
+
jiti@2.5.1:
|
| 968 |
+
resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==}
|
| 969 |
+
hasBin: true
|
| 970 |
+
|
| 971 |
+
js-tokens@4.0.0:
|
| 972 |
+
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
| 973 |
+
|
| 974 |
+
js-yaml@4.1.0:
|
| 975 |
+
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
| 976 |
+
hasBin: true
|
| 977 |
+
|
| 978 |
+
jsesc@3.1.0:
|
| 979 |
+
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
|
| 980 |
+
engines: {node: '>=6'}
|
| 981 |
+
hasBin: true
|
| 982 |
+
|
| 983 |
+
json-buffer@3.0.1:
|
| 984 |
+
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
|
| 985 |
+
|
| 986 |
+
json-schema-traverse@0.4.1:
|
| 987 |
+
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
| 988 |
+
|
| 989 |
+
json-stable-stringify-without-jsonify@1.0.1:
|
| 990 |
+
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
| 991 |
+
|
| 992 |
+
json5@2.2.3:
|
| 993 |
+
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
| 994 |
+
engines: {node: '>=6'}
|
| 995 |
+
hasBin: true
|
| 996 |
+
|
| 997 |
+
keyv@4.5.4:
|
| 998 |
+
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
| 999 |
+
|
| 1000 |
+
levn@0.4.1:
|
| 1001 |
+
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
| 1002 |
+
engines: {node: '>= 0.8.0'}
|
| 1003 |
+
|
| 1004 |
+
lightningcss-darwin-arm64@1.30.1:
|
| 1005 |
+
resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==}
|
| 1006 |
+
engines: {node: '>= 12.0.0'}
|
| 1007 |
+
cpu: [arm64]
|
| 1008 |
+
os: [darwin]
|
| 1009 |
+
|
| 1010 |
+
lightningcss-darwin-x64@1.30.1:
|
| 1011 |
+
resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==}
|
| 1012 |
+
engines: {node: '>= 12.0.0'}
|
| 1013 |
+
cpu: [x64]
|
| 1014 |
+
os: [darwin]
|
| 1015 |
+
|
| 1016 |
+
lightningcss-freebsd-x64@1.30.1:
|
| 1017 |
+
resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==}
|
| 1018 |
+
engines: {node: '>= 12.0.0'}
|
| 1019 |
+
cpu: [x64]
|
| 1020 |
+
os: [freebsd]
|
| 1021 |
+
|
| 1022 |
+
lightningcss-linux-arm-gnueabihf@1.30.1:
|
| 1023 |
+
resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==}
|
| 1024 |
+
engines: {node: '>= 12.0.0'}
|
| 1025 |
+
cpu: [arm]
|
| 1026 |
+
os: [linux]
|
| 1027 |
+
|
| 1028 |
+
lightningcss-linux-arm64-gnu@1.30.1:
|
| 1029 |
+
resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==}
|
| 1030 |
+
engines: {node: '>= 12.0.0'}
|
| 1031 |
+
cpu: [arm64]
|
| 1032 |
+
os: [linux]
|
| 1033 |
+
|
| 1034 |
+
lightningcss-linux-arm64-musl@1.30.1:
|
| 1035 |
+
resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
|
| 1036 |
+
engines: {node: '>= 12.0.0'}
|
| 1037 |
+
cpu: [arm64]
|
| 1038 |
+
os: [linux]
|
| 1039 |
+
|
| 1040 |
+
lightningcss-linux-x64-gnu@1.30.1:
|
| 1041 |
+
resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
|
| 1042 |
+
engines: {node: '>= 12.0.0'}
|
| 1043 |
+
cpu: [x64]
|
| 1044 |
+
os: [linux]
|
| 1045 |
+
|
| 1046 |
+
lightningcss-linux-x64-musl@1.30.1:
|
| 1047 |
+
resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
|
| 1048 |
+
engines: {node: '>= 12.0.0'}
|
| 1049 |
+
cpu: [x64]
|
| 1050 |
+
os: [linux]
|
| 1051 |
+
|
| 1052 |
+
lightningcss-win32-arm64-msvc@1.30.1:
|
| 1053 |
+
resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
|
| 1054 |
+
engines: {node: '>= 12.0.0'}
|
| 1055 |
+
cpu: [arm64]
|
| 1056 |
+
os: [win32]
|
| 1057 |
+
|
| 1058 |
+
lightningcss-win32-x64-msvc@1.30.1:
|
| 1059 |
+
resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==}
|
| 1060 |
+
engines: {node: '>= 12.0.0'}
|
| 1061 |
+
cpu: [x64]
|
| 1062 |
+
os: [win32]
|
| 1063 |
+
|
| 1064 |
+
lightningcss@1.30.1:
|
| 1065 |
+
resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
|
| 1066 |
+
engines: {node: '>= 12.0.0'}
|
| 1067 |
+
|
| 1068 |
+
lilconfig@3.1.3:
|
| 1069 |
+
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
| 1070 |
+
engines: {node: '>=14'}
|
| 1071 |
+
|
| 1072 |
+
lines-and-columns@1.2.4:
|
| 1073 |
+
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
| 1074 |
+
|
| 1075 |
+
locate-path@6.0.0:
|
| 1076 |
+
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
| 1077 |
+
engines: {node: '>=10'}
|
| 1078 |
+
|
| 1079 |
+
lodash.merge@4.6.2:
|
| 1080 |
+
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
| 1081 |
+
|
| 1082 |
+
lru-cache@10.4.3:
|
| 1083 |
+
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
| 1084 |
+
|
| 1085 |
+
lru-cache@5.1.1:
|
| 1086 |
+
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
| 1087 |
+
|
| 1088 |
+
merge2@1.4.1:
|
| 1089 |
+
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
| 1090 |
+
engines: {node: '>= 8'}
|
| 1091 |
+
|
| 1092 |
+
micromatch@4.0.8:
|
| 1093 |
+
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
| 1094 |
+
engines: {node: '>=8.6'}
|
| 1095 |
+
|
| 1096 |
+
minimatch@3.1.2:
|
| 1097 |
+
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
| 1098 |
+
|
| 1099 |
+
minimatch@9.0.5:
|
| 1100 |
+
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
| 1101 |
+
engines: {node: '>=16 || 14 >=14.17'}
|
| 1102 |
+
|
| 1103 |
+
minipass@7.1.2:
|
| 1104 |
+
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
| 1105 |
+
engines: {node: '>=16 || 14 >=14.17'}
|
| 1106 |
+
|
| 1107 |
+
ms@2.1.3:
|
| 1108 |
+
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
| 1109 |
+
|
| 1110 |
+
mz@2.7.0:
|
| 1111 |
+
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
| 1112 |
+
|
| 1113 |
+
nanoid@3.3.11:
|
| 1114 |
+
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
| 1115 |
+
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
| 1116 |
+
hasBin: true
|
| 1117 |
+
|
| 1118 |
+
natural-compare@1.4.0:
|
| 1119 |
+
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
| 1120 |
+
|
| 1121 |
+
node-releases@2.0.19:
|
| 1122 |
+
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
| 1123 |
+
|
| 1124 |
+
normalize-path@3.0.0:
|
| 1125 |
+
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
| 1126 |
+
engines: {node: '>=0.10.0'}
|
| 1127 |
+
|
| 1128 |
+
normalize-range@0.1.2:
|
| 1129 |
+
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
| 1130 |
+
engines: {node: '>=0.10.0'}
|
| 1131 |
+
|
| 1132 |
+
object-assign@4.1.1:
|
| 1133 |
+
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
| 1134 |
+
engines: {node: '>=0.10.0'}
|
| 1135 |
+
|
| 1136 |
+
object-hash@3.0.0:
|
| 1137 |
+
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
| 1138 |
+
engines: {node: '>= 6'}
|
| 1139 |
+
|
| 1140 |
+
optionator@0.9.4:
|
| 1141 |
+
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
| 1142 |
+
engines: {node: '>= 0.8.0'}
|
| 1143 |
+
|
| 1144 |
+
p-limit@3.1.0:
|
| 1145 |
+
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
| 1146 |
+
engines: {node: '>=10'}
|
| 1147 |
+
|
| 1148 |
+
p-locate@5.0.0:
|
| 1149 |
+
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
| 1150 |
+
engines: {node: '>=10'}
|
| 1151 |
+
|
| 1152 |
+
package-json-from-dist@1.0.1:
|
| 1153 |
+
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
| 1154 |
+
|
| 1155 |
+
parent-module@1.0.1:
|
| 1156 |
+
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
| 1157 |
+
engines: {node: '>=6'}
|
| 1158 |
+
|
| 1159 |
+
path-exists@4.0.0:
|
| 1160 |
+
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
| 1161 |
+
engines: {node: '>=8'}
|
| 1162 |
+
|
| 1163 |
+
path-key@3.1.1:
|
| 1164 |
+
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
| 1165 |
+
engines: {node: '>=8'}
|
| 1166 |
+
|
| 1167 |
+
path-parse@1.0.7:
|
| 1168 |
+
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
| 1169 |
+
|
| 1170 |
+
path-scurry@1.11.1:
|
| 1171 |
+
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
| 1172 |
+
engines: {node: '>=16 || 14 >=14.18'}
|
| 1173 |
+
|
| 1174 |
+
picocolors@1.1.1:
|
| 1175 |
+
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
| 1176 |
+
|
| 1177 |
+
picomatch@2.3.1:
|
| 1178 |
+
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
| 1179 |
+
engines: {node: '>=8.6'}
|
| 1180 |
+
|
| 1181 |
+
picomatch@4.0.3:
|
| 1182 |
+
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
| 1183 |
+
engines: {node: '>=12'}
|
| 1184 |
+
|
| 1185 |
+
pify@2.3.0:
|
| 1186 |
+
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
| 1187 |
+
engines: {node: '>=0.10.0'}
|
| 1188 |
+
|
| 1189 |
+
pirates@4.0.7:
|
| 1190 |
+
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
| 1191 |
+
engines: {node: '>= 6'}
|
| 1192 |
+
|
| 1193 |
+
postcss-import@15.1.0:
|
| 1194 |
+
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
|
| 1195 |
+
engines: {node: '>=14.0.0'}
|
| 1196 |
+
peerDependencies:
|
| 1197 |
+
postcss: ^8.0.0
|
| 1198 |
+
|
| 1199 |
+
postcss-js@4.0.1:
|
| 1200 |
+
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
|
| 1201 |
+
engines: {node: ^12 || ^14 || >= 16}
|
| 1202 |
+
peerDependencies:
|
| 1203 |
+
postcss: ^8.4.21
|
| 1204 |
+
|
| 1205 |
+
postcss-load-config@4.0.2:
|
| 1206 |
+
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
|
| 1207 |
+
engines: {node: '>= 14'}
|
| 1208 |
+
peerDependencies:
|
| 1209 |
+
postcss: '>=8.0.9'
|
| 1210 |
+
ts-node: '>=9.0.0'
|
| 1211 |
+
peerDependenciesMeta:
|
| 1212 |
+
postcss:
|
| 1213 |
+
optional: true
|
| 1214 |
+
ts-node:
|
| 1215 |
+
optional: true
|
| 1216 |
+
|
| 1217 |
+
postcss-nested@6.2.0:
|
| 1218 |
+
resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
|
| 1219 |
+
engines: {node: '>=12.0'}
|
| 1220 |
+
peerDependencies:
|
| 1221 |
+
postcss: ^8.2.14
|
| 1222 |
+
|
| 1223 |
+
postcss-selector-parser@6.1.2:
|
| 1224 |
+
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
|
| 1225 |
+
engines: {node: '>=4'}
|
| 1226 |
+
|
| 1227 |
+
postcss-value-parser@4.2.0:
|
| 1228 |
+
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
| 1229 |
+
|
| 1230 |
+
postcss@8.5.6:
|
| 1231 |
+
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
| 1232 |
+
engines: {node: ^10 || ^12 || >=14}
|
| 1233 |
+
|
| 1234 |
+
prelude-ls@1.2.1:
|
| 1235 |
+
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
| 1236 |
+
engines: {node: '>= 0.8.0'}
|
| 1237 |
+
|
| 1238 |
+
punycode@2.3.1:
|
| 1239 |
+
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
| 1240 |
+
engines: {node: '>=6'}
|
| 1241 |
+
|
| 1242 |
+
queue-microtask@1.2.3:
|
| 1243 |
+
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
| 1244 |
+
|
| 1245 |
+
react-dom@19.1.1:
|
| 1246 |
+
resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==}
|
| 1247 |
+
peerDependencies:
|
| 1248 |
+
react: ^19.1.1
|
| 1249 |
+
|
| 1250 |
+
react-refresh@0.17.0:
|
| 1251 |
+
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
| 1252 |
+
engines: {node: '>=0.10.0'}
|
| 1253 |
+
|
| 1254 |
+
react@19.1.1:
|
| 1255 |
+
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
|
| 1256 |
+
engines: {node: '>=0.10.0'}
|
| 1257 |
+
|
| 1258 |
+
read-cache@1.0.0:
|
| 1259 |
+
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
| 1260 |
+
|
| 1261 |
+
readdirp@3.6.0:
|
| 1262 |
+
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
| 1263 |
+
engines: {node: '>=8.10.0'}
|
| 1264 |
+
|
| 1265 |
+
resolve-from@4.0.0:
|
| 1266 |
+
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
| 1267 |
+
engines: {node: '>=4'}
|
| 1268 |
+
|
| 1269 |
+
resolve@1.22.10:
|
| 1270 |
+
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
|
| 1271 |
+
engines: {node: '>= 0.4'}
|
| 1272 |
+
hasBin: true
|
| 1273 |
+
|
| 1274 |
+
reusify@1.1.0:
|
| 1275 |
+
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
| 1276 |
+
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
| 1277 |
+
|
| 1278 |
+
rollup@4.46.2:
|
| 1279 |
+
resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==}
|
| 1280 |
+
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
| 1281 |
+
hasBin: true
|
| 1282 |
+
|
| 1283 |
+
run-parallel@1.2.0:
|
| 1284 |
+
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
| 1285 |
+
|
| 1286 |
+
scheduler@0.26.0:
|
| 1287 |
+
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
|
| 1288 |
+
|
| 1289 |
+
semver@6.3.1:
|
| 1290 |
+
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
| 1291 |
+
hasBin: true
|
| 1292 |
+
|
| 1293 |
+
semver@7.7.2:
|
| 1294 |
+
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
|
| 1295 |
+
engines: {node: '>=10'}
|
| 1296 |
+
hasBin: true
|
| 1297 |
+
|
| 1298 |
+
shebang-command@2.0.0:
|
| 1299 |
+
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
| 1300 |
+
engines: {node: '>=8'}
|
| 1301 |
+
|
| 1302 |
+
shebang-regex@3.0.0:
|
| 1303 |
+
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
| 1304 |
+
engines: {node: '>=8'}
|
| 1305 |
+
|
| 1306 |
+
signal-exit@4.1.0:
|
| 1307 |
+
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
| 1308 |
+
engines: {node: '>=14'}
|
| 1309 |
+
|
| 1310 |
+
source-map-js@1.2.1:
|
| 1311 |
+
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
| 1312 |
+
engines: {node: '>=0.10.0'}
|
| 1313 |
+
|
| 1314 |
+
string-width@4.2.3:
|
| 1315 |
+
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
| 1316 |
+
engines: {node: '>=8'}
|
| 1317 |
+
|
| 1318 |
+
string-width@5.1.2:
|
| 1319 |
+
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
| 1320 |
+
engines: {node: '>=12'}
|
| 1321 |
+
|
| 1322 |
+
strip-ansi@6.0.1:
|
| 1323 |
+
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
| 1324 |
+
engines: {node: '>=8'}
|
| 1325 |
+
|
| 1326 |
+
strip-ansi@7.1.0:
|
| 1327 |
+
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
|
| 1328 |
+
engines: {node: '>=12'}
|
| 1329 |
+
|
| 1330 |
+
strip-json-comments@3.1.1:
|
| 1331 |
+
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
| 1332 |
+
engines: {node: '>=8'}
|
| 1333 |
+
|
| 1334 |
+
sucrase@3.35.0:
|
| 1335 |
+
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
|
| 1336 |
+
engines: {node: '>=16 || 14 >=14.17'}
|
| 1337 |
+
hasBin: true
|
| 1338 |
+
|
| 1339 |
+
supports-color@7.2.0:
|
| 1340 |
+
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
| 1341 |
+
engines: {node: '>=8'}
|
| 1342 |
+
|
| 1343 |
+
supports-preserve-symlinks-flag@1.0.0:
|
| 1344 |
+
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
| 1345 |
+
engines: {node: '>= 0.4'}
|
| 1346 |
+
|
| 1347 |
+
tailwindcss@3.4.17:
|
| 1348 |
+
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
|
| 1349 |
+
engines: {node: '>=14.0.0'}
|
| 1350 |
+
hasBin: true
|
| 1351 |
+
|
| 1352 |
+
thenify-all@1.6.0:
|
| 1353 |
+
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
| 1354 |
+
engines: {node: '>=0.8'}
|
| 1355 |
+
|
| 1356 |
+
thenify@3.3.1:
|
| 1357 |
+
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
| 1358 |
+
|
| 1359 |
+
tinyglobby@0.2.14:
|
| 1360 |
+
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
|
| 1361 |
+
engines: {node: '>=12.0.0'}
|
| 1362 |
+
|
| 1363 |
+
to-regex-range@5.0.1:
|
| 1364 |
+
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
| 1365 |
+
engines: {node: '>=8.0'}
|
| 1366 |
+
|
| 1367 |
+
ts-api-utils@2.1.0:
|
| 1368 |
+
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
|
| 1369 |
+
engines: {node: '>=18.12'}
|
| 1370 |
+
peerDependencies:
|
| 1371 |
+
typescript: '>=4.8.4'
|
| 1372 |
+
|
| 1373 |
+
ts-interface-checker@0.1.13:
|
| 1374 |
+
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
| 1375 |
+
|
| 1376 |
+
type-check@0.4.0:
|
| 1377 |
+
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
| 1378 |
+
engines: {node: '>= 0.8.0'}
|
| 1379 |
+
|
| 1380 |
+
typescript-eslint@8.39.0:
|
| 1381 |
+
resolution: {integrity: sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==}
|
| 1382 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 1383 |
+
peerDependencies:
|
| 1384 |
+
eslint: ^8.57.0 || ^9.0.0
|
| 1385 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 1386 |
+
|
| 1387 |
+
typescript@5.8.3:
|
| 1388 |
+
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
| 1389 |
+
engines: {node: '>=14.17'}
|
| 1390 |
+
hasBin: true
|
| 1391 |
+
|
| 1392 |
+
update-browserslist-db@1.1.3:
|
| 1393 |
+
resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
|
| 1394 |
+
hasBin: true
|
| 1395 |
+
peerDependencies:
|
| 1396 |
+
browserslist: '>= 4.21.0'
|
| 1397 |
+
|
| 1398 |
+
uri-js@4.4.1:
|
| 1399 |
+
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
| 1400 |
+
|
| 1401 |
+
util-deprecate@1.0.2:
|
| 1402 |
+
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
| 1403 |
+
|
| 1404 |
+
vite@7.1.1:
|
| 1405 |
+
resolution: {integrity: sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==}
|
| 1406 |
+
engines: {node: ^20.19.0 || >=22.12.0}
|
| 1407 |
+
hasBin: true
|
| 1408 |
+
peerDependencies:
|
| 1409 |
+
'@types/node': ^20.19.0 || >=22.12.0
|
| 1410 |
+
jiti: '>=1.21.0'
|
| 1411 |
+
less: ^4.0.0
|
| 1412 |
+
lightningcss: ^1.21.0
|
| 1413 |
+
sass: ^1.70.0
|
| 1414 |
+
sass-embedded: ^1.70.0
|
| 1415 |
+
stylus: '>=0.54.8'
|
| 1416 |
+
sugarss: ^5.0.0
|
| 1417 |
+
terser: ^5.16.0
|
| 1418 |
+
tsx: ^4.8.1
|
| 1419 |
+
yaml: ^2.4.2
|
| 1420 |
+
peerDependenciesMeta:
|
| 1421 |
+
'@types/node':
|
| 1422 |
+
optional: true
|
| 1423 |
+
jiti:
|
| 1424 |
+
optional: true
|
| 1425 |
+
less:
|
| 1426 |
+
optional: true
|
| 1427 |
+
lightningcss:
|
| 1428 |
+
optional: true
|
| 1429 |
+
sass:
|
| 1430 |
+
optional: true
|
| 1431 |
+
sass-embedded:
|
| 1432 |
+
optional: true
|
| 1433 |
+
stylus:
|
| 1434 |
+
optional: true
|
| 1435 |
+
sugarss:
|
| 1436 |
+
optional: true
|
| 1437 |
+
terser:
|
| 1438 |
+
optional: true
|
| 1439 |
+
tsx:
|
| 1440 |
+
optional: true
|
| 1441 |
+
yaml:
|
| 1442 |
+
optional: true
|
| 1443 |
+
|
| 1444 |
+
which@2.0.2:
|
| 1445 |
+
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
| 1446 |
+
engines: {node: '>= 8'}
|
| 1447 |
+
hasBin: true
|
| 1448 |
+
|
| 1449 |
+
word-wrap@1.2.5:
|
| 1450 |
+
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
| 1451 |
+
engines: {node: '>=0.10.0'}
|
| 1452 |
+
|
| 1453 |
+
wrap-ansi@7.0.0:
|
| 1454 |
+
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
| 1455 |
+
engines: {node: '>=10'}
|
| 1456 |
+
|
| 1457 |
+
wrap-ansi@8.1.0:
|
| 1458 |
+
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
| 1459 |
+
engines: {node: '>=12'}
|
| 1460 |
+
|
| 1461 |
+
yallist@3.1.1:
|
| 1462 |
+
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
| 1463 |
+
|
| 1464 |
+
yaml@2.8.1:
|
| 1465 |
+
resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==}
|
| 1466 |
+
engines: {node: '>= 14.6'}
|
| 1467 |
+
hasBin: true
|
| 1468 |
+
|
| 1469 |
+
yocto-queue@0.1.0:
|
| 1470 |
+
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
| 1471 |
+
engines: {node: '>=10'}
|
| 1472 |
+
|
| 1473 |
+
snapshots:
|
| 1474 |
+
|
| 1475 |
+
'@alloc/quick-lru@5.2.0': {}
|
| 1476 |
+
|
| 1477 |
+
'@ampproject/remapping@2.3.0':
|
| 1478 |
+
dependencies:
|
| 1479 |
+
'@jridgewell/gen-mapping': 0.3.12
|
| 1480 |
+
'@jridgewell/trace-mapping': 0.3.29
|
| 1481 |
+
|
| 1482 |
+
'@babel/code-frame@7.27.1':
|
| 1483 |
+
dependencies:
|
| 1484 |
+
'@babel/helper-validator-identifier': 7.27.1
|
| 1485 |
+
js-tokens: 4.0.0
|
| 1486 |
+
picocolors: 1.1.1
|
| 1487 |
+
|
| 1488 |
+
'@babel/compat-data@7.28.0': {}
|
| 1489 |
+
|
| 1490 |
+
'@babel/core@7.28.0':
|
| 1491 |
+
dependencies:
|
| 1492 |
+
'@ampproject/remapping': 2.3.0
|
| 1493 |
+
'@babel/code-frame': 7.27.1
|
| 1494 |
+
'@babel/generator': 7.28.0
|
| 1495 |
+
'@babel/helper-compilation-targets': 7.27.2
|
| 1496 |
+
'@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0)
|
| 1497 |
+
'@babel/helpers': 7.28.2
|
| 1498 |
+
'@babel/parser': 7.28.0
|
| 1499 |
+
'@babel/template': 7.27.2
|
| 1500 |
+
'@babel/traverse': 7.28.0
|
| 1501 |
+
'@babel/types': 7.28.2
|
| 1502 |
+
convert-source-map: 2.0.0
|
| 1503 |
+
debug: 4.4.1
|
| 1504 |
+
gensync: 1.0.0-beta.2
|
| 1505 |
+
json5: 2.2.3
|
| 1506 |
+
semver: 6.3.1
|
| 1507 |
+
transitivePeerDependencies:
|
| 1508 |
+
- supports-color
|
| 1509 |
+
|
| 1510 |
+
'@babel/generator@7.28.0':
|
| 1511 |
+
dependencies:
|
| 1512 |
+
'@babel/parser': 7.28.0
|
| 1513 |
+
'@babel/types': 7.28.2
|
| 1514 |
+
'@jridgewell/gen-mapping': 0.3.12
|
| 1515 |
+
'@jridgewell/trace-mapping': 0.3.29
|
| 1516 |
+
jsesc: 3.1.0
|
| 1517 |
+
|
| 1518 |
+
'@babel/helper-compilation-targets@7.27.2':
|
| 1519 |
+
dependencies:
|
| 1520 |
+
'@babel/compat-data': 7.28.0
|
| 1521 |
+
'@babel/helper-validator-option': 7.27.1
|
| 1522 |
+
browserslist: 4.25.1
|
| 1523 |
+
lru-cache: 5.1.1
|
| 1524 |
+
semver: 6.3.1
|
| 1525 |
+
|
| 1526 |
+
'@babel/helper-globals@7.28.0': {}
|
| 1527 |
+
|
| 1528 |
+
'@babel/helper-module-imports@7.27.1':
|
| 1529 |
+
dependencies:
|
| 1530 |
+
'@babel/traverse': 7.28.0
|
| 1531 |
+
'@babel/types': 7.28.2
|
| 1532 |
+
transitivePeerDependencies:
|
| 1533 |
+
- supports-color
|
| 1534 |
+
|
| 1535 |
+
'@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)':
|
| 1536 |
+
dependencies:
|
| 1537 |
+
'@babel/core': 7.28.0
|
| 1538 |
+
'@babel/helper-module-imports': 7.27.1
|
| 1539 |
+
'@babel/helper-validator-identifier': 7.27.1
|
| 1540 |
+
'@babel/traverse': 7.28.0
|
| 1541 |
+
transitivePeerDependencies:
|
| 1542 |
+
- supports-color
|
| 1543 |
+
|
| 1544 |
+
'@babel/helper-plugin-utils@7.27.1': {}
|
| 1545 |
+
|
| 1546 |
+
'@babel/helper-string-parser@7.27.1': {}
|
| 1547 |
+
|
| 1548 |
+
'@babel/helper-validator-identifier@7.27.1': {}
|
| 1549 |
+
|
| 1550 |
+
'@babel/helper-validator-option@7.27.1': {}
|
| 1551 |
+
|
| 1552 |
+
'@babel/helpers@7.28.2':
|
| 1553 |
+
dependencies:
|
| 1554 |
+
'@babel/template': 7.27.2
|
| 1555 |
+
'@babel/types': 7.28.2
|
| 1556 |
+
|
| 1557 |
+
'@babel/parser@7.28.0':
|
| 1558 |
+
dependencies:
|
| 1559 |
+
'@babel/types': 7.28.2
|
| 1560 |
+
|
| 1561 |
+
'@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)':
|
| 1562 |
+
dependencies:
|
| 1563 |
+
'@babel/core': 7.28.0
|
| 1564 |
+
'@babel/helper-plugin-utils': 7.27.1
|
| 1565 |
+
|
| 1566 |
+
'@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)':
|
| 1567 |
+
dependencies:
|
| 1568 |
+
'@babel/core': 7.28.0
|
| 1569 |
+
'@babel/helper-plugin-utils': 7.27.1
|
| 1570 |
+
|
| 1571 |
+
'@babel/template@7.27.2':
|
| 1572 |
+
dependencies:
|
| 1573 |
+
'@babel/code-frame': 7.27.1
|
| 1574 |
+
'@babel/parser': 7.28.0
|
| 1575 |
+
'@babel/types': 7.28.2
|
| 1576 |
+
|
| 1577 |
+
'@babel/traverse@7.28.0':
|
| 1578 |
+
dependencies:
|
| 1579 |
+
'@babel/code-frame': 7.27.1
|
| 1580 |
+
'@babel/generator': 7.28.0
|
| 1581 |
+
'@babel/helper-globals': 7.28.0
|
| 1582 |
+
'@babel/parser': 7.28.0
|
| 1583 |
+
'@babel/template': 7.27.2
|
| 1584 |
+
'@babel/types': 7.28.2
|
| 1585 |
+
debug: 4.4.1
|
| 1586 |
+
transitivePeerDependencies:
|
| 1587 |
+
- supports-color
|
| 1588 |
+
|
| 1589 |
+
'@babel/types@7.28.2':
|
| 1590 |
+
dependencies:
|
| 1591 |
+
'@babel/helper-string-parser': 7.27.1
|
| 1592 |
+
'@babel/helper-validator-identifier': 7.27.1
|
| 1593 |
+
|
| 1594 |
+
'@esbuild/aix-ppc64@0.25.8':
|
| 1595 |
+
optional: true
|
| 1596 |
+
|
| 1597 |
+
'@esbuild/android-arm64@0.25.8':
|
| 1598 |
+
optional: true
|
| 1599 |
+
|
| 1600 |
+
'@esbuild/android-arm@0.25.8':
|
| 1601 |
+
optional: true
|
| 1602 |
+
|
| 1603 |
+
'@esbuild/android-x64@0.25.8':
|
| 1604 |
+
optional: true
|
| 1605 |
+
|
| 1606 |
+
'@esbuild/darwin-arm64@0.25.8':
|
| 1607 |
+
optional: true
|
| 1608 |
+
|
| 1609 |
+
'@esbuild/darwin-x64@0.25.8':
|
| 1610 |
+
optional: true
|
| 1611 |
+
|
| 1612 |
+
'@esbuild/freebsd-arm64@0.25.8':
|
| 1613 |
+
optional: true
|
| 1614 |
+
|
| 1615 |
+
'@esbuild/freebsd-x64@0.25.8':
|
| 1616 |
+
optional: true
|
| 1617 |
+
|
| 1618 |
+
'@esbuild/linux-arm64@0.25.8':
|
| 1619 |
+
optional: true
|
| 1620 |
+
|
| 1621 |
+
'@esbuild/linux-arm@0.25.8':
|
| 1622 |
+
optional: true
|
| 1623 |
+
|
| 1624 |
+
'@esbuild/linux-ia32@0.25.8':
|
| 1625 |
+
optional: true
|
| 1626 |
+
|
| 1627 |
+
'@esbuild/linux-loong64@0.25.8':
|
| 1628 |
+
optional: true
|
| 1629 |
+
|
| 1630 |
+
'@esbuild/linux-mips64el@0.25.8':
|
| 1631 |
+
optional: true
|
| 1632 |
+
|
| 1633 |
+
'@esbuild/linux-ppc64@0.25.8':
|
| 1634 |
+
optional: true
|
| 1635 |
+
|
| 1636 |
+
'@esbuild/linux-riscv64@0.25.8':
|
| 1637 |
+
optional: true
|
| 1638 |
+
|
| 1639 |
+
'@esbuild/linux-s390x@0.25.8':
|
| 1640 |
+
optional: true
|
| 1641 |
+
|
| 1642 |
+
'@esbuild/linux-x64@0.25.8':
|
| 1643 |
+
optional: true
|
| 1644 |
+
|
| 1645 |
+
'@esbuild/netbsd-arm64@0.25.8':
|
| 1646 |
+
optional: true
|
| 1647 |
+
|
| 1648 |
+
'@esbuild/netbsd-x64@0.25.8':
|
| 1649 |
+
optional: true
|
| 1650 |
+
|
| 1651 |
+
'@esbuild/openbsd-arm64@0.25.8':
|
| 1652 |
+
optional: true
|
| 1653 |
+
|
| 1654 |
+
'@esbuild/openbsd-x64@0.25.8':
|
| 1655 |
+
optional: true
|
| 1656 |
+
|
| 1657 |
+
'@esbuild/openharmony-arm64@0.25.8':
|
| 1658 |
+
optional: true
|
| 1659 |
+
|
| 1660 |
+
'@esbuild/sunos-x64@0.25.8':
|
| 1661 |
+
optional: true
|
| 1662 |
+
|
| 1663 |
+
'@esbuild/win32-arm64@0.25.8':
|
| 1664 |
+
optional: true
|
| 1665 |
+
|
| 1666 |
+
'@esbuild/win32-ia32@0.25.8':
|
| 1667 |
+
optional: true
|
| 1668 |
+
|
| 1669 |
+
'@esbuild/win32-x64@0.25.8':
|
| 1670 |
+
optional: true
|
| 1671 |
+
|
| 1672 |
+
'@eslint-community/eslint-utils@4.7.0(eslint@9.33.0(jiti@2.5.1))':
|
| 1673 |
+
dependencies:
|
| 1674 |
+
eslint: 9.33.0(jiti@2.5.1)
|
| 1675 |
+
eslint-visitor-keys: 3.4.3
|
| 1676 |
+
|
| 1677 |
+
'@eslint-community/regexpp@4.12.1': {}
|
| 1678 |
+
|
| 1679 |
+
'@eslint/config-array@0.21.0':
|
| 1680 |
+
dependencies:
|
| 1681 |
+
'@eslint/object-schema': 2.1.6
|
| 1682 |
+
debug: 4.4.1
|
| 1683 |
+
minimatch: 3.1.2
|
| 1684 |
+
transitivePeerDependencies:
|
| 1685 |
+
- supports-color
|
| 1686 |
+
|
| 1687 |
+
'@eslint/config-helpers@0.3.1': {}
|
| 1688 |
+
|
| 1689 |
+
'@eslint/core@0.15.2':
|
| 1690 |
+
dependencies:
|
| 1691 |
+
'@types/json-schema': 7.0.15
|
| 1692 |
+
|
| 1693 |
+
'@eslint/eslintrc@3.3.1':
|
| 1694 |
+
dependencies:
|
| 1695 |
+
ajv: 6.12.6
|
| 1696 |
+
debug: 4.4.1
|
| 1697 |
+
espree: 10.4.0
|
| 1698 |
+
globals: 14.0.0
|
| 1699 |
+
ignore: 5.3.2
|
| 1700 |
+
import-fresh: 3.3.1
|
| 1701 |
+
js-yaml: 4.1.0
|
| 1702 |
+
minimatch: 3.1.2
|
| 1703 |
+
strip-json-comments: 3.1.1
|
| 1704 |
+
transitivePeerDependencies:
|
| 1705 |
+
- supports-color
|
| 1706 |
+
|
| 1707 |
+
'@eslint/js@9.33.0': {}
|
| 1708 |
+
|
| 1709 |
+
'@eslint/object-schema@2.1.6': {}
|
| 1710 |
+
|
| 1711 |
+
'@eslint/plugin-kit@0.3.5':
|
| 1712 |
+
dependencies:
|
| 1713 |
+
'@eslint/core': 0.15.2
|
| 1714 |
+
levn: 0.4.1
|
| 1715 |
+
|
| 1716 |
+
'@humanfs/core@0.19.1': {}
|
| 1717 |
+
|
| 1718 |
+
'@humanfs/node@0.16.6':
|
| 1719 |
+
dependencies:
|
| 1720 |
+
'@humanfs/core': 0.19.1
|
| 1721 |
+
'@humanwhocodes/retry': 0.3.1
|
| 1722 |
+
|
| 1723 |
+
'@humanwhocodes/module-importer@1.0.1': {}
|
| 1724 |
+
|
| 1725 |
+
'@humanwhocodes/retry@0.3.1': {}
|
| 1726 |
+
|
| 1727 |
+
'@humanwhocodes/retry@0.4.3': {}
|
| 1728 |
+
|
| 1729 |
+
'@isaacs/cliui@8.0.2':
|
| 1730 |
+
dependencies:
|
| 1731 |
+
string-width: 5.1.2
|
| 1732 |
+
string-width-cjs: string-width@4.2.3
|
| 1733 |
+
strip-ansi: 7.1.0
|
| 1734 |
+
strip-ansi-cjs: strip-ansi@6.0.1
|
| 1735 |
+
wrap-ansi: 8.1.0
|
| 1736 |
+
wrap-ansi-cjs: wrap-ansi@7.0.0
|
| 1737 |
+
|
| 1738 |
+
'@jridgewell/gen-mapping@0.3.12':
|
| 1739 |
+
dependencies:
|
| 1740 |
+
'@jridgewell/sourcemap-codec': 1.5.4
|
| 1741 |
+
'@jridgewell/trace-mapping': 0.3.29
|
| 1742 |
+
|
| 1743 |
+
'@jridgewell/resolve-uri@3.1.2': {}
|
| 1744 |
+
|
| 1745 |
+
'@jridgewell/sourcemap-codec@1.5.4': {}
|
| 1746 |
+
|
| 1747 |
+
'@jridgewell/trace-mapping@0.3.29':
|
| 1748 |
+
dependencies:
|
| 1749 |
+
'@jridgewell/resolve-uri': 3.1.2
|
| 1750 |
+
'@jridgewell/sourcemap-codec': 1.5.4
|
| 1751 |
+
|
| 1752 |
+
'@nodelib/fs.scandir@2.1.5':
|
| 1753 |
+
dependencies:
|
| 1754 |
+
'@nodelib/fs.stat': 2.0.5
|
| 1755 |
+
run-parallel: 1.2.0
|
| 1756 |
+
|
| 1757 |
+
'@nodelib/fs.stat@2.0.5': {}
|
| 1758 |
+
|
| 1759 |
+
'@nodelib/fs.walk@1.2.8':
|
| 1760 |
+
dependencies:
|
| 1761 |
+
'@nodelib/fs.scandir': 2.1.5
|
| 1762 |
+
fastq: 1.19.1
|
| 1763 |
+
|
| 1764 |
+
'@pkgjs/parseargs@0.11.0':
|
| 1765 |
+
optional: true
|
| 1766 |
+
|
| 1767 |
+
'@rolldown/pluginutils@1.0.0-beta.27': {}
|
| 1768 |
+
|
| 1769 |
+
'@rollup/rollup-android-arm-eabi@4.46.2':
|
| 1770 |
+
optional: true
|
| 1771 |
+
|
| 1772 |
+
'@rollup/rollup-android-arm64@4.46.2':
|
| 1773 |
+
optional: true
|
| 1774 |
+
|
| 1775 |
+
'@rollup/rollup-darwin-arm64@4.46.2':
|
| 1776 |
+
optional: true
|
| 1777 |
+
|
| 1778 |
+
'@rollup/rollup-darwin-x64@4.46.2':
|
| 1779 |
+
optional: true
|
| 1780 |
+
|
| 1781 |
+
'@rollup/rollup-freebsd-arm64@4.46.2':
|
| 1782 |
+
optional: true
|
| 1783 |
+
|
| 1784 |
+
'@rollup/rollup-freebsd-x64@4.46.2':
|
| 1785 |
+
optional: true
|
| 1786 |
+
|
| 1787 |
+
'@rollup/rollup-linux-arm-gnueabihf@4.46.2':
|
| 1788 |
+
optional: true
|
| 1789 |
+
|
| 1790 |
+
'@rollup/rollup-linux-arm-musleabihf@4.46.2':
|
| 1791 |
+
optional: true
|
| 1792 |
+
|
| 1793 |
+
'@rollup/rollup-linux-arm64-gnu@4.46.2':
|
| 1794 |
+
optional: true
|
| 1795 |
+
|
| 1796 |
+
'@rollup/rollup-linux-arm64-musl@4.46.2':
|
| 1797 |
+
optional: true
|
| 1798 |
+
|
| 1799 |
+
'@rollup/rollup-linux-loongarch64-gnu@4.46.2':
|
| 1800 |
+
optional: true
|
| 1801 |
+
|
| 1802 |
+
'@rollup/rollup-linux-ppc64-gnu@4.46.2':
|
| 1803 |
+
optional: true
|
| 1804 |
+
|
| 1805 |
+
'@rollup/rollup-linux-riscv64-gnu@4.46.2':
|
| 1806 |
+
optional: true
|
| 1807 |
+
|
| 1808 |
+
'@rollup/rollup-linux-riscv64-musl@4.46.2':
|
| 1809 |
+
optional: true
|
| 1810 |
+
|
| 1811 |
+
'@rollup/rollup-linux-s390x-gnu@4.46.2':
|
| 1812 |
+
optional: true
|
| 1813 |
+
|
| 1814 |
+
'@rollup/rollup-linux-x64-gnu@4.46.2':
|
| 1815 |
+
optional: true
|
| 1816 |
+
|
| 1817 |
+
'@rollup/rollup-linux-x64-musl@4.46.2':
|
| 1818 |
+
optional: true
|
| 1819 |
+
|
| 1820 |
+
'@rollup/rollup-win32-arm64-msvc@4.46.2':
|
| 1821 |
+
optional: true
|
| 1822 |
+
|
| 1823 |
+
'@rollup/rollup-win32-ia32-msvc@4.46.2':
|
| 1824 |
+
optional: true
|
| 1825 |
+
|
| 1826 |
+
'@rollup/rollup-win32-x64-msvc@4.46.2':
|
| 1827 |
+
optional: true
|
| 1828 |
+
|
| 1829 |
+
'@types/babel__core@7.20.5':
|
| 1830 |
+
dependencies:
|
| 1831 |
+
'@babel/parser': 7.28.0
|
| 1832 |
+
'@babel/types': 7.28.2
|
| 1833 |
+
'@types/babel__generator': 7.27.0
|
| 1834 |
+
'@types/babel__template': 7.4.4
|
| 1835 |
+
'@types/babel__traverse': 7.28.0
|
| 1836 |
+
|
| 1837 |
+
'@types/babel__generator@7.27.0':
|
| 1838 |
+
dependencies:
|
| 1839 |
+
'@babel/types': 7.28.2
|
| 1840 |
+
|
| 1841 |
+
'@types/babel__template@7.4.4':
|
| 1842 |
+
dependencies:
|
| 1843 |
+
'@babel/parser': 7.28.0
|
| 1844 |
+
'@babel/types': 7.28.2
|
| 1845 |
+
|
| 1846 |
+
'@types/babel__traverse@7.28.0':
|
| 1847 |
+
dependencies:
|
| 1848 |
+
'@babel/types': 7.28.2
|
| 1849 |
+
|
| 1850 |
+
'@types/estree@1.0.8': {}
|
| 1851 |
+
|
| 1852 |
+
'@types/json-schema@7.0.15': {}
|
| 1853 |
+
|
| 1854 |
+
'@types/react-dom@19.1.7(@types/react@19.1.9(react@19.1.1))':
|
| 1855 |
+
dependencies:
|
| 1856 |
+
'@types/react': 19.1.9(react@19.1.1)
|
| 1857 |
+
|
| 1858 |
+
'@types/react@19.1.9(react@19.1.1)':
|
| 1859 |
+
dependencies:
|
| 1860 |
+
csstype: 3.1.3
|
| 1861 |
+
react: 19.1.1
|
| 1862 |
+
|
| 1863 |
+
'@typescript-eslint/eslint-plugin@8.39.0(@typescript-eslint/parser@8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)':
|
| 1864 |
+
dependencies:
|
| 1865 |
+
'@eslint-community/regexpp': 4.12.1
|
| 1866 |
+
'@typescript-eslint/parser': 8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)
|
| 1867 |
+
'@typescript-eslint/scope-manager': 8.39.0
|
| 1868 |
+
'@typescript-eslint/type-utils': 8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)
|
| 1869 |
+
'@typescript-eslint/utils': 8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)
|
| 1870 |
+
'@typescript-eslint/visitor-keys': 8.39.0
|
| 1871 |
+
eslint: 9.33.0(jiti@2.5.1)
|
| 1872 |
+
graphemer: 1.4.0
|
| 1873 |
+
ignore: 7.0.5
|
| 1874 |
+
natural-compare: 1.4.0
|
| 1875 |
+
ts-api-utils: 2.1.0(typescript@5.8.3)
|
| 1876 |
+
typescript: 5.8.3
|
| 1877 |
+
transitivePeerDependencies:
|
| 1878 |
+
- supports-color
|
| 1879 |
+
|
| 1880 |
+
'@typescript-eslint/parser@8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)':
|
| 1881 |
+
dependencies:
|
| 1882 |
+
'@typescript-eslint/scope-manager': 8.39.0
|
| 1883 |
+
'@typescript-eslint/types': 8.39.0
|
| 1884 |
+
'@typescript-eslint/typescript-estree': 8.39.0(typescript@5.8.3)
|
| 1885 |
+
'@typescript-eslint/visitor-keys': 8.39.0
|
| 1886 |
+
debug: 4.4.1
|
| 1887 |
+
eslint: 9.33.0(jiti@2.5.1)
|
| 1888 |
+
typescript: 5.8.3
|
| 1889 |
+
transitivePeerDependencies:
|
| 1890 |
+
- supports-color
|
| 1891 |
+
|
| 1892 |
+
'@typescript-eslint/project-service@8.39.0(typescript@5.8.3)':
|
| 1893 |
+
dependencies:
|
| 1894 |
+
'@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.8.3)
|
| 1895 |
+
'@typescript-eslint/types': 8.39.0
|
| 1896 |
+
debug: 4.4.1
|
| 1897 |
+
typescript: 5.8.3
|
| 1898 |
+
transitivePeerDependencies:
|
| 1899 |
+
- supports-color
|
| 1900 |
+
|
| 1901 |
+
'@typescript-eslint/scope-manager@8.39.0':
|
| 1902 |
+
dependencies:
|
| 1903 |
+
'@typescript-eslint/types': 8.39.0
|
| 1904 |
+
'@typescript-eslint/visitor-keys': 8.39.0
|
| 1905 |
+
|
| 1906 |
+
'@typescript-eslint/tsconfig-utils@8.39.0(typescript@5.8.3)':
|
| 1907 |
+
dependencies:
|
| 1908 |
+
typescript: 5.8.3
|
| 1909 |
+
|
| 1910 |
+
'@typescript-eslint/type-utils@8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)':
|
| 1911 |
+
dependencies:
|
| 1912 |
+
'@typescript-eslint/types': 8.39.0
|
| 1913 |
+
'@typescript-eslint/typescript-estree': 8.39.0(typescript@5.8.3)
|
| 1914 |
+
'@typescript-eslint/utils': 8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)
|
| 1915 |
+
debug: 4.4.1
|
| 1916 |
+
eslint: 9.33.0(jiti@2.5.1)
|
| 1917 |
+
ts-api-utils: 2.1.0(typescript@5.8.3)
|
| 1918 |
+
typescript: 5.8.3
|
| 1919 |
+
transitivePeerDependencies:
|
| 1920 |
+
- supports-color
|
| 1921 |
+
|
| 1922 |
+
'@typescript-eslint/types@8.39.0': {}
|
| 1923 |
+
|
| 1924 |
+
'@typescript-eslint/typescript-estree@8.39.0(typescript@5.8.3)':
|
| 1925 |
+
dependencies:
|
| 1926 |
+
'@typescript-eslint/project-service': 8.39.0(typescript@5.8.3)
|
| 1927 |
+
'@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.8.3)
|
| 1928 |
+
'@typescript-eslint/types': 8.39.0
|
| 1929 |
+
'@typescript-eslint/visitor-keys': 8.39.0
|
| 1930 |
+
debug: 4.4.1
|
| 1931 |
+
fast-glob: 3.3.3
|
| 1932 |
+
is-glob: 4.0.3
|
| 1933 |
+
minimatch: 9.0.5
|
| 1934 |
+
semver: 7.7.2
|
| 1935 |
+
ts-api-utils: 2.1.0(typescript@5.8.3)
|
| 1936 |
+
typescript: 5.8.3
|
| 1937 |
+
transitivePeerDependencies:
|
| 1938 |
+
- supports-color
|
| 1939 |
+
|
| 1940 |
+
'@typescript-eslint/utils@8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)':
|
| 1941 |
+
dependencies:
|
| 1942 |
+
'@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1))
|
| 1943 |
+
'@typescript-eslint/scope-manager': 8.39.0
|
| 1944 |
+
'@typescript-eslint/types': 8.39.0
|
| 1945 |
+
'@typescript-eslint/typescript-estree': 8.39.0(typescript@5.8.3)
|
| 1946 |
+
eslint: 9.33.0(jiti@2.5.1)
|
| 1947 |
+
typescript: 5.8.3
|
| 1948 |
+
transitivePeerDependencies:
|
| 1949 |
+
- supports-color
|
| 1950 |
+
|
| 1951 |
+
'@typescript-eslint/visitor-keys@8.39.0':
|
| 1952 |
+
dependencies:
|
| 1953 |
+
'@typescript-eslint/types': 8.39.0
|
| 1954 |
+
eslint-visitor-keys: 4.2.1
|
| 1955 |
+
|
| 1956 |
+
'@vitejs/plugin-react@4.7.0(vite@7.1.1(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1))':
|
| 1957 |
+
dependencies:
|
| 1958 |
+
'@babel/core': 7.28.0
|
| 1959 |
+
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0)
|
| 1960 |
+
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0)
|
| 1961 |
+
'@rolldown/pluginutils': 1.0.0-beta.27
|
| 1962 |
+
'@types/babel__core': 7.20.5
|
| 1963 |
+
react-refresh: 0.17.0
|
| 1964 |
+
vite: 7.1.1(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)
|
| 1965 |
+
transitivePeerDependencies:
|
| 1966 |
+
- supports-color
|
| 1967 |
+
|
| 1968 |
+
acorn-jsx@5.3.2(acorn@8.15.0):
|
| 1969 |
+
dependencies:
|
| 1970 |
+
acorn: 8.15.0
|
| 1971 |
+
|
| 1972 |
+
acorn@8.15.0: {}
|
| 1973 |
+
|
| 1974 |
+
ajv@6.12.6:
|
| 1975 |
+
dependencies:
|
| 1976 |
+
fast-deep-equal: 3.1.3
|
| 1977 |
+
fast-json-stable-stringify: 2.1.0
|
| 1978 |
+
json-schema-traverse: 0.4.1
|
| 1979 |
+
uri-js: 4.4.1
|
| 1980 |
+
|
| 1981 |
+
ansi-regex@5.0.1: {}
|
| 1982 |
+
|
| 1983 |
+
ansi-regex@6.1.0: {}
|
| 1984 |
+
|
| 1985 |
+
ansi-styles@4.3.0:
|
| 1986 |
+
dependencies:
|
| 1987 |
+
color-convert: 2.0.1
|
| 1988 |
+
|
| 1989 |
+
ansi-styles@6.2.1: {}
|
| 1990 |
+
|
| 1991 |
+
any-promise@1.3.0: {}
|
| 1992 |
+
|
| 1993 |
+
anymatch@3.1.3:
|
| 1994 |
+
dependencies:
|
| 1995 |
+
normalize-path: 3.0.0
|
| 1996 |
+
picomatch: 2.3.1
|
| 1997 |
+
|
| 1998 |
+
arg@5.0.2: {}
|
| 1999 |
+
|
| 2000 |
+
argparse@2.0.1: {}
|
| 2001 |
+
|
| 2002 |
+
autoprefixer@10.4.21(postcss@8.5.6):
|
| 2003 |
+
dependencies:
|
| 2004 |
+
browserslist: 4.25.1
|
| 2005 |
+
caniuse-lite: 1.0.30001733
|
| 2006 |
+
fraction.js: 4.3.7
|
| 2007 |
+
normalize-range: 0.1.2
|
| 2008 |
+
picocolors: 1.1.1
|
| 2009 |
+
postcss: 8.5.6
|
| 2010 |
+
postcss-value-parser: 4.2.0
|
| 2011 |
+
|
| 2012 |
+
balanced-match@1.0.2: {}
|
| 2013 |
+
|
| 2014 |
+
binary-extensions@2.3.0: {}
|
| 2015 |
+
|
| 2016 |
+
brace-expansion@1.1.12:
|
| 2017 |
+
dependencies:
|
| 2018 |
+
balanced-match: 1.0.2
|
| 2019 |
+
concat-map: 0.0.1
|
| 2020 |
+
|
| 2021 |
+
brace-expansion@2.0.2:
|
| 2022 |
+
dependencies:
|
| 2023 |
+
balanced-match: 1.0.2
|
| 2024 |
+
|
| 2025 |
+
braces@3.0.3:
|
| 2026 |
+
dependencies:
|
| 2027 |
+
fill-range: 7.1.1
|
| 2028 |
+
|
| 2029 |
+
browserslist@4.25.1:
|
| 2030 |
+
dependencies:
|
| 2031 |
+
caniuse-lite: 1.0.30001733
|
| 2032 |
+
electron-to-chromium: 1.5.199
|
| 2033 |
+
node-releases: 2.0.19
|
| 2034 |
+
update-browserslist-db: 1.1.3(browserslist@4.25.1)
|
| 2035 |
+
|
| 2036 |
+
callsites@3.1.0: {}
|
| 2037 |
+
|
| 2038 |
+
camelcase-css@2.0.1: {}
|
| 2039 |
+
|
| 2040 |
+
caniuse-lite@1.0.30001733: {}
|
| 2041 |
+
|
| 2042 |
+
chalk@4.1.2:
|
| 2043 |
+
dependencies:
|
| 2044 |
+
ansi-styles: 4.3.0
|
| 2045 |
+
supports-color: 7.2.0
|
| 2046 |
+
|
| 2047 |
+
chokidar@3.6.0:
|
| 2048 |
+
dependencies:
|
| 2049 |
+
anymatch: 3.1.3
|
| 2050 |
+
braces: 3.0.3
|
| 2051 |
+
glob-parent: 5.1.2
|
| 2052 |
+
is-binary-path: 2.1.0
|
| 2053 |
+
is-glob: 4.0.3
|
| 2054 |
+
normalize-path: 3.0.0
|
| 2055 |
+
readdirp: 3.6.0
|
| 2056 |
+
optionalDependencies:
|
| 2057 |
+
fsevents: 2.3.3
|
| 2058 |
+
|
| 2059 |
+
classnames@2.5.1: {}
|
| 2060 |
+
|
| 2061 |
+
color-convert@2.0.1:
|
| 2062 |
+
dependencies:
|
| 2063 |
+
color-name: 1.1.4
|
| 2064 |
+
|
| 2065 |
+
color-name@1.1.4: {}
|
| 2066 |
+
|
| 2067 |
+
commander@4.1.1: {}
|
| 2068 |
+
|
| 2069 |
+
concat-map@0.0.1: {}
|
| 2070 |
+
|
| 2071 |
+
convert-source-map@2.0.0: {}
|
| 2072 |
+
|
| 2073 |
+
cross-spawn@7.0.6:
|
| 2074 |
+
dependencies:
|
| 2075 |
+
path-key: 3.1.1
|
| 2076 |
+
shebang-command: 2.0.0
|
| 2077 |
+
which: 2.0.2
|
| 2078 |
+
|
| 2079 |
+
cssesc@3.0.0: {}
|
| 2080 |
+
|
| 2081 |
+
csstype@3.1.3: {}
|
| 2082 |
+
|
| 2083 |
+
debug@4.4.1:
|
| 2084 |
+
dependencies:
|
| 2085 |
+
ms: 2.1.3
|
| 2086 |
+
|
| 2087 |
+
deep-is@0.1.4: {}
|
| 2088 |
+
|
| 2089 |
+
detect-libc@2.0.4:
|
| 2090 |
+
optional: true
|
| 2091 |
+
|
| 2092 |
+
didyoumean@1.2.2: {}
|
| 2093 |
+
|
| 2094 |
+
dlv@1.1.3: {}
|
| 2095 |
+
|
| 2096 |
+
eastasianwidth@0.2.0: {}
|
| 2097 |
+
|
| 2098 |
+
electron-to-chromium@1.5.199: {}
|
| 2099 |
+
|
| 2100 |
+
emoji-regex@8.0.0: {}
|
| 2101 |
+
|
| 2102 |
+
emoji-regex@9.2.2: {}
|
| 2103 |
+
|
| 2104 |
+
esbuild@0.25.8:
|
| 2105 |
+
optionalDependencies:
|
| 2106 |
+
'@esbuild/aix-ppc64': 0.25.8
|
| 2107 |
+
'@esbuild/android-arm': 0.25.8
|
| 2108 |
+
'@esbuild/android-arm64': 0.25.8
|
| 2109 |
+
'@esbuild/android-x64': 0.25.8
|
| 2110 |
+
'@esbuild/darwin-arm64': 0.25.8
|
| 2111 |
+
'@esbuild/darwin-x64': 0.25.8
|
| 2112 |
+
'@esbuild/freebsd-arm64': 0.25.8
|
| 2113 |
+
'@esbuild/freebsd-x64': 0.25.8
|
| 2114 |
+
'@esbuild/linux-arm': 0.25.8
|
| 2115 |
+
'@esbuild/linux-arm64': 0.25.8
|
| 2116 |
+
'@esbuild/linux-ia32': 0.25.8
|
| 2117 |
+
'@esbuild/linux-loong64': 0.25.8
|
| 2118 |
+
'@esbuild/linux-mips64el': 0.25.8
|
| 2119 |
+
'@esbuild/linux-ppc64': 0.25.8
|
| 2120 |
+
'@esbuild/linux-riscv64': 0.25.8
|
| 2121 |
+
'@esbuild/linux-s390x': 0.25.8
|
| 2122 |
+
'@esbuild/linux-x64': 0.25.8
|
| 2123 |
+
'@esbuild/netbsd-arm64': 0.25.8
|
| 2124 |
+
'@esbuild/netbsd-x64': 0.25.8
|
| 2125 |
+
'@esbuild/openbsd-arm64': 0.25.8
|
| 2126 |
+
'@esbuild/openbsd-x64': 0.25.8
|
| 2127 |
+
'@esbuild/openharmony-arm64': 0.25.8
|
| 2128 |
+
'@esbuild/sunos-x64': 0.25.8
|
| 2129 |
+
'@esbuild/win32-arm64': 0.25.8
|
| 2130 |
+
'@esbuild/win32-ia32': 0.25.8
|
| 2131 |
+
'@esbuild/win32-x64': 0.25.8
|
| 2132 |
+
|
| 2133 |
+
escalade@3.2.0: {}
|
| 2134 |
+
|
| 2135 |
+
escape-string-regexp@4.0.0: {}
|
| 2136 |
+
|
| 2137 |
+
eslint-plugin-react-hooks@5.2.0(eslint@9.33.0(jiti@2.5.1)):
|
| 2138 |
+
dependencies:
|
| 2139 |
+
eslint: 9.33.0(jiti@2.5.1)
|
| 2140 |
+
|
| 2141 |
+
eslint-plugin-react-refresh@0.4.20(eslint@9.33.0(jiti@2.5.1)):
|
| 2142 |
+
dependencies:
|
| 2143 |
+
eslint: 9.33.0(jiti@2.5.1)
|
| 2144 |
+
|
| 2145 |
+
eslint-scope@8.4.0:
|
| 2146 |
+
dependencies:
|
| 2147 |
+
esrecurse: 4.3.0
|
| 2148 |
+
estraverse: 5.3.0
|
| 2149 |
+
|
| 2150 |
+
eslint-visitor-keys@3.4.3: {}
|
| 2151 |
+
|
| 2152 |
+
eslint-visitor-keys@4.2.1: {}
|
| 2153 |
+
|
| 2154 |
+
eslint@9.33.0(jiti@2.5.1):
|
| 2155 |
+
dependencies:
|
| 2156 |
+
'@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1))
|
| 2157 |
+
'@eslint-community/regexpp': 4.12.1
|
| 2158 |
+
'@eslint/config-array': 0.21.0
|
| 2159 |
+
'@eslint/config-helpers': 0.3.1
|
| 2160 |
+
'@eslint/core': 0.15.2
|
| 2161 |
+
'@eslint/eslintrc': 3.3.1
|
| 2162 |
+
'@eslint/js': 9.33.0
|
| 2163 |
+
'@eslint/plugin-kit': 0.3.5
|
| 2164 |
+
'@humanfs/node': 0.16.6
|
| 2165 |
+
'@humanwhocodes/module-importer': 1.0.1
|
| 2166 |
+
'@humanwhocodes/retry': 0.4.3
|
| 2167 |
+
'@types/estree': 1.0.8
|
| 2168 |
+
'@types/json-schema': 7.0.15
|
| 2169 |
+
ajv: 6.12.6
|
| 2170 |
+
chalk: 4.1.2
|
| 2171 |
+
cross-spawn: 7.0.6
|
| 2172 |
+
debug: 4.4.1
|
| 2173 |
+
escape-string-regexp: 4.0.0
|
| 2174 |
+
eslint-scope: 8.4.0
|
| 2175 |
+
eslint-visitor-keys: 4.2.1
|
| 2176 |
+
espree: 10.4.0
|
| 2177 |
+
esquery: 1.6.0
|
| 2178 |
+
esutils: 2.0.3
|
| 2179 |
+
fast-deep-equal: 3.1.3
|
| 2180 |
+
file-entry-cache: 8.0.0
|
| 2181 |
+
find-up: 5.0.0
|
| 2182 |
+
glob-parent: 6.0.2
|
| 2183 |
+
ignore: 5.3.2
|
| 2184 |
+
imurmurhash: 0.1.4
|
| 2185 |
+
is-glob: 4.0.3
|
| 2186 |
+
json-stable-stringify-without-jsonify: 1.0.1
|
| 2187 |
+
lodash.merge: 4.6.2
|
| 2188 |
+
minimatch: 3.1.2
|
| 2189 |
+
natural-compare: 1.4.0
|
| 2190 |
+
optionator: 0.9.4
|
| 2191 |
+
optionalDependencies:
|
| 2192 |
+
jiti: 2.5.1
|
| 2193 |
+
transitivePeerDependencies:
|
| 2194 |
+
- supports-color
|
| 2195 |
+
|
| 2196 |
+
espree@10.4.0:
|
| 2197 |
+
dependencies:
|
| 2198 |
+
acorn: 8.15.0
|
| 2199 |
+
acorn-jsx: 5.3.2(acorn@8.15.0)
|
| 2200 |
+
eslint-visitor-keys: 4.2.1
|
| 2201 |
+
|
| 2202 |
+
esquery@1.6.0:
|
| 2203 |
+
dependencies:
|
| 2204 |
+
estraverse: 5.3.0
|
| 2205 |
+
|
| 2206 |
+
esrecurse@4.3.0:
|
| 2207 |
+
dependencies:
|
| 2208 |
+
estraverse: 5.3.0
|
| 2209 |
+
|
| 2210 |
+
estraverse@5.3.0: {}
|
| 2211 |
+
|
| 2212 |
+
esutils@2.0.3: {}
|
| 2213 |
+
|
| 2214 |
+
fast-deep-equal@3.1.3: {}
|
| 2215 |
+
|
| 2216 |
+
fast-glob@3.3.3:
|
| 2217 |
+
dependencies:
|
| 2218 |
+
'@nodelib/fs.stat': 2.0.5
|
| 2219 |
+
'@nodelib/fs.walk': 1.2.8
|
| 2220 |
+
glob-parent: 5.1.2
|
| 2221 |
+
merge2: 1.4.1
|
| 2222 |
+
micromatch: 4.0.8
|
| 2223 |
+
|
| 2224 |
+
fast-json-stable-stringify@2.1.0: {}
|
| 2225 |
+
|
| 2226 |
+
fast-levenshtein@2.0.6: {}
|
| 2227 |
+
|
| 2228 |
+
fastq@1.19.1:
|
| 2229 |
+
dependencies:
|
| 2230 |
+
reusify: 1.1.0
|
| 2231 |
+
|
| 2232 |
+
fdir@6.4.6(picomatch@4.0.3):
|
| 2233 |
+
optionalDependencies:
|
| 2234 |
+
picomatch: 4.0.3
|
| 2235 |
+
|
| 2236 |
+
file-entry-cache@8.0.0:
|
| 2237 |
+
dependencies:
|
| 2238 |
+
flat-cache: 4.0.1
|
| 2239 |
+
|
| 2240 |
+
fill-range@7.1.1:
|
| 2241 |
+
dependencies:
|
| 2242 |
+
to-regex-range: 5.0.1
|
| 2243 |
+
|
| 2244 |
+
find-up@5.0.0:
|
| 2245 |
+
dependencies:
|
| 2246 |
+
locate-path: 6.0.0
|
| 2247 |
+
path-exists: 4.0.0
|
| 2248 |
+
|
| 2249 |
+
flat-cache@4.0.1:
|
| 2250 |
+
dependencies:
|
| 2251 |
+
flatted: 3.3.3
|
| 2252 |
+
keyv: 4.5.4
|
| 2253 |
+
|
| 2254 |
+
flatted@3.3.3: {}
|
| 2255 |
+
|
| 2256 |
+
foreground-child@3.3.1:
|
| 2257 |
+
dependencies:
|
| 2258 |
+
cross-spawn: 7.0.6
|
| 2259 |
+
signal-exit: 4.1.0
|
| 2260 |
+
|
| 2261 |
+
fraction.js@4.3.7: {}
|
| 2262 |
+
|
| 2263 |
+
fsevents@2.3.3:
|
| 2264 |
+
optional: true
|
| 2265 |
+
|
| 2266 |
+
function-bind@1.1.2: {}
|
| 2267 |
+
|
| 2268 |
+
gensync@1.0.0-beta.2: {}
|
| 2269 |
+
|
| 2270 |
+
glob-parent@5.1.2:
|
| 2271 |
+
dependencies:
|
| 2272 |
+
is-glob: 4.0.3
|
| 2273 |
+
|
| 2274 |
+
glob-parent@6.0.2:
|
| 2275 |
+
dependencies:
|
| 2276 |
+
is-glob: 4.0.3
|
| 2277 |
+
|
| 2278 |
+
glob@10.4.5:
|
| 2279 |
+
dependencies:
|
| 2280 |
+
foreground-child: 3.3.1
|
| 2281 |
+
jackspeak: 3.4.3
|
| 2282 |
+
minimatch: 9.0.5
|
| 2283 |
+
minipass: 7.1.2
|
| 2284 |
+
package-json-from-dist: 1.0.1
|
| 2285 |
+
path-scurry: 1.11.1
|
| 2286 |
+
|
| 2287 |
+
globals@14.0.0: {}
|
| 2288 |
+
|
| 2289 |
+
globals@16.3.0: {}
|
| 2290 |
+
|
| 2291 |
+
graphemer@1.4.0: {}
|
| 2292 |
+
|
| 2293 |
+
has-flag@4.0.0: {}
|
| 2294 |
+
|
| 2295 |
+
hasown@2.0.2:
|
| 2296 |
+
dependencies:
|
| 2297 |
+
function-bind: 1.1.2
|
| 2298 |
+
|
| 2299 |
+
ignore@5.3.2: {}
|
| 2300 |
+
|
| 2301 |
+
ignore@7.0.5: {}
|
| 2302 |
+
|
| 2303 |
+
import-fresh@3.3.1:
|
| 2304 |
+
dependencies:
|
| 2305 |
+
parent-module: 1.0.1
|
| 2306 |
+
resolve-from: 4.0.0
|
| 2307 |
+
|
| 2308 |
+
imurmurhash@0.1.4: {}
|
| 2309 |
+
|
| 2310 |
+
is-binary-path@2.1.0:
|
| 2311 |
+
dependencies:
|
| 2312 |
+
binary-extensions: 2.3.0
|
| 2313 |
+
|
| 2314 |
+
is-core-module@2.16.1:
|
| 2315 |
+
dependencies:
|
| 2316 |
+
hasown: 2.0.2
|
| 2317 |
+
|
| 2318 |
+
is-extglob@2.1.1: {}
|
| 2319 |
+
|
| 2320 |
+
is-fullwidth-code-point@3.0.0: {}
|
| 2321 |
+
|
| 2322 |
+
is-glob@4.0.3:
|
| 2323 |
+
dependencies:
|
| 2324 |
+
is-extglob: 2.1.1
|
| 2325 |
+
|
| 2326 |
+
is-number@7.0.0: {}
|
| 2327 |
+
|
| 2328 |
+
isexe@2.0.0: {}
|
| 2329 |
+
|
| 2330 |
+
jackspeak@3.4.3:
|
| 2331 |
+
dependencies:
|
| 2332 |
+
'@isaacs/cliui': 8.0.2
|
| 2333 |
+
optionalDependencies:
|
| 2334 |
+
'@pkgjs/parseargs': 0.11.0
|
| 2335 |
+
|
| 2336 |
+
jiti@1.21.7: {}
|
| 2337 |
+
|
| 2338 |
+
jiti@2.5.1:
|
| 2339 |
+
optional: true
|
| 2340 |
+
|
| 2341 |
+
js-tokens@4.0.0: {}
|
| 2342 |
+
|
| 2343 |
+
js-yaml@4.1.0:
|
| 2344 |
+
dependencies:
|
| 2345 |
+
argparse: 2.0.1
|
| 2346 |
+
|
| 2347 |
+
jsesc@3.1.0: {}
|
| 2348 |
+
|
| 2349 |
+
json-buffer@3.0.1: {}
|
| 2350 |
+
|
| 2351 |
+
json-schema-traverse@0.4.1: {}
|
| 2352 |
+
|
| 2353 |
+
json-stable-stringify-without-jsonify@1.0.1: {}
|
| 2354 |
+
|
| 2355 |
+
json5@2.2.3: {}
|
| 2356 |
+
|
| 2357 |
+
keyv@4.5.4:
|
| 2358 |
+
dependencies:
|
| 2359 |
+
json-buffer: 3.0.1
|
| 2360 |
+
|
| 2361 |
+
levn@0.4.1:
|
| 2362 |
+
dependencies:
|
| 2363 |
+
prelude-ls: 1.2.1
|
| 2364 |
+
type-check: 0.4.0
|
| 2365 |
+
|
| 2366 |
+
lightningcss-darwin-arm64@1.30.1:
|
| 2367 |
+
optional: true
|
| 2368 |
+
|
| 2369 |
+
lightningcss-darwin-x64@1.30.1:
|
| 2370 |
+
optional: true
|
| 2371 |
+
|
| 2372 |
+
lightningcss-freebsd-x64@1.30.1:
|
| 2373 |
+
optional: true
|
| 2374 |
+
|
| 2375 |
+
lightningcss-linux-arm-gnueabihf@1.30.1:
|
| 2376 |
+
optional: true
|
| 2377 |
+
|
| 2378 |
+
lightningcss-linux-arm64-gnu@1.30.1:
|
| 2379 |
+
optional: true
|
| 2380 |
+
|
| 2381 |
+
lightningcss-linux-arm64-musl@1.30.1:
|
| 2382 |
+
optional: true
|
| 2383 |
+
|
| 2384 |
+
lightningcss-linux-x64-gnu@1.30.1:
|
| 2385 |
+
optional: true
|
| 2386 |
+
|
| 2387 |
+
lightningcss-linux-x64-musl@1.30.1:
|
| 2388 |
+
optional: true
|
| 2389 |
+
|
| 2390 |
+
lightningcss-win32-arm64-msvc@1.30.1:
|
| 2391 |
+
optional: true
|
| 2392 |
+
|
| 2393 |
+
lightningcss-win32-x64-msvc@1.30.1:
|
| 2394 |
+
optional: true
|
| 2395 |
+
|
| 2396 |
+
lightningcss@1.30.1:
|
| 2397 |
+
dependencies:
|
| 2398 |
+
detect-libc: 2.0.4
|
| 2399 |
+
optionalDependencies:
|
| 2400 |
+
lightningcss-darwin-arm64: 1.30.1
|
| 2401 |
+
lightningcss-darwin-x64: 1.30.1
|
| 2402 |
+
lightningcss-freebsd-x64: 1.30.1
|
| 2403 |
+
lightningcss-linux-arm-gnueabihf: 1.30.1
|
| 2404 |
+
lightningcss-linux-arm64-gnu: 1.30.1
|
| 2405 |
+
lightningcss-linux-arm64-musl: 1.30.1
|
| 2406 |
+
lightningcss-linux-x64-gnu: 1.30.1
|
| 2407 |
+
lightningcss-linux-x64-musl: 1.30.1
|
| 2408 |
+
lightningcss-win32-arm64-msvc: 1.30.1
|
| 2409 |
+
lightningcss-win32-x64-msvc: 1.30.1
|
| 2410 |
+
optional: true
|
| 2411 |
+
|
| 2412 |
+
lilconfig@3.1.3: {}
|
| 2413 |
+
|
| 2414 |
+
lines-and-columns@1.2.4: {}
|
| 2415 |
+
|
| 2416 |
+
locate-path@6.0.0:
|
| 2417 |
+
dependencies:
|
| 2418 |
+
p-locate: 5.0.0
|
| 2419 |
+
|
| 2420 |
+
lodash.merge@4.6.2: {}
|
| 2421 |
+
|
| 2422 |
+
lru-cache@10.4.3: {}
|
| 2423 |
+
|
| 2424 |
+
lru-cache@5.1.1:
|
| 2425 |
+
dependencies:
|
| 2426 |
+
yallist: 3.1.1
|
| 2427 |
+
|
| 2428 |
+
merge2@1.4.1: {}
|
| 2429 |
+
|
| 2430 |
+
micromatch@4.0.8:
|
| 2431 |
+
dependencies:
|
| 2432 |
+
braces: 3.0.3
|
| 2433 |
+
picomatch: 2.3.1
|
| 2434 |
+
|
| 2435 |
+
minimatch@3.1.2:
|
| 2436 |
+
dependencies:
|
| 2437 |
+
brace-expansion: 1.1.12
|
| 2438 |
+
|
| 2439 |
+
minimatch@9.0.5:
|
| 2440 |
+
dependencies:
|
| 2441 |
+
brace-expansion: 2.0.2
|
| 2442 |
+
|
| 2443 |
+
minipass@7.1.2: {}
|
| 2444 |
+
|
| 2445 |
+
ms@2.1.3: {}
|
| 2446 |
+
|
| 2447 |
+
mz@2.7.0:
|
| 2448 |
+
dependencies:
|
| 2449 |
+
any-promise: 1.3.0
|
| 2450 |
+
object-assign: 4.1.1
|
| 2451 |
+
thenify-all: 1.6.0
|
| 2452 |
+
|
| 2453 |
+
nanoid@3.3.11: {}
|
| 2454 |
+
|
| 2455 |
+
natural-compare@1.4.0: {}
|
| 2456 |
+
|
| 2457 |
+
node-releases@2.0.19: {}
|
| 2458 |
+
|
| 2459 |
+
normalize-path@3.0.0: {}
|
| 2460 |
+
|
| 2461 |
+
normalize-range@0.1.2: {}
|
| 2462 |
+
|
| 2463 |
+
object-assign@4.1.1: {}
|
| 2464 |
+
|
| 2465 |
+
object-hash@3.0.0: {}
|
| 2466 |
+
|
| 2467 |
+
optionator@0.9.4:
|
| 2468 |
+
dependencies:
|
| 2469 |
+
deep-is: 0.1.4
|
| 2470 |
+
fast-levenshtein: 2.0.6
|
| 2471 |
+
levn: 0.4.1
|
| 2472 |
+
prelude-ls: 1.2.1
|
| 2473 |
+
type-check: 0.4.0
|
| 2474 |
+
word-wrap: 1.2.5
|
| 2475 |
+
|
| 2476 |
+
p-limit@3.1.0:
|
| 2477 |
+
dependencies:
|
| 2478 |
+
yocto-queue: 0.1.0
|
| 2479 |
+
|
| 2480 |
+
p-locate@5.0.0:
|
| 2481 |
+
dependencies:
|
| 2482 |
+
p-limit: 3.1.0
|
| 2483 |
+
|
| 2484 |
+
package-json-from-dist@1.0.1: {}
|
| 2485 |
+
|
| 2486 |
+
parent-module@1.0.1:
|
| 2487 |
+
dependencies:
|
| 2488 |
+
callsites: 3.1.0
|
| 2489 |
+
|
| 2490 |
+
path-exists@4.0.0: {}
|
| 2491 |
+
|
| 2492 |
+
path-key@3.1.1: {}
|
| 2493 |
+
|
| 2494 |
+
path-parse@1.0.7: {}
|
| 2495 |
+
|
| 2496 |
+
path-scurry@1.11.1:
|
| 2497 |
+
dependencies:
|
| 2498 |
+
lru-cache: 10.4.3
|
| 2499 |
+
minipass: 7.1.2
|
| 2500 |
+
|
| 2501 |
+
picocolors@1.1.1: {}
|
| 2502 |
+
|
| 2503 |
+
picomatch@2.3.1: {}
|
| 2504 |
+
|
| 2505 |
+
picomatch@4.0.3: {}
|
| 2506 |
+
|
| 2507 |
+
pify@2.3.0: {}
|
| 2508 |
+
|
| 2509 |
+
pirates@4.0.7: {}
|
| 2510 |
+
|
| 2511 |
+
postcss-import@15.1.0(postcss@8.5.6):
|
| 2512 |
+
dependencies:
|
| 2513 |
+
postcss: 8.5.6
|
| 2514 |
+
postcss-value-parser: 4.2.0
|
| 2515 |
+
read-cache: 1.0.0
|
| 2516 |
+
resolve: 1.22.10
|
| 2517 |
+
|
| 2518 |
+
postcss-js@4.0.1(postcss@8.5.6):
|
| 2519 |
+
dependencies:
|
| 2520 |
+
camelcase-css: 2.0.1
|
| 2521 |
+
postcss: 8.5.6
|
| 2522 |
+
|
| 2523 |
+
postcss-load-config@4.0.2(postcss@8.5.6):
|
| 2524 |
+
dependencies:
|
| 2525 |
+
lilconfig: 3.1.3
|
| 2526 |
+
yaml: 2.8.1
|
| 2527 |
+
optionalDependencies:
|
| 2528 |
+
postcss: 8.5.6
|
| 2529 |
+
|
| 2530 |
+
postcss-nested@6.2.0(postcss@8.5.6):
|
| 2531 |
+
dependencies:
|
| 2532 |
+
postcss: 8.5.6
|
| 2533 |
+
postcss-selector-parser: 6.1.2
|
| 2534 |
+
|
| 2535 |
+
postcss-selector-parser@6.1.2:
|
| 2536 |
+
dependencies:
|
| 2537 |
+
cssesc: 3.0.0
|
| 2538 |
+
util-deprecate: 1.0.2
|
| 2539 |
+
|
| 2540 |
+
postcss-value-parser@4.2.0: {}
|
| 2541 |
+
|
| 2542 |
+
postcss@8.5.6:
|
| 2543 |
+
dependencies:
|
| 2544 |
+
nanoid: 3.3.11
|
| 2545 |
+
picocolors: 1.1.1
|
| 2546 |
+
source-map-js: 1.2.1
|
| 2547 |
+
|
| 2548 |
+
prelude-ls@1.2.1: {}
|
| 2549 |
+
|
| 2550 |
+
punycode@2.3.1: {}
|
| 2551 |
+
|
| 2552 |
+
queue-microtask@1.2.3: {}
|
| 2553 |
+
|
| 2554 |
+
react-dom@19.1.1(react@19.1.1):
|
| 2555 |
+
dependencies:
|
| 2556 |
+
react: 19.1.1
|
| 2557 |
+
scheduler: 0.26.0
|
| 2558 |
+
|
| 2559 |
+
react-refresh@0.17.0: {}
|
| 2560 |
+
|
| 2561 |
+
react@19.1.1: {}
|
| 2562 |
+
|
| 2563 |
+
read-cache@1.0.0:
|
| 2564 |
+
dependencies:
|
| 2565 |
+
pify: 2.3.0
|
| 2566 |
+
|
| 2567 |
+
readdirp@3.6.0:
|
| 2568 |
+
dependencies:
|
| 2569 |
+
picomatch: 2.3.1
|
| 2570 |
+
|
| 2571 |
+
resolve-from@4.0.0: {}
|
| 2572 |
+
|
| 2573 |
+
resolve@1.22.10:
|
| 2574 |
+
dependencies:
|
| 2575 |
+
is-core-module: 2.16.1
|
| 2576 |
+
path-parse: 1.0.7
|
| 2577 |
+
supports-preserve-symlinks-flag: 1.0.0
|
| 2578 |
+
|
| 2579 |
+
reusify@1.1.0: {}
|
| 2580 |
+
|
| 2581 |
+
rollup@4.46.2:
|
| 2582 |
+
dependencies:
|
| 2583 |
+
'@types/estree': 1.0.8
|
| 2584 |
+
optionalDependencies:
|
| 2585 |
+
'@rollup/rollup-android-arm-eabi': 4.46.2
|
| 2586 |
+
'@rollup/rollup-android-arm64': 4.46.2
|
| 2587 |
+
'@rollup/rollup-darwin-arm64': 4.46.2
|
| 2588 |
+
'@rollup/rollup-darwin-x64': 4.46.2
|
| 2589 |
+
'@rollup/rollup-freebsd-arm64': 4.46.2
|
| 2590 |
+
'@rollup/rollup-freebsd-x64': 4.46.2
|
| 2591 |
+
'@rollup/rollup-linux-arm-gnueabihf': 4.46.2
|
| 2592 |
+
'@rollup/rollup-linux-arm-musleabihf': 4.46.2
|
| 2593 |
+
'@rollup/rollup-linux-arm64-gnu': 4.46.2
|
| 2594 |
+
'@rollup/rollup-linux-arm64-musl': 4.46.2
|
| 2595 |
+
'@rollup/rollup-linux-loongarch64-gnu': 4.46.2
|
| 2596 |
+
'@rollup/rollup-linux-ppc64-gnu': 4.46.2
|
| 2597 |
+
'@rollup/rollup-linux-riscv64-gnu': 4.46.2
|
| 2598 |
+
'@rollup/rollup-linux-riscv64-musl': 4.46.2
|
| 2599 |
+
'@rollup/rollup-linux-s390x-gnu': 4.46.2
|
| 2600 |
+
'@rollup/rollup-linux-x64-gnu': 4.46.2
|
| 2601 |
+
'@rollup/rollup-linux-x64-musl': 4.46.2
|
| 2602 |
+
'@rollup/rollup-win32-arm64-msvc': 4.46.2
|
| 2603 |
+
'@rollup/rollup-win32-ia32-msvc': 4.46.2
|
| 2604 |
+
'@rollup/rollup-win32-x64-msvc': 4.46.2
|
| 2605 |
+
fsevents: 2.3.3
|
| 2606 |
+
|
| 2607 |
+
run-parallel@1.2.0:
|
| 2608 |
+
dependencies:
|
| 2609 |
+
queue-microtask: 1.2.3
|
| 2610 |
+
|
| 2611 |
+
scheduler@0.26.0: {}
|
| 2612 |
+
|
| 2613 |
+
semver@6.3.1: {}
|
| 2614 |
+
|
| 2615 |
+
semver@7.7.2: {}
|
| 2616 |
+
|
| 2617 |
+
shebang-command@2.0.0:
|
| 2618 |
+
dependencies:
|
| 2619 |
+
shebang-regex: 3.0.0
|
| 2620 |
+
|
| 2621 |
+
shebang-regex@3.0.0: {}
|
| 2622 |
+
|
| 2623 |
+
signal-exit@4.1.0: {}
|
| 2624 |
+
|
| 2625 |
+
source-map-js@1.2.1: {}
|
| 2626 |
+
|
| 2627 |
+
string-width@4.2.3:
|
| 2628 |
+
dependencies:
|
| 2629 |
+
emoji-regex: 8.0.0
|
| 2630 |
+
is-fullwidth-code-point: 3.0.0
|
| 2631 |
+
strip-ansi: 6.0.1
|
| 2632 |
+
|
| 2633 |
+
string-width@5.1.2:
|
| 2634 |
+
dependencies:
|
| 2635 |
+
eastasianwidth: 0.2.0
|
| 2636 |
+
emoji-regex: 9.2.2
|
| 2637 |
+
strip-ansi: 7.1.0
|
| 2638 |
+
|
| 2639 |
+
strip-ansi@6.0.1:
|
| 2640 |
+
dependencies:
|
| 2641 |
+
ansi-regex: 5.0.1
|
| 2642 |
+
|
| 2643 |
+
strip-ansi@7.1.0:
|
| 2644 |
+
dependencies:
|
| 2645 |
+
ansi-regex: 6.1.0
|
| 2646 |
+
|
| 2647 |
+
strip-json-comments@3.1.1: {}
|
| 2648 |
+
|
| 2649 |
+
sucrase@3.35.0:
|
| 2650 |
+
dependencies:
|
| 2651 |
+
'@jridgewell/gen-mapping': 0.3.12
|
| 2652 |
+
commander: 4.1.1
|
| 2653 |
+
glob: 10.4.5
|
| 2654 |
+
lines-and-columns: 1.2.4
|
| 2655 |
+
mz: 2.7.0
|
| 2656 |
+
pirates: 4.0.7
|
| 2657 |
+
ts-interface-checker: 0.1.13
|
| 2658 |
+
|
| 2659 |
+
supports-color@7.2.0:
|
| 2660 |
+
dependencies:
|
| 2661 |
+
has-flag: 4.0.0
|
| 2662 |
+
|
| 2663 |
+
supports-preserve-symlinks-flag@1.0.0: {}
|
| 2664 |
+
|
| 2665 |
+
tailwindcss@3.4.17:
|
| 2666 |
+
dependencies:
|
| 2667 |
+
'@alloc/quick-lru': 5.2.0
|
| 2668 |
+
arg: 5.0.2
|
| 2669 |
+
chokidar: 3.6.0
|
| 2670 |
+
didyoumean: 1.2.2
|
| 2671 |
+
dlv: 1.1.3
|
| 2672 |
+
fast-glob: 3.3.3
|
| 2673 |
+
glob-parent: 6.0.2
|
| 2674 |
+
is-glob: 4.0.3
|
| 2675 |
+
jiti: 1.21.7
|
| 2676 |
+
lilconfig: 3.1.3
|
| 2677 |
+
micromatch: 4.0.8
|
| 2678 |
+
normalize-path: 3.0.0
|
| 2679 |
+
object-hash: 3.0.0
|
| 2680 |
+
picocolors: 1.1.1
|
| 2681 |
+
postcss: 8.5.6
|
| 2682 |
+
postcss-import: 15.1.0(postcss@8.5.6)
|
| 2683 |
+
postcss-js: 4.0.1(postcss@8.5.6)
|
| 2684 |
+
postcss-load-config: 4.0.2(postcss@8.5.6)
|
| 2685 |
+
postcss-nested: 6.2.0(postcss@8.5.6)
|
| 2686 |
+
postcss-selector-parser: 6.1.2
|
| 2687 |
+
resolve: 1.22.10
|
| 2688 |
+
sucrase: 3.35.0
|
| 2689 |
+
transitivePeerDependencies:
|
| 2690 |
+
- ts-node
|
| 2691 |
+
|
| 2692 |
+
thenify-all@1.6.0:
|
| 2693 |
+
dependencies:
|
| 2694 |
+
thenify: 3.3.1
|
| 2695 |
+
|
| 2696 |
+
thenify@3.3.1:
|
| 2697 |
+
dependencies:
|
| 2698 |
+
any-promise: 1.3.0
|
| 2699 |
+
|
| 2700 |
+
tinyglobby@0.2.14:
|
| 2701 |
+
dependencies:
|
| 2702 |
+
fdir: 6.4.6(picomatch@4.0.3)
|
| 2703 |
+
picomatch: 4.0.3
|
| 2704 |
+
|
| 2705 |
+
to-regex-range@5.0.1:
|
| 2706 |
+
dependencies:
|
| 2707 |
+
is-number: 7.0.0
|
| 2708 |
+
|
| 2709 |
+
ts-api-utils@2.1.0(typescript@5.8.3):
|
| 2710 |
+
dependencies:
|
| 2711 |
+
typescript: 5.8.3
|
| 2712 |
+
|
| 2713 |
+
ts-interface-checker@0.1.13: {}
|
| 2714 |
+
|
| 2715 |
+
type-check@0.4.0:
|
| 2716 |
+
dependencies:
|
| 2717 |
+
prelude-ls: 1.2.1
|
| 2718 |
+
|
| 2719 |
+
typescript-eslint@8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3):
|
| 2720 |
+
dependencies:
|
| 2721 |
+
'@typescript-eslint/eslint-plugin': 8.39.0(@typescript-eslint/parser@8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)
|
| 2722 |
+
'@typescript-eslint/parser': 8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)
|
| 2723 |
+
'@typescript-eslint/typescript-estree': 8.39.0(typescript@5.8.3)
|
| 2724 |
+
'@typescript-eslint/utils': 8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)
|
| 2725 |
+
eslint: 9.33.0(jiti@2.5.1)
|
| 2726 |
+
typescript: 5.8.3
|
| 2727 |
+
transitivePeerDependencies:
|
| 2728 |
+
- supports-color
|
| 2729 |
+
|
| 2730 |
+
typescript@5.8.3: {}
|
| 2731 |
+
|
| 2732 |
+
update-browserslist-db@1.1.3(browserslist@4.25.1):
|
| 2733 |
+
dependencies:
|
| 2734 |
+
browserslist: 4.25.1
|
| 2735 |
+
escalade: 3.2.0
|
| 2736 |
+
picocolors: 1.1.1
|
| 2737 |
+
|
| 2738 |
+
uri-js@4.4.1:
|
| 2739 |
+
dependencies:
|
| 2740 |
+
punycode: 2.3.1
|
| 2741 |
+
|
| 2742 |
+
util-deprecate@1.0.2: {}
|
| 2743 |
+
|
| 2744 |
+
vite@7.1.1(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1):
|
| 2745 |
+
dependencies:
|
| 2746 |
+
esbuild: 0.25.8
|
| 2747 |
+
fdir: 6.4.6(picomatch@4.0.3)
|
| 2748 |
+
picomatch: 4.0.3
|
| 2749 |
+
postcss: 8.5.6
|
| 2750 |
+
rollup: 4.46.2
|
| 2751 |
+
tinyglobby: 0.2.14
|
| 2752 |
+
optionalDependencies:
|
| 2753 |
+
fsevents: 2.3.3
|
| 2754 |
+
jiti: 2.5.1
|
| 2755 |
+
lightningcss: 1.30.1
|
| 2756 |
+
yaml: 2.8.1
|
| 2757 |
+
|
| 2758 |
+
which@2.0.2:
|
| 2759 |
+
dependencies:
|
| 2760 |
+
isexe: 2.0.0
|
| 2761 |
+
|
| 2762 |
+
word-wrap@1.2.5: {}
|
| 2763 |
+
|
| 2764 |
+
wrap-ansi@7.0.0:
|
| 2765 |
+
dependencies:
|
| 2766 |
+
ansi-styles: 4.3.0
|
| 2767 |
+
string-width: 4.2.3
|
| 2768 |
+
strip-ansi: 6.0.1
|
| 2769 |
+
|
| 2770 |
+
wrap-ansi@8.1.0:
|
| 2771 |
+
dependencies:
|
| 2772 |
+
ansi-styles: 6.2.1
|
| 2773 |
+
string-width: 5.1.2
|
| 2774 |
+
strip-ansi: 7.1.0
|
| 2775 |
+
|
| 2776 |
+
yallist@3.1.1: {}
|
| 2777 |
+
|
| 2778 |
+
yaml@2.8.1: {}
|
| 2779 |
+
|
| 2780 |
+
yocto-queue@0.1.0: {}
|
frontend/postcss.config.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export default {
|
| 2 |
+
plugins: {
|
| 3 |
+
tailwindcss: {},
|
| 4 |
+
autoprefixer: {},
|
| 5 |
+
},
|
| 6 |
+
}
|
frontend/public/logo.svg
ADDED
|
|
frontend/public/vite.svg
ADDED
|
|
frontend/src/App.css
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Main app styles - matches original Next.js design */
|
| 2 |
+
|
| 3 |
+
/* Card styling to match original */
|
| 4 |
+
.card {
|
| 5 |
+
@apply bg-white rounded-xl border border-gray-200 shadow-sm transition-shadow duration-200;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
.card:hover {
|
| 9 |
+
@apply shadow-md;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
/* Button styling to match original */
|
| 13 |
+
.btn {
|
| 14 |
+
@apply inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium shadow disabled:opacity-50 disabled:cursor-not-allowed;
|
| 15 |
+
background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 16 |
+
color: white;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
/* Input styling with animated blue glow */
|
| 20 |
+
.input {
|
| 21 |
+
@apply rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2;
|
| 22 |
+
transition: box-shadow .2s ease;
|
| 23 |
+
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.5);
|
| 24 |
+
}
|
| 25 |
+
.input:focus {
|
| 26 |
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.35);
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
/* Badge styling for potential use */
|
| 30 |
+
.badge {
|
| 31 |
+
@apply inline-flex items-center rounded-full bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
/* Custom spinner animation */
|
| 35 |
+
@keyframes spin {
|
| 36 |
+
to {
|
| 37 |
+
transform: rotate(360deg);
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.animate-spin {
|
| 42 |
+
animation: spin 1s linear infinite;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/* Line clamp utility for older browsers */
|
| 46 |
+
.line-clamp-2 {
|
| 47 |
+
display: -webkit-box;
|
| 48 |
+
-webkit-line-clamp: 2;
|
| 49 |
+
-webkit-box-orient: vertical;
|
| 50 |
+
overflow: hidden;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
/* Prose styling for newsletter content */
|
| 54 |
+
.prose {
|
| 55 |
+
max-width: 65ch;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.prose-sm {
|
| 59 |
+
font-size: 0.875rem;
|
| 60 |
+
line-height: 1.5;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
/* Ensure the app fills the viewport */
|
| 64 |
+
#root {
|
| 65 |
+
min-height: 100vh;
|
| 66 |
+
}
|
frontend/src/App.tsx
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useMemo, useState } from 'react'
|
| 2 |
+
import FeedPicker from './components/FeedPicker'
|
| 3 |
+
import EditorModal from './components/EditorModal'
|
| 4 |
+
import TweetCards from './components/TweetCards'
|
| 5 |
+
import './App.css'
|
| 6 |
+
|
| 7 |
+
type Article = { title: string, link: string, summary?: string, published?: string, source?: string }
|
| 8 |
+
|
| 9 |
+
// Simple spinner component
|
| 10 |
+
function Spinner() {
|
| 11 |
+
return (
|
| 12 |
+
<div className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]" role="status">
|
| 13 |
+
<span className="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]">Loading...</span>
|
| 14 |
+
</div>
|
| 15 |
+
)
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
function App() {
|
| 19 |
+
const [sessionId] = useState(() => Math.random().toString(36).slice(2))
|
| 20 |
+
const [apiBase] = useState('/api')
|
| 21 |
+
const [sources, setSources] = useState<string[]>([])
|
| 22 |
+
const [articles, setArticles] = useState<Article[]>([])
|
| 23 |
+
const [summary, setSummary] = useState('')
|
| 24 |
+
const [tweets, setTweets] = useState<Array<{id: string, content: string, summary_title: string, summary_link: string, summary_source: string}>>([])
|
| 25 |
+
|
| 26 |
+
const [newsletterHtml, setNewsletterHtml] = useState('')
|
| 27 |
+
const [loadingHighlights, setLoadingHighlights] = useState(false)
|
| 28 |
+
const [loadingSummaries, setLoadingSummaries] = useState(false)
|
| 29 |
+
const [loadingTweets, setLoadingTweets] = useState(false)
|
| 30 |
+
const [loadingNewsletter, setLoadingNewsletter] = useState(false)
|
| 31 |
+
const [summariesMode, setSummariesMode] = useState(false)
|
| 32 |
+
const [pageIndex, setPageIndex] = useState(0)
|
| 33 |
+
const [highlights, setHighlights] = useState<Array<{ title: string, link: string, source?: string, summary: string }>>([])
|
| 34 |
+
|
| 35 |
+
const [selectedArticles, setSelectedArticles] = useState<string[]>([]) // Array of article URLs
|
| 36 |
+
const [isPaginated, setIsPaginated] = useState(true) // Default to paginated view
|
| 37 |
+
const [currentEditingTweetId, setCurrentEditingTweetId] = useState<string | null>(null)
|
| 38 |
+
const [tweetConversations, setTweetConversations] = useState<Record<string, Array<{role: string, content: string}>>>({})
|
| 39 |
+
|
| 40 |
+
const [pendingTweetUpdate, setPendingTweetUpdate] = useState<string | null>(null)
|
| 41 |
+
const hasHighlights = useMemo(() => !!summary, [summary])
|
| 42 |
+
|
| 43 |
+
const [editorOpen, setEditorOpen] = useState(false)
|
| 44 |
+
const [editorTitle, setEditorTitle] = useState('Editor')
|
| 45 |
+
const [editorText, setEditorText] = useState('')
|
| 46 |
+
const [editTarget, setEditTarget] = useState<'summary' | `tweet-${number}` | 'newsletter'>('summary')
|
| 47 |
+
|
| 48 |
+
async function fetchAndSummarize() {
|
| 49 |
+
setLoadingHighlights(true)
|
| 50 |
+
try {
|
| 51 |
+
// 1) Aggregate articles (uses selected sources or backend defaults)
|
| 52 |
+
const resAgg = await fetch(`${apiBase}/aggregate`, {
|
| 53 |
+
method: 'POST',
|
| 54 |
+
headers: { 'Content-Type': 'application/json' },
|
| 55 |
+
body: JSON.stringify({ sources })
|
| 56 |
+
})
|
| 57 |
+
const dataAgg = await resAgg.json()
|
| 58 |
+
|
| 59 |
+
// Auto-select all articles by default
|
| 60 |
+
const articleUrls = dataAgg.articles.map((a: any) => a.link)
|
| 61 |
+
|
| 62 |
+
// 2) Try to summarize using the freshly fetched articles
|
| 63 |
+
let summaryText = ''
|
| 64 |
+
try {
|
| 65 |
+
const resSum = await fetch(`${apiBase}/highlights`, {
|
| 66 |
+
method: 'POST',
|
| 67 |
+
headers: { 'Content-Type': 'application/json' },
|
| 68 |
+
body: JSON.stringify({ session_id: sessionId, articles: dataAgg.articles })
|
| 69 |
+
})
|
| 70 |
+
const dataSum = await resSum.json()
|
| 71 |
+
|
| 72 |
+
// Set appropriate message based on whether articles were found
|
| 73 |
+
if (dataAgg.articles && dataAgg.articles.length > 0) {
|
| 74 |
+
summaryText = dataSum.summary_markdown || 'Highlights generated successfully.'
|
| 75 |
+
} else {
|
| 76 |
+
summaryText = 'No articles found for the selected sources in the past 7 days.'
|
| 77 |
+
}
|
| 78 |
+
} catch (summaryError) {
|
| 79 |
+
// If summary fails (e.g., no OpenAI key), still show articles and allow progression
|
| 80 |
+
console.error('Summary generation failed:', summaryError)
|
| 81 |
+
if (dataAgg.articles && dataAgg.articles.length > 0) {
|
| 82 |
+
summaryText = 'Articles fetched successfully. Click "Get Summaries" to process selected articles.'
|
| 83 |
+
} else {
|
| 84 |
+
summaryText = 'No articles found for the selected sources in the past 7 days.'
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// Update all state together after loading is complete
|
| 89 |
+
setLoadingHighlights(false)
|
| 90 |
+
setArticles(dataAgg.articles)
|
| 91 |
+
setSelectedArticles(articleUrls)
|
| 92 |
+
setSummariesMode(false)
|
| 93 |
+
setSummary(summaryText)
|
| 94 |
+
} catch (error) {
|
| 95 |
+
setLoadingHighlights(false)
|
| 96 |
+
console.error('Failed to fetch articles:', error)
|
| 97 |
+
setSummary('Failed to fetch articles. Please try again.')
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
async function getHighlights() {
|
| 102 |
+
// Validate selection limit before making API call
|
| 103 |
+
if (selectedArticles.length > 5) {
|
| 104 |
+
alert('Please select 5 or fewer articles for summarization. You currently have ' + selectedArticles.length + ' articles selected.')
|
| 105 |
+
return
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
setLoadingSummaries(true)
|
| 109 |
+
try {
|
| 110 |
+
// Only scrape selected articles
|
| 111 |
+
const selectedArticleData = articles.filter(a => selectedArticles.includes(a.link))
|
| 112 |
+
|
| 113 |
+
const res = await fetch(`${apiBase}/summaries_selected`, {
|
| 114 |
+
method: 'POST',
|
| 115 |
+
headers: { 'Content-Type': 'application/json' },
|
| 116 |
+
body: JSON.stringify({ articles: selectedArticleData })
|
| 117 |
+
})
|
| 118 |
+
const data = await res.json()
|
| 119 |
+
const items: { title: string, link: string, source?: string, summary: string }[] = data.items || []
|
| 120 |
+
|
| 121 |
+
// Batch all state updates after loading is complete
|
| 122 |
+
setLoadingSummaries(false)
|
| 123 |
+
setHighlights(items)
|
| 124 |
+
setSummariesMode(true)
|
| 125 |
+
setPageIndex(0)
|
| 126 |
+
} catch (error) {
|
| 127 |
+
setLoadingSummaries(false)
|
| 128 |
+
throw error
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
function resetSummaries() {
|
| 133 |
+
setSummariesMode(false)
|
| 134 |
+
setSummary('')
|
| 135 |
+
setArticles([])
|
| 136 |
+
setTweets([])
|
| 137 |
+
setNewsletterHtml('')
|
| 138 |
+
setPageIndex(0)
|
| 139 |
+
setHighlights([])
|
| 140 |
+
setSelectedArticles([])
|
| 141 |
+
setIsPaginated(true) // Reset to default paginated view
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
function toggleArticleSelection(articleUrl: string) {
|
| 145 |
+
setSelectedArticles(prev =>
|
| 146 |
+
prev.includes(articleUrl)
|
| 147 |
+
? prev.filter(url => url !== articleUrl)
|
| 148 |
+
: [...prev, articleUrl]
|
| 149 |
+
)
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
function selectAllArticles() {
|
| 153 |
+
setSelectedArticles(articles.map(a => a.link))
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
function deselectAllArticles() {
|
| 157 |
+
setSelectedArticles([])
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
async function makeTweets() {
|
| 161 |
+
setLoadingTweets(true)
|
| 162 |
+
try {
|
| 163 |
+
const res = await fetch(`${apiBase}/tweets`, {
|
| 164 |
+
method: 'POST',
|
| 165 |
+
headers: { 'Content-Type': 'application/json' },
|
| 166 |
+
body: JSON.stringify({
|
| 167 |
+
session_id: sessionId,
|
| 168 |
+
summaries: highlights // Send highlights instead of summary_markdown
|
| 169 |
+
})
|
| 170 |
+
})
|
| 171 |
+
const data = await res.json()
|
| 172 |
+
|
| 173 |
+
// Batch state updates after loading is complete
|
| 174 |
+
setLoadingTweets(false)
|
| 175 |
+
setTweets(data.tweets)
|
| 176 |
+
} catch (error) {
|
| 177 |
+
setLoadingTweets(false)
|
| 178 |
+
throw error
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
async function makeNewsletter() {
|
| 183 |
+
setLoadingNewsletter(true)
|
| 184 |
+
try {
|
| 185 |
+
const res = await fetch(`${apiBase}/newsletter`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: sessionId, summary_markdown: summary, articles }) })
|
| 186 |
+
const data = await res.json()
|
| 187 |
+
|
| 188 |
+
// Batch state updates after loading is complete
|
| 189 |
+
setLoadingNewsletter(false)
|
| 190 |
+
setNewsletterHtml(data.html)
|
| 191 |
+
} catch (error) {
|
| 192 |
+
setLoadingNewsletter(false)
|
| 193 |
+
throw error
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
function openEditor(target: 'summary' | `tweet-${number}` | 'newsletter') {
|
| 198 |
+
setEditTarget(target)
|
| 199 |
+
if (target === 'summary') {
|
| 200 |
+
setEditorTitle('Edit Summary')
|
| 201 |
+
setEditorText(summary)
|
| 202 |
+
} else if (target.startsWith('tweet-')) {
|
| 203 |
+
const idx = Number(target.split('-')[1])
|
| 204 |
+
setEditorTitle(`Edit Tweet ${idx + 1}`)
|
| 205 |
+
setEditorText(tweets[idx]?.content || '')
|
| 206 |
+
} else {
|
| 207 |
+
setEditorTitle('Edit Newsletter (HTML)')
|
| 208 |
+
setEditorText(newsletterHtml)
|
| 209 |
+
}
|
| 210 |
+
setEditorOpen(true)
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
function openTweetEditor(tweet: {id: string, content: string, summary_title: string, summary_link: string, summary_source: string}) {
|
| 214 |
+
// Close any existing editor first, then open the new one
|
| 215 |
+
if (currentEditingTweetId === tweet.id) {
|
| 216 |
+
setCurrentEditingTweetId(null)
|
| 217 |
+
setPendingTweetUpdate(null)
|
| 218 |
+
} else {
|
| 219 |
+
setCurrentEditingTweetId(tweet.id)
|
| 220 |
+
setPendingTweetUpdate(null)
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
async function sendTweetMessage(message: string) {
|
| 225 |
+
if (!currentEditingTweetId) return
|
| 226 |
+
|
| 227 |
+
const currentTweet = tweets.find(t => t.id === currentEditingTweetId)
|
| 228 |
+
if (!currentTweet) return
|
| 229 |
+
|
| 230 |
+
try {
|
| 231 |
+
const res = await fetch(`${apiBase}/edit_tweet`, {
|
| 232 |
+
method: 'POST',
|
| 233 |
+
headers: { 'Content-Type': 'application/json' },
|
| 234 |
+
body: JSON.stringify({
|
| 235 |
+
session_id: sessionId,
|
| 236 |
+
tweet_id: currentTweet.id,
|
| 237 |
+
current_tweet: currentTweet.content,
|
| 238 |
+
original_summary: highlights.find(h => h.link === currentTweet.summary_link)?.summary || '',
|
| 239 |
+
user_message: message,
|
| 240 |
+
conversation_history: tweetConversations[currentTweet.id] || []
|
| 241 |
+
})
|
| 242 |
+
})
|
| 243 |
+
|
| 244 |
+
const data = await res.json()
|
| 245 |
+
|
| 246 |
+
// Store pending update instead of immediately applying it
|
| 247 |
+
setPendingTweetUpdate(data.new_tweet)
|
| 248 |
+
|
| 249 |
+
// Update conversation history
|
| 250 |
+
setTweetConversations(prev => ({
|
| 251 |
+
...prev,
|
| 252 |
+
[currentTweet.id]: data.conversation_history
|
| 253 |
+
}))
|
| 254 |
+
|
| 255 |
+
return data.ai_response
|
| 256 |
+
} catch (error) {
|
| 257 |
+
console.error('Error editing tweet:', error)
|
| 258 |
+
return 'Sorry, I encountered an error while processing your request.'
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
function acceptTweetUpdate() {
|
| 263 |
+
if (!currentEditingTweetId || !pendingTweetUpdate) return
|
| 264 |
+
|
| 265 |
+
// Update tweet content in main list
|
| 266 |
+
const updatedTweets = tweets.map(tweet =>
|
| 267 |
+
tweet.id === currentEditingTweetId
|
| 268 |
+
? { ...tweet, content: pendingTweetUpdate }
|
| 269 |
+
: tweet
|
| 270 |
+
)
|
| 271 |
+
setTweets(updatedTweets)
|
| 272 |
+
setPendingTweetUpdate(null)
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
function rejectTweetUpdate() {
|
| 276 |
+
setPendingTweetUpdate(null)
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
function onEditorDone(newText: string) {
|
| 280 |
+
if (editTarget === 'summary') setSummary(newText)
|
| 281 |
+
else if (editTarget.startsWith('tweet-')) {
|
| 282 |
+
const idx = Number(editTarget.split('-')[1])
|
| 283 |
+
const next = tweets.slice()
|
| 284 |
+
if (next[idx]) {
|
| 285 |
+
next[idx] = { ...next[idx], content: newText }
|
| 286 |
+
setTweets(next)
|
| 287 |
+
}
|
| 288 |
+
} else setNewsletterHtml(newText)
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
async function download() {
|
| 292 |
+
const res = await fetch(`${apiBase}/download_html`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: sessionId, html: newsletterHtml }) })
|
| 293 |
+
const blob = await res.blob()
|
| 294 |
+
const url = URL.createObjectURL(blob)
|
| 295 |
+
const a = document.createElement('a')
|
| 296 |
+
a.href = url
|
| 297 |
+
a.download = 'ai_weekly.html'
|
| 298 |
+
document.body.appendChild(a)
|
| 299 |
+
a.click()
|
| 300 |
+
URL.revokeObjectURL(url)
|
| 301 |
+
a.remove()
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
return (
|
| 305 |
+
<div className="min-h-screen bg-gradient-to-b from-indigo-50 to-purple-50 text-gray-900">
|
| 306 |
+
<div className="mx-auto max-w-5xl px-4 py-6">
|
| 307 |
+
<header className="mb-6">
|
| 308 |
+
<div className="flex items-center gap-3">
|
| 309 |
+
<img src="/logo.svg" alt="AI Newsletter" className="h-10 w-10" />
|
| 310 |
+
<div>
|
| 311 |
+
<h1 className="text-xl font-semibold bg-gradient-to-r from-indigo-500 to-purple-500 bg-clip-text text-transparent">
|
| 312 |
+
AI Newsletter Generator
|
| 313 |
+
</h1>
|
| 314 |
+
<p className="text-xs text-gray-500">Curate, summarize, and publish—fast.</p>
|
| 315 |
+
</div>
|
| 316 |
+
</div>
|
| 317 |
+
</header>
|
| 318 |
+
<main className="space-y-4">
|
| 319 |
+
|
| 320 |
+
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
| 321 |
+
<div className="md:col-span-1">
|
| 322 |
+
<FeedPicker selected={sources} setSelected={setSources} />
|
| 323 |
+
</div>
|
| 324 |
+
<div className="md:col-span-2 space-y-3">
|
| 325 |
+
<div className="card p-4">
|
| 326 |
+
<div className="mb-2 flex items-center justify-between">
|
| 327 |
+
<h3 className="text-sm font-semibold">Weekly Highlights</h3>
|
| 328 |
+
<div className="flex flex-wrap items-center gap-2">
|
| 329 |
+
{!hasHighlights && !summariesMode && (
|
| 330 |
+
<button className="btn flex items-center gap-2" onClick={fetchAndSummarize} disabled={loadingHighlights}>
|
| 331 |
+
{loadingHighlights && <Spinner />}
|
| 332 |
+
Get Highlights
|
| 333 |
+
</button>
|
| 334 |
+
)}
|
| 335 |
+
{hasHighlights && !summariesMode && (
|
| 336 |
+
<>
|
| 337 |
+
<button className="btn" onClick={resetSummaries}>Reset</button>
|
| 338 |
+
<button
|
| 339 |
+
className="btn flex items-center gap-2"
|
| 340 |
+
onClick={async () => { await getHighlights(); /* switches to summariesMode */ }}
|
| 341 |
+
disabled={loadingSummaries || selectedArticles.length === 0}
|
| 342 |
+
title={selectedArticles.length === 0 ? "Please select at least one article" : `Process ${selectedArticles.length} selected articles`}
|
| 343 |
+
>
|
| 344 |
+
{loadingSummaries && <Spinner />}
|
| 345 |
+
Get Summaries ({selectedArticles.length})
|
| 346 |
+
</button>
|
| 347 |
+
</>
|
| 348 |
+
)}
|
| 349 |
+
{summariesMode && (
|
| 350 |
+
<>
|
| 351 |
+
<button className="btn" onClick={resetSummaries}>Reset</button>
|
| 352 |
+
<label className="flex items-center gap-2 text-sm">
|
| 353 |
+
<input
|
| 354 |
+
type="checkbox"
|
| 355 |
+
checked={isPaginated}
|
| 356 |
+
onChange={(e) => {
|
| 357 |
+
setIsPaginated(e.target.checked)
|
| 358 |
+
setPageIndex(0) // Reset to first page when toggling
|
| 359 |
+
}}
|
| 360 |
+
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
| 361 |
+
/>
|
| 362 |
+
Paginated View
|
| 363 |
+
</label>
|
| 364 |
+
</>
|
| 365 |
+
)}
|
| 366 |
+
</div>
|
| 367 |
+
</div>
|
| 368 |
+
{!summariesMode ? (
|
| 369 |
+
<>
|
| 370 |
+
{!hasHighlights && (
|
| 371 |
+
summary ? (
|
| 372 |
+
<div className="max-h-96 overflow-y-auto">
|
| 373 |
+
<pre className="whitespace-pre-wrap text-sm text-gray-900">{summary}</pre>
|
| 374 |
+
</div>
|
| 375 |
+
) : (
|
| 376 |
+
<div className="text-sm text-gray-500">No highlights yet.</div>
|
| 377 |
+
)
|
| 378 |
+
)}
|
| 379 |
+
|
| 380 |
+
{hasHighlights && articles.length === 0 && (
|
| 381 |
+
<div className="mt-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
|
| 382 |
+
<div className="text-sm text-yellow-800">
|
| 383 |
+
No articles found for the selected sources in the past 7 days.
|
| 384 |
+
</div>
|
| 385 |
+
</div>
|
| 386 |
+
)}
|
| 387 |
+
|
| 388 |
+
{articles.length > 0 && (
|
| 389 |
+
<div className="mt-4 border-t pt-4">
|
| 390 |
+
<div className="flex items-center justify-between mb-3">
|
| 391 |
+
<h4 className="text-sm font-medium text-gray-700">
|
| 392 |
+
Articles Found ({articles.length})
|
| 393 |
+
</h4>
|
| 394 |
+
<div className="flex gap-2">
|
| 395 |
+
<button
|
| 396 |
+
className="text-xs text-blue-600 hover:text-blue-800"
|
| 397 |
+
onClick={selectAllArticles}
|
| 398 |
+
>
|
| 399 |
+
Select All
|
| 400 |
+
</button>
|
| 401 |
+
<button
|
| 402 |
+
className="text-xs text-gray-600 hover:text-gray-800"
|
| 403 |
+
onClick={deselectAllArticles}
|
| 404 |
+
>
|
| 405 |
+
Deselect All
|
| 406 |
+
</button>
|
| 407 |
+
</div>
|
| 408 |
+
</div>
|
| 409 |
+
<div className="max-h-64 overflow-y-auto space-y-2">
|
| 410 |
+
{articles.map((article, i) => (
|
| 411 |
+
<div key={`${article.link}-${i}`} className="flex items-start gap-3 p-3 border rounded-lg hover:bg-gray-50">
|
| 412 |
+
<input
|
| 413 |
+
type="checkbox"
|
| 414 |
+
checked={selectedArticles.includes(article.link)}
|
| 415 |
+
onChange={() => toggleArticleSelection(article.link)}
|
| 416 |
+
className="mt-1 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
| 417 |
+
/>
|
| 418 |
+
<div className="flex-1 min-w-0">
|
| 419 |
+
<a
|
| 420 |
+
href={article.link}
|
| 421 |
+
target="_blank"
|
| 422 |
+
rel="noopener noreferrer"
|
| 423 |
+
className="text-sm font-medium text-blue-600 hover:text-blue-800 block truncate"
|
| 424 |
+
>
|
| 425 |
+
{article.title}
|
| 426 |
+
</a>
|
| 427 |
+
<div className="text-xs text-gray-500 mt-1">
|
| 428 |
+
{article.source} {article.published && `• ${article.published}`}
|
| 429 |
+
</div>
|
| 430 |
+
{article.summary && (
|
| 431 |
+
<div className="text-xs text-gray-600 mt-1 line-clamp-2">
|
| 432 |
+
{article.summary}
|
| 433 |
+
</div>
|
| 434 |
+
)}
|
| 435 |
+
</div>
|
| 436 |
+
</div>
|
| 437 |
+
))}
|
| 438 |
+
</div>
|
| 439 |
+
<div className="mt-3 text-xs text-gray-600">
|
| 440 |
+
{selectedArticles.length} of {articles.length} articles selected
|
| 441 |
+
</div>
|
| 442 |
+
</div>
|
| 443 |
+
)}
|
| 444 |
+
</>
|
| 445 |
+
) : (
|
| 446 |
+
<>
|
| 447 |
+
{isPaginated ? (
|
| 448 |
+
// Paginated view - one summary per page
|
| 449 |
+
<div className="space-y-4">
|
| 450 |
+
{highlights.length > 0 && (
|
| 451 |
+
<div className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
|
| 452 |
+
<div className="flex items-start justify-between mb-2">
|
| 453 |
+
<a
|
| 454 |
+
href={highlights[pageIndex]?.link}
|
| 455 |
+
target="_blank"
|
| 456 |
+
rel="noopener noreferrer"
|
| 457 |
+
className="text-lg font-semibold text-blue-600 hover:text-blue-800 leading-tight"
|
| 458 |
+
>
|
| 459 |
+
{highlights[pageIndex]?.title}
|
| 460 |
+
</a>
|
| 461 |
+
</div>
|
| 462 |
+
<div className="text-sm text-gray-500 mb-3">
|
| 463 |
+
{highlights[pageIndex]?.source}
|
| 464 |
+
</div>
|
| 465 |
+
<div className="prose prose-sm max-w-none">
|
| 466 |
+
<div className="text-gray-700 whitespace-pre-wrap">{highlights[pageIndex]?.summary}</div>
|
| 467 |
+
</div>
|
| 468 |
+
</div>
|
| 469 |
+
)}
|
| 470 |
+
|
| 471 |
+
{/* Pagination controls */}
|
| 472 |
+
{highlights.length > 1 && (
|
| 473 |
+
<div className="flex items-center justify-center gap-4 mt-4">
|
| 474 |
+
<button
|
| 475 |
+
className="px-3 py-1 border rounded text-sm disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
|
| 476 |
+
onClick={() => setPageIndex(Math.max(0, pageIndex - 1))}
|
| 477 |
+
disabled={pageIndex === 0}
|
| 478 |
+
>
|
| 479 |
+
← Previous
|
| 480 |
+
</button>
|
| 481 |
+
<span className="text-sm text-gray-600">
|
| 482 |
+
{pageIndex + 1} of {highlights.length}
|
| 483 |
+
</span>
|
| 484 |
+
<button
|
| 485 |
+
className="px-3 py-1 border rounded text-sm disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
|
| 486 |
+
onClick={() => setPageIndex(Math.min(highlights.length - 1, pageIndex + 1))}
|
| 487 |
+
disabled={pageIndex === highlights.length - 1}
|
| 488 |
+
>
|
| 489 |
+
Next →
|
| 490 |
+
</button>
|
| 491 |
+
</div>
|
| 492 |
+
)}
|
| 493 |
+
</div>
|
| 494 |
+
) : (
|
| 495 |
+
// List view - all summaries at once (original behavior)
|
| 496 |
+
<div className="max-h-96 overflow-y-auto space-y-4">
|
| 497 |
+
{highlights.map((it, i) => (
|
| 498 |
+
<div key={`${it.link}-${i}`} className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
|
| 499 |
+
<div className="flex items-start justify-between mb-2">
|
| 500 |
+
<a
|
| 501 |
+
href={it.link}
|
| 502 |
+
target="_blank"
|
| 503 |
+
rel="noopener noreferrer"
|
| 504 |
+
className="text-lg font-semibold text-blue-600 hover:text-blue-800 leading-tight"
|
| 505 |
+
>
|
| 506 |
+
{it.title}
|
| 507 |
+
</a>
|
| 508 |
+
</div>
|
| 509 |
+
<div className="text-sm text-gray-500 mb-3">
|
| 510 |
+
{it.source}
|
| 511 |
+
</div>
|
| 512 |
+
<div className="prose prose-sm max-w-none">
|
| 513 |
+
<div className="text-gray-700 whitespace-pre-wrap">{it.summary}</div>
|
| 514 |
+
</div>
|
| 515 |
+
</div>
|
| 516 |
+
))}
|
| 517 |
+
<div className="text-center text-sm text-gray-600 pt-2">
|
| 518 |
+
{highlights.length} summaries generated
|
| 519 |
+
</div>
|
| 520 |
+
</div>
|
| 521 |
+
)}
|
| 522 |
+
</>
|
| 523 |
+
)}
|
| 524 |
+
</div>
|
| 525 |
+
<div className="card p-4">
|
| 526 |
+
<div className="mb-2 flex items-center justify-between">
|
| 527 |
+
<h3 className="text-sm font-semibold">X/Tweets</h3>
|
| 528 |
+
<div className="flex gap-2">
|
| 529 |
+
<button className="btn flex items-center gap-2" onClick={makeTweets} disabled={highlights.length === 0 || loadingTweets}>
|
| 530 |
+
{loadingTweets && <Spinner />}
|
| 531 |
+
Generate X/Tweets
|
| 532 |
+
</button>
|
| 533 |
+
</div>
|
| 534 |
+
</div>
|
| 535 |
+
{tweets.length > 0 ? (
|
| 536 |
+
<TweetCards
|
| 537 |
+
tweets={tweets}
|
| 538 |
+
onEdit={(tweet) => openTweetEditor(tweet)}
|
| 539 |
+
currentEditingTweetId={currentEditingTweetId}
|
| 540 |
+
tweetConversations={tweetConversations}
|
| 541 |
+
pendingTweetUpdate={pendingTweetUpdate}
|
| 542 |
+
highlights={highlights}
|
| 543 |
+
onSendMessage={sendTweetMessage}
|
| 544 |
+
onAcceptUpdate={acceptTweetUpdate}
|
| 545 |
+
onRejectUpdate={rejectTweetUpdate}
|
| 546 |
+
onCloseEditor={() => {
|
| 547 |
+
setCurrentEditingTweetId(null)
|
| 548 |
+
setPendingTweetUpdate(null)
|
| 549 |
+
}}
|
| 550 |
+
/>
|
| 551 |
+
) : (
|
| 552 |
+
<div className="text-sm text-gray-500">No tweets yet.</div>
|
| 553 |
+
)}
|
| 554 |
+
</div>
|
| 555 |
+
<div className="card p-4">
|
| 556 |
+
<div className="mb-2 flex items-center justify-between">
|
| 557 |
+
<h3 className="text-sm font-semibold">Newsletter</h3>
|
| 558 |
+
<div className="flex gap-2">
|
| 559 |
+
<button
|
| 560 |
+
className="btn flex items-center gap-2"
|
| 561 |
+
onClick={makeNewsletter}
|
| 562 |
+
disabled={highlights.length === 0 || loadingNewsletter}
|
| 563 |
+
title={highlights.length === 0 ? "Please create summaries first by clicking 'Get Summaries'" : "Generate newsletter from summaries"}
|
| 564 |
+
>
|
| 565 |
+
{loadingNewsletter && <Spinner />}
|
| 566 |
+
Generate
|
| 567 |
+
</button>
|
| 568 |
+
<button className="btn" onClick={() => openEditor('newsletter')} disabled={!newsletterHtml}>Edit with AI</button>
|
| 569 |
+
<button className="btn" onClick={download} disabled={!newsletterHtml}>Download</button>
|
| 570 |
+
</div>
|
| 571 |
+
</div>
|
| 572 |
+
<div className="overflow-hidden rounded-lg border">
|
| 573 |
+
{newsletterHtml ? <iframe srcDoc={newsletterHtml} className="h-[500px] w-full" /> : <div className="p-4 text-sm text-gray-500">No newsletter yet.</div>}
|
| 574 |
+
</div>
|
| 575 |
+
</div>
|
| 576 |
+
</div>
|
| 577 |
+
</div>
|
| 578 |
+
|
| 579 |
+
<EditorModal
|
| 580 |
+
open={editorOpen}
|
| 581 |
+
onClose={() => setEditorOpen(false)}
|
| 582 |
+
onDone={onEditorDone}
|
| 583 |
+
initialText={editorText}
|
| 584 |
+
title={editorTitle}
|
| 585 |
+
sessionId={sessionId}
|
| 586 |
+
apiBase={apiBase}
|
| 587 |
+
/>
|
| 588 |
+
</main>
|
| 589 |
+
</div>
|
| 590 |
+
</div>
|
| 591 |
+
)
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
export default App
|
frontend/src/assets/react.svg
ADDED
|
|
frontend/src/components/EditorModal.tsx
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState } from 'react'
|
| 2 |
+
|
| 3 |
+
interface EditorModalProps {
|
| 4 |
+
open: boolean
|
| 5 |
+
onClose: () => void
|
| 6 |
+
onDone: (text: string) => void
|
| 7 |
+
initialText: string
|
| 8 |
+
title: string
|
| 9 |
+
sessionId: string
|
| 10 |
+
apiBase: string
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
export default function EditorModal({
|
| 14 |
+
open,
|
| 15 |
+
onClose,
|
| 16 |
+
onDone,
|
| 17 |
+
initialText,
|
| 18 |
+
title,
|
| 19 |
+
sessionId: _,
|
| 20 |
+
apiBase: __
|
| 21 |
+
}: EditorModalProps) {
|
| 22 |
+
const [text, setText] = useState(initialText)
|
| 23 |
+
|
| 24 |
+
if (!open) return null
|
| 25 |
+
|
| 26 |
+
return (
|
| 27 |
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
| 28 |
+
<div className="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] flex flex-col">
|
| 29 |
+
<div className="flex items-center justify-between p-4 border-b">
|
| 30 |
+
<h2 className="text-lg font-semibold">{title}</h2>
|
| 31 |
+
<button
|
| 32 |
+
onClick={onClose}
|
| 33 |
+
className="text-gray-500 hover:text-gray-700"
|
| 34 |
+
>
|
| 35 |
+
✕
|
| 36 |
+
</button>
|
| 37 |
+
</div>
|
| 38 |
+
|
| 39 |
+
<div className="flex-1 p-4 overflow-hidden">
|
| 40 |
+
<textarea
|
| 41 |
+
value={text}
|
| 42 |
+
onChange={(e) => setText(e.target.value)}
|
| 43 |
+
className="w-full h-full resize-none border rounded p-3 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
| 44 |
+
placeholder="Enter your text here..."
|
| 45 |
+
/>
|
| 46 |
+
</div>
|
| 47 |
+
|
| 48 |
+
<div className="flex justify-end gap-2 p-4 border-t">
|
| 49 |
+
<button
|
| 50 |
+
onClick={onClose}
|
| 51 |
+
className="px-4 py-2 text-gray-600 border rounded hover:bg-gray-50"
|
| 52 |
+
>
|
| 53 |
+
Cancel
|
| 54 |
+
</button>
|
| 55 |
+
<button
|
| 56 |
+
onClick={() => {
|
| 57 |
+
onDone(text)
|
| 58 |
+
onClose()
|
| 59 |
+
}}
|
| 60 |
+
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
| 61 |
+
>
|
| 62 |
+
Save
|
| 63 |
+
</button>
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
)
|
| 68 |
+
}
|
frontend/src/components/FeedPicker.tsx
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useEffect, useState } from 'react'
|
| 2 |
+
|
| 3 |
+
export default function FeedPicker({
|
| 4 |
+
selected,
|
| 5 |
+
setSelected,
|
| 6 |
+
apiBase = '/api',
|
| 7 |
+
}: {
|
| 8 |
+
selected: string[]
|
| 9 |
+
setSelected: (s: string[]) => void
|
| 10 |
+
apiBase?: string
|
| 11 |
+
}) {
|
| 12 |
+
const [defaults, setDefaults] = useState<Record<string, string>>({})
|
| 13 |
+
// const [custom, setCustom] = useState('') // Temporarily disabled
|
| 14 |
+
|
| 15 |
+
useEffect(() => {
|
| 16 |
+
let cancelled = false
|
| 17 |
+
fetch(`${apiBase}/defaults`).then(r => r.json()).then((data: Record<string, string>) => {
|
| 18 |
+
if (cancelled) return
|
| 19 |
+
setDefaults(data)
|
| 20 |
+
// Default select all if none selected yet
|
| 21 |
+
if (selected.length === 0) {
|
| 22 |
+
const all = Object.values(data)
|
| 23 |
+
setSelected(all)
|
| 24 |
+
}
|
| 25 |
+
}).catch(console.error)
|
| 26 |
+
return () => { cancelled = true }
|
| 27 |
+
}, [apiBase])
|
| 28 |
+
|
| 29 |
+
function toggle(url: string) {
|
| 30 |
+
setSelected(selected.includes(url) ? selected.filter(u => u !== url) : [...selected, url])
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
// function addCustom() { // Temporarily disabled
|
| 34 |
+
// try {
|
| 35 |
+
// const url = new URL(custom).toString()
|
| 36 |
+
// if (!selected.includes(url)) setSelected([...selected, url])
|
| 37 |
+
// setCustom('')
|
| 38 |
+
// } catch { /* ignore invalid */ }
|
| 39 |
+
// }
|
| 40 |
+
|
| 41 |
+
return (
|
| 42 |
+
<div className="card p-4">
|
| 43 |
+
<h3 className="mb-3 text-sm font-semibold">Sources</h3>
|
| 44 |
+
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
| 45 |
+
{Object.entries(defaults).map(([name, url]) => (
|
| 46 |
+
<label key={url} className="flex items-center gap-2">
|
| 47 |
+
<input type="checkbox" checked={selected.includes(url)} onChange={() => toggle(url)} />
|
| 48 |
+
<span className="text-sm">{name}</span>
|
| 49 |
+
</label>
|
| 50 |
+
))}
|
| 51 |
+
</div>
|
| 52 |
+
{/* Custom RSS URL input - temporarily disabled */}
|
| 53 |
+
{/*
|
| 54 |
+
<div className="mt-4 flex gap-2">
|
| 55 |
+
<input
|
| 56 |
+
type="text"
|
| 57 |
+
placeholder="Custom RSS URL"
|
| 58 |
+
className="input flex-1"
|
| 59 |
+
value={custom}
|
| 60 |
+
onChange={e => setCustom(e.target.value)}
|
| 61 |
+
/>
|
| 62 |
+
<button className="btn" onClick={addCustom}>Add</button>
|
| 63 |
+
</div>
|
| 64 |
+
*/}
|
| 65 |
+
</div>
|
| 66 |
+
)
|
| 67 |
+
}
|
frontend/src/components/TweetCards.tsx
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
type Tweet = {
|
| 2 |
+
id: string
|
| 3 |
+
content: string
|
| 4 |
+
summary_title: string
|
| 5 |
+
summary_link: string
|
| 6 |
+
summary_source: string
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
type Highlight = {
|
| 10 |
+
title: string
|
| 11 |
+
link: string
|
| 12 |
+
source?: string
|
| 13 |
+
summary: string
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
export default function TweetCards({
|
| 17 |
+
tweets,
|
| 18 |
+
onEdit,
|
| 19 |
+
currentEditingTweetId,
|
| 20 |
+
tweetConversations,
|
| 21 |
+
pendingTweetUpdate,
|
| 22 |
+
highlights: _highlights,
|
| 23 |
+
onSendMessage,
|
| 24 |
+
onAcceptUpdate,
|
| 25 |
+
onRejectUpdate,
|
| 26 |
+
onCloseEditor
|
| 27 |
+
}: {
|
| 28 |
+
tweets: Tweet[],
|
| 29 |
+
onEdit: (tweet: Tweet) => void,
|
| 30 |
+
currentEditingTweetId: string | null,
|
| 31 |
+
tweetConversations: Record<string, Array<{role: string, content: string}>>,
|
| 32 |
+
pendingTweetUpdate: string | null,
|
| 33 |
+
highlights: Highlight[],
|
| 34 |
+
onSendMessage: (message: string) => Promise<string | undefined>,
|
| 35 |
+
onAcceptUpdate: () => void,
|
| 36 |
+
onRejectUpdate: () => void,
|
| 37 |
+
onCloseEditor: () => void
|
| 38 |
+
}) {
|
| 39 |
+
const XLogo = () => (
|
| 40 |
+
<svg viewBox="0 0 24 24" className="h-5 w-5 fill-current" aria-hidden="true">
|
| 41 |
+
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
|
| 42 |
+
</svg>
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
return (
|
| 46 |
+
<div className="space-y-4">
|
| 47 |
+
{tweets.map((tweet) => (
|
| 48 |
+
<div key={tweet.id} className="bg-white border border-gray-200 rounded-xl p-4 hover:bg-gray-50 transition-colors">
|
| 49 |
+
{/* Header */}
|
| 50 |
+
<div className="flex items-start space-x-3">
|
| 51 |
+
<div className="flex-shrink-0">
|
| 52 |
+
<div className="w-10 h-10 bg-black rounded-full flex items-center justify-center">
|
| 53 |
+
<XLogo />
|
| 54 |
+
</div>
|
| 55 |
+
</div>
|
| 56 |
+
<div className="flex-1 min-w-0">
|
| 57 |
+
<div className="flex items-center space-x-2">
|
| 58 |
+
<div className="text-sm font-bold text-gray-900">AI Newsletter</div>
|
| 59 |
+
<div className="text-sm text-gray-500">@AI_Newsletter</div>
|
| 60 |
+
<div className="text-sm text-gray-500">·</div>
|
| 61 |
+
<div className="text-sm text-gray-500">now</div>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
{/* Tweet Content */}
|
| 65 |
+
<div className="mt-2">
|
| 66 |
+
<div className="text-gray-900 whitespace-pre-wrap break-words">
|
| 67 |
+
{tweet.content}
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
|
| 71 |
+
{/* Source Link */}
|
| 72 |
+
<div className="mt-3 p-3 border border-gray-200 rounded-lg bg-gray-50">
|
| 73 |
+
<div className="text-xs text-gray-500 mb-1">{tweet.summary_source}</div>
|
| 74 |
+
<a
|
| 75 |
+
href={tweet.summary_link}
|
| 76 |
+
target="_blank"
|
| 77 |
+
rel="noopener noreferrer"
|
| 78 |
+
className="text-sm font-medium text-blue-600 hover:text-blue-800 line-clamp-2"
|
| 79 |
+
>
|
| 80 |
+
{tweet.summary_title}
|
| 81 |
+
</a>
|
| 82 |
+
</div>
|
| 83 |
+
|
| 84 |
+
{/* Actions */}
|
| 85 |
+
<div className="mt-4 flex items-center justify-between">
|
| 86 |
+
<div className="flex items-center space-x-6 text-gray-500">
|
| 87 |
+
<button className="flex items-center space-x-2 hover:text-blue-500 transition-colors">
|
| 88 |
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 89 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
| 90 |
+
</svg>
|
| 91 |
+
<span className="text-sm">0</span>
|
| 92 |
+
</button>
|
| 93 |
+
<button className="flex items-center space-x-2 hover:text-green-500 transition-colors">
|
| 94 |
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 95 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" />
|
| 96 |
+
</svg>
|
| 97 |
+
<span className="text-sm">0</span>
|
| 98 |
+
</button>
|
| 99 |
+
<button className="flex items-center space-x-2 hover:text-red-500 transition-colors">
|
| 100 |
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 101 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
|
| 102 |
+
</svg>
|
| 103 |
+
<span className="text-sm">0</span>
|
| 104 |
+
</button>
|
| 105 |
+
<button className="flex items-center space-x-2 hover:text-blue-500 transition-colors">
|
| 106 |
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 107 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z" />
|
| 108 |
+
</svg>
|
| 109 |
+
<span className="text-sm">0</span>
|
| 110 |
+
</button>
|
| 111 |
+
</div>
|
| 112 |
+
{currentEditingTweetId !== tweet.id && (
|
| 113 |
+
<button
|
| 114 |
+
onClick={() => onEdit(tweet)}
|
| 115 |
+
className="px-3 py-1 text-sm bg-blue-500 text-white rounded-full hover:bg-blue-600 transition-colors"
|
| 116 |
+
>
|
| 117 |
+
Edit with AI
|
| 118 |
+
</button>
|
| 119 |
+
)}
|
| 120 |
+
</div>
|
| 121 |
+
</div>
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
{/* Inline Chatbot - Show only for the currently editing tweet */}
|
| 125 |
+
{currentEditingTweetId === tweet.id && (
|
| 126 |
+
<div className="mt-4 border-t border-gray-200 pt-4">
|
| 127 |
+
<div className="bg-gray-50 rounded-lg p-4">
|
| 128 |
+
<div className="flex items-center justify-between mb-3">
|
| 129 |
+
<h4 className="text-sm font-medium text-gray-700">Edit Tweet with AI</h4>
|
| 130 |
+
<button
|
| 131 |
+
onClick={onCloseEditor}
|
| 132 |
+
className="text-gray-500 hover:text-gray-700"
|
| 133 |
+
>
|
| 134 |
+
✕
|
| 135 |
+
</button>
|
| 136 |
+
</div>
|
| 137 |
+
|
| 138 |
+
{/* Current Tweet Preview */}
|
| 139 |
+
<div className="mb-4 p-3 bg-white rounded border">
|
| 140 |
+
<div className="text-xs font-medium text-gray-600 mb-1">Current Tweet:</div>
|
| 141 |
+
<div className="text-sm text-gray-900">{tweet.content}</div>
|
| 142 |
+
<div className="text-xs text-gray-500 mt-1">
|
| 143 |
+
About: {tweet.summary_title}
|
| 144 |
+
</div>
|
| 145 |
+
</div>
|
| 146 |
+
|
| 147 |
+
{/* Conversation History */}
|
| 148 |
+
<div className="max-h-64 overflow-y-auto mb-4">
|
| 149 |
+
<div className="space-y-2">
|
| 150 |
+
{(tweetConversations[tweet.id] || []).map((msg, i) => {
|
| 151 |
+
const isLastMessage = i === (tweetConversations[tweet.id] || []).length - 1
|
| 152 |
+
const isAIMessage = msg.role === 'assistant'
|
| 153 |
+
const hasUpdateInMessage = pendingTweetUpdate && isLastMessage && isAIMessage
|
| 154 |
+
|
| 155 |
+
return (
|
| 156 |
+
<div key={i}>
|
| 157 |
+
<div className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
| 158 |
+
<div className={`max-w-[80%] p-2 rounded text-sm ${
|
| 159 |
+
msg.role === 'user'
|
| 160 |
+
? 'bg-blue-500 text-white'
|
| 161 |
+
: 'bg-white border text-gray-900'
|
| 162 |
+
}`}>
|
| 163 |
+
{msg.content}
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
|
| 167 |
+
{/* Show accept/reject buttons after the last AI message with an update */}
|
| 168 |
+
{hasUpdateInMessage && (
|
| 169 |
+
<div className="mt-2 ml-auto max-w-[80%]">
|
| 170 |
+
<div className="p-3 bg-blue-50 border border-blue-200 rounded">
|
| 171 |
+
<div className="flex items-center justify-between mb-1">
|
| 172 |
+
<div className="text-xs font-medium text-blue-700">Suggested Tweet:</div>
|
| 173 |
+
<div className={`text-xs font-mono ${
|
| 174 |
+
pendingTweetUpdate.length > 280 ? 'text-red-600' :
|
| 175 |
+
pendingTweetUpdate.length > 260 ? 'text-yellow-600' : 'text-green-600'
|
| 176 |
+
}`}>
|
| 177 |
+
{pendingTweetUpdate.length}/280
|
| 178 |
+
</div>
|
| 179 |
+
</div>
|
| 180 |
+
<div className="text-sm text-gray-900 mb-2 p-2 bg-white rounded border">
|
| 181 |
+
{pendingTweetUpdate}
|
| 182 |
+
</div>
|
| 183 |
+
<div className="flex gap-2">
|
| 184 |
+
<button
|
| 185 |
+
onClick={onAcceptUpdate}
|
| 186 |
+
className="px-2 py-1 bg-green-500 text-white text-xs rounded hover:bg-green-600 transition-colors"
|
| 187 |
+
disabled={pendingTweetUpdate.length > 280}
|
| 188 |
+
>
|
| 189 |
+
✓ Accept
|
| 190 |
+
</button>
|
| 191 |
+
<button
|
| 192 |
+
onClick={onRejectUpdate}
|
| 193 |
+
className="px-2 py-1 bg-red-500 text-white text-xs rounded hover:bg-red-600 transition-colors"
|
| 194 |
+
>
|
| 195 |
+
✗ Reject
|
| 196 |
+
</button>
|
| 197 |
+
</div>
|
| 198 |
+
{pendingTweetUpdate.length > 280 && (
|
| 199 |
+
<div className="mt-1 text-xs text-red-600">
|
| 200 |
+
Tweet is too long! Ask the AI to shorten it.
|
| 201 |
+
</div>
|
| 202 |
+
)}
|
| 203 |
+
</div>
|
| 204 |
+
</div>
|
| 205 |
+
)}
|
| 206 |
+
</div>
|
| 207 |
+
)
|
| 208 |
+
})}
|
| 209 |
+
</div>
|
| 210 |
+
</div>
|
| 211 |
+
|
| 212 |
+
{/* Message Input */}
|
| 213 |
+
<form onSubmit={async (e) => {
|
| 214 |
+
e.preventDefault()
|
| 215 |
+
const formData = new FormData(e.target as HTMLFormElement)
|
| 216 |
+
const message = formData.get('message') as string
|
| 217 |
+
if (!message.trim()) return
|
| 218 |
+
|
| 219 |
+
// Clear input
|
| 220 |
+
const form = e.target as HTMLFormElement
|
| 221 |
+
form.reset()
|
| 222 |
+
|
| 223 |
+
// Send message and get AI response
|
| 224 |
+
await onSendMessage(message)
|
| 225 |
+
}}>
|
| 226 |
+
<div className="flex gap-2">
|
| 227 |
+
<input
|
| 228 |
+
name="message"
|
| 229 |
+
type="text"
|
| 230 |
+
placeholder="Tell me how to improve this tweet..."
|
| 231 |
+
className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
|
| 232 |
+
/>
|
| 233 |
+
<button
|
| 234 |
+
type="submit"
|
| 235 |
+
className="px-3 py-2 bg-blue-500 text-white text-sm rounded hover:bg-blue-600 transition-colors"
|
| 236 |
+
>
|
| 237 |
+
Send
|
| 238 |
+
</button>
|
| 239 |
+
</div>
|
| 240 |
+
</form>
|
| 241 |
+
</div>
|
| 242 |
+
</div>
|
| 243 |
+
)}
|
| 244 |
+
</div>
|
| 245 |
+
))}
|
| 246 |
+
</div>
|
| 247 |
+
)
|
| 248 |
+
}
|
frontend/src/index.css
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@tailwind base;
|
| 2 |
+
@tailwind components;
|
| 3 |
+
@tailwind utilities;
|
frontend/src/main.tsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { StrictMode } from 'react'
|
| 2 |
+
import { createRoot } from 'react-dom/client'
|
| 3 |
+
import './index.css'
|
| 4 |
+
import App from './App.tsx'
|
| 5 |
+
|
| 6 |
+
createRoot(document.getElementById('root')!).render(
|
| 7 |
+
<StrictMode>
|
| 8 |
+
<App />
|
| 9 |
+
</StrictMode>,
|
| 10 |
+
)
|
frontend/src/vite-env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
/// <reference types="vite/client" />
|
frontend/tailwind.config.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** @type {import('tailwindcss').Config} */
|
| 2 |
+
export default {
|
| 3 |
+
content: [
|
| 4 |
+
"./index.html",
|
| 5 |
+
"./src/**/*.{js,ts,jsx,tsx}",
|
| 6 |
+
],
|
| 7 |
+
theme: {
|
| 8 |
+
extend: {},
|
| 9 |
+
},
|
| 10 |
+
plugins: [],
|
| 11 |
+
}
|
frontend/tsconfig.app.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
| 4 |
+
"target": "ES2022",
|
| 5 |
+
"useDefineForClassFields": true,
|
| 6 |
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
| 7 |
+
"module": "ESNext",
|
| 8 |
+
"skipLibCheck": true,
|
| 9 |
+
|
| 10 |
+
/* Bundler mode */
|
| 11 |
+
"moduleResolution": "bundler",
|
| 12 |
+
"allowImportingTsExtensions": true,
|
| 13 |
+
"verbatimModuleSyntax": true,
|
| 14 |
+
"moduleDetection": "force",
|
| 15 |
+
"noEmit": true,
|
| 16 |
+
"jsx": "react-jsx",
|
| 17 |
+
|
| 18 |
+
/* Linting */
|
| 19 |
+
"strict": true,
|
| 20 |
+
"noUnusedLocals": true,
|
| 21 |
+
"noUnusedParameters": true,
|
| 22 |
+
"erasableSyntaxOnly": true,
|
| 23 |
+
"noFallthroughCasesInSwitch": true,
|
| 24 |
+
"noUncheckedSideEffectImports": true
|
| 25 |
+
},
|
| 26 |
+
"include": ["src"]
|
| 27 |
+
}
|
frontend/tsconfig.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"files": [],
|
| 3 |
+
"references": [
|
| 4 |
+
{ "path": "./tsconfig.app.json" },
|
| 5 |
+
{ "path": "./tsconfig.node.json" }
|
| 6 |
+
]
|
| 7 |
+
}
|
frontend/tsconfig.node.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
| 4 |
+
"target": "ES2023",
|
| 5 |
+
"lib": ["ES2023"],
|
| 6 |
+
"module": "ESNext",
|
| 7 |
+
"skipLibCheck": true,
|
| 8 |
+
|
| 9 |
+
/* Bundler mode */
|
| 10 |
+
"moduleResolution": "bundler",
|
| 11 |
+
"allowImportingTsExtensions": true,
|
| 12 |
+
"verbatimModuleSyntax": true,
|
| 13 |
+
"moduleDetection": "force",
|
| 14 |
+
"noEmit": true,
|
| 15 |
+
|
| 16 |
+
/* Linting */
|
| 17 |
+
"strict": true,
|
| 18 |
+
"noUnusedLocals": true,
|
| 19 |
+
"noUnusedParameters": true,
|
| 20 |
+
"erasableSyntaxOnly": true,
|
| 21 |
+
"noFallthroughCasesInSwitch": true,
|
| 22 |
+
"noUncheckedSideEffectImports": true
|
| 23 |
+
},
|
| 24 |
+
"include": ["vite.config.ts"]
|
| 25 |
+
}
|
frontend/vite.config.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from 'vite'
|
| 2 |
+
import react from '@vitejs/plugin-react'
|
| 3 |
+
|
| 4 |
+
// https://vite.dev/config/
|
| 5 |
+
export default defineConfig({
|
| 6 |
+
plugins: [react()],
|
| 7 |
+
})
|
pyproject.toml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "ai-newsletter"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "AI Newsletter Generator - Full Stack Application"
|
| 5 |
+
requires-python = ">=3.12"
|
| 6 |
+
dependencies = [
|
| 7 |
+
"fastapi>=0.116.0",
|
| 8 |
+
"uvicorn>=0.35.0",
|
| 9 |
+
"openai>=1.99.0",
|
| 10 |
+
"feedparser>=6.0.11",
|
| 11 |
+
"pydantic>=2.11.0",
|
| 12 |
+
"httpx>=0.28.0",
|
| 13 |
+
"python-dateutil>=2.9.0",
|
| 14 |
+
"python-dotenv>=1.1.0",
|
| 15 |
+
]
|
| 16 |
+
|
| 17 |
+
[tool.uv]
|
| 18 |
+
dev-dependencies = []
|
| 19 |
+
|
| 20 |
+
|
requirements.txt
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file was autogenerated by uv via the following command:
|
| 2 |
+
# uv export --format requirements-txt --no-hashes
|
| 3 |
+
annotated-types==0.7.0
|
| 4 |
+
# via pydantic
|
| 5 |
+
anyio==4.10.0
|
| 6 |
+
# via
|
| 7 |
+
# httpx
|
| 8 |
+
# openai
|
| 9 |
+
# starlette
|
| 10 |
+
certifi==2025.8.3
|
| 11 |
+
# via
|
| 12 |
+
# httpcore
|
| 13 |
+
# httpx
|
| 14 |
+
click==8.2.1
|
| 15 |
+
# via uvicorn
|
| 16 |
+
colorama==0.4.6 ; sys_platform == 'win32'
|
| 17 |
+
# via
|
| 18 |
+
# click
|
| 19 |
+
# tqdm
|
| 20 |
+
distro==1.9.0
|
| 21 |
+
# via openai
|
| 22 |
+
fastapi==0.116.1
|
| 23 |
+
# via ai-newsletter
|
| 24 |
+
feedparser==6.0.11
|
| 25 |
+
# via ai-newsletter
|
| 26 |
+
h11==0.16.0
|
| 27 |
+
# via
|
| 28 |
+
# httpcore
|
| 29 |
+
# uvicorn
|
| 30 |
+
httpcore==1.0.9
|
| 31 |
+
# via httpx
|
| 32 |
+
httpx==0.28.1
|
| 33 |
+
# via
|
| 34 |
+
# ai-newsletter
|
| 35 |
+
# openai
|
| 36 |
+
idna==3.10
|
| 37 |
+
# via
|
| 38 |
+
# anyio
|
| 39 |
+
# httpx
|
| 40 |
+
jiter==0.10.0
|
| 41 |
+
# via openai
|
| 42 |
+
openai==1.99.5
|
| 43 |
+
# via ai-newsletter
|
| 44 |
+
pydantic==2.11.7
|
| 45 |
+
# via
|
| 46 |
+
# ai-newsletter
|
| 47 |
+
# fastapi
|
| 48 |
+
# openai
|
| 49 |
+
pydantic-core==2.33.2
|
| 50 |
+
# via pydantic
|
| 51 |
+
python-dateutil==2.9.0.post0
|
| 52 |
+
# via ai-newsletter
|
| 53 |
+
python-dotenv==1.1.1
|
| 54 |
+
# via ai-newsletter
|
| 55 |
+
sgmllib3k==1.0.0
|
| 56 |
+
# via feedparser
|
| 57 |
+
six==1.17.0
|
| 58 |
+
# via python-dateutil
|
| 59 |
+
sniffio==1.3.1
|
| 60 |
+
# via
|
| 61 |
+
# anyio
|
| 62 |
+
# openai
|
| 63 |
+
starlette==0.47.2
|
| 64 |
+
# via fastapi
|
| 65 |
+
tqdm==4.67.1
|
| 66 |
+
# via openai
|
| 67 |
+
typing-extensions==4.14.1
|
| 68 |
+
# via
|
| 69 |
+
# anyio
|
| 70 |
+
# fastapi
|
| 71 |
+
# openai
|
| 72 |
+
# pydantic
|
| 73 |
+
# pydantic-core
|
| 74 |
+
# starlette
|
| 75 |
+
# typing-inspection
|
| 76 |
+
typing-inspection==0.4.1
|
| 77 |
+
# via pydantic
|
| 78 |
+
uvicorn==0.35.0
|
| 79 |
+
# via ai-newsletter
|
uv.lock
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version = 1
|
| 2 |
+
revision = 1
|
| 3 |
+
requires-python = ">=3.12"
|
| 4 |
+
resolution-markers = [
|
| 5 |
+
"python_full_version >= '3.13'",
|
| 6 |
+
"python_full_version < '3.13'",
|
| 7 |
+
]
|
| 8 |
+
|
| 9 |
+
[[package]]
|
| 10 |
+
name = "ai-newsletter"
|
| 11 |
+
version = "0.1.0"
|
| 12 |
+
source = { virtual = "." }
|
| 13 |
+
dependencies = [
|
| 14 |
+
{ name = "fastapi" },
|
| 15 |
+
{ name = "feedparser" },
|
| 16 |
+
{ name = "httpx" },
|
| 17 |
+
{ name = "openai" },
|
| 18 |
+
{ name = "pydantic" },
|
| 19 |
+
{ name = "python-dateutil" },
|
| 20 |
+
{ name = "python-dotenv" },
|
| 21 |
+
{ name = "uvicorn" },
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
[package.metadata]
|
| 25 |
+
requires-dist = [
|
| 26 |
+
{ name = "fastapi", specifier = ">=0.116.0" },
|
| 27 |
+
{ name = "feedparser", specifier = ">=6.0.11" },
|
| 28 |
+
{ name = "httpx", specifier = ">=0.28.0" },
|
| 29 |
+
{ name = "openai", specifier = ">=1.99.0" },
|
| 30 |
+
{ name = "pydantic", specifier = ">=2.11.0" },
|
| 31 |
+
{ name = "python-dateutil", specifier = ">=2.9.0" },
|
| 32 |
+
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
| 33 |
+
{ name = "uvicorn", specifier = ">=0.35.0" },
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
[package.metadata.requires-dev]
|
| 37 |
+
dev = []
|
| 38 |
+
|
| 39 |
+
[[package]]
|
| 40 |
+
name = "annotated-types"
|
| 41 |
+
version = "0.7.0"
|
| 42 |
+
source = { registry = "https://pypi.org/simple" }
|
| 43 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
|
| 44 |
+
wheels = [
|
| 45 |
+
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
| 46 |
+
]
|
| 47 |
+
|
| 48 |
+
[[package]]
|
| 49 |
+
name = "anyio"
|
| 50 |
+
version = "4.10.0"
|
| 51 |
+
source = { registry = "https://pypi.org/simple" }
|
| 52 |
+
dependencies = [
|
| 53 |
+
{ name = "idna" },
|
| 54 |
+
{ name = "sniffio" },
|
| 55 |
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
| 56 |
+
]
|
| 57 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252 }
|
| 58 |
+
wheels = [
|
| 59 |
+
{ url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213 },
|
| 60 |
+
]
|
| 61 |
+
|
| 62 |
+
[[package]]
|
| 63 |
+
name = "certifi"
|
| 64 |
+
version = "2025.8.3"
|
| 65 |
+
source = { registry = "https://pypi.org/simple" }
|
| 66 |
+
sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386 }
|
| 67 |
+
wheels = [
|
| 68 |
+
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216 },
|
| 69 |
+
]
|
| 70 |
+
|
| 71 |
+
[[package]]
|
| 72 |
+
name = "click"
|
| 73 |
+
version = "8.2.1"
|
| 74 |
+
source = { registry = "https://pypi.org/simple" }
|
| 75 |
+
dependencies = [
|
| 76 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 77 |
+
]
|
| 78 |
+
sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 }
|
| 79 |
+
wheels = [
|
| 80 |
+
{ url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 },
|
| 81 |
+
]
|
| 82 |
+
|
| 83 |
+
[[package]]
|
| 84 |
+
name = "colorama"
|
| 85 |
+
version = "0.4.6"
|
| 86 |
+
source = { registry = "https://pypi.org/simple" }
|
| 87 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
| 88 |
+
wheels = [
|
| 89 |
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
| 90 |
+
]
|
| 91 |
+
|
| 92 |
+
[[package]]
|
| 93 |
+
name = "distro"
|
| 94 |
+
version = "1.9.0"
|
| 95 |
+
source = { registry = "https://pypi.org/simple" }
|
| 96 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 }
|
| 97 |
+
wheels = [
|
| 98 |
+
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 },
|
| 99 |
+
]
|
| 100 |
+
|
| 101 |
+
[[package]]
|
| 102 |
+
name = "fastapi"
|
| 103 |
+
version = "0.116.1"
|
| 104 |
+
source = { registry = "https://pypi.org/simple" }
|
| 105 |
+
dependencies = [
|
| 106 |
+
{ name = "pydantic" },
|
| 107 |
+
{ name = "starlette" },
|
| 108 |
+
{ name = "typing-extensions" },
|
| 109 |
+
]
|
| 110 |
+
sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485 }
|
| 111 |
+
wheels = [
|
| 112 |
+
{ url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631 },
|
| 113 |
+
]
|
| 114 |
+
|
| 115 |
+
[[package]]
|
| 116 |
+
name = "feedparser"
|
| 117 |
+
version = "6.0.11"
|
| 118 |
+
source = { registry = "https://pypi.org/simple" }
|
| 119 |
+
dependencies = [
|
| 120 |
+
{ name = "sgmllib3k" },
|
| 121 |
+
]
|
| 122 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ff/aa/7af346ebeb42a76bf108027fe7f3328bb4e57a3a96e53e21fd9ef9dd6dd0/feedparser-6.0.11.tar.gz", hash = "sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5", size = 286197 }
|
| 123 |
+
wheels = [
|
| 124 |
+
{ url = "https://files.pythonhosted.org/packages/7c/d4/8c31aad9cc18f451c49f7f9cfb5799dadffc88177f7917bc90a66459b1d7/feedparser-6.0.11-py3-none-any.whl", hash = "sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45", size = 81343 },
|
| 125 |
+
]
|
| 126 |
+
|
| 127 |
+
[[package]]
|
| 128 |
+
name = "h11"
|
| 129 |
+
version = "0.16.0"
|
| 130 |
+
source = { registry = "https://pypi.org/simple" }
|
| 131 |
+
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 }
|
| 132 |
+
wheels = [
|
| 133 |
+
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 },
|
| 134 |
+
]
|
| 135 |
+
|
| 136 |
+
[[package]]
|
| 137 |
+
name = "httpcore"
|
| 138 |
+
version = "1.0.9"
|
| 139 |
+
source = { registry = "https://pypi.org/simple" }
|
| 140 |
+
dependencies = [
|
| 141 |
+
{ name = "certifi" },
|
| 142 |
+
{ name = "h11" },
|
| 143 |
+
]
|
| 144 |
+
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 }
|
| 145 |
+
wheels = [
|
| 146 |
+
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 },
|
| 147 |
+
]
|
| 148 |
+
|
| 149 |
+
[[package]]
|
| 150 |
+
name = "httpx"
|
| 151 |
+
version = "0.28.1"
|
| 152 |
+
source = { registry = "https://pypi.org/simple" }
|
| 153 |
+
dependencies = [
|
| 154 |
+
{ name = "anyio" },
|
| 155 |
+
{ name = "certifi" },
|
| 156 |
+
{ name = "httpcore" },
|
| 157 |
+
{ name = "idna" },
|
| 158 |
+
]
|
| 159 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
|
| 160 |
+
wheels = [
|
| 161 |
+
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
|
| 162 |
+
]
|
| 163 |
+
|
| 164 |
+
[[package]]
|
| 165 |
+
name = "idna"
|
| 166 |
+
version = "3.10"
|
| 167 |
+
source = { registry = "https://pypi.org/simple" }
|
| 168 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
| 169 |
+
wheels = [
|
| 170 |
+
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
| 171 |
+
]
|
| 172 |
+
|
| 173 |
+
[[package]]
|
| 174 |
+
name = "jiter"
|
| 175 |
+
version = "0.10.0"
|
| 176 |
+
source = { registry = "https://pypi.org/simple" }
|
| 177 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759 }
|
| 178 |
+
wheels = [
|
| 179 |
+
{ url = "https://files.pythonhosted.org/packages/6d/b5/348b3313c58f5fbfb2194eb4d07e46a35748ba6e5b3b3046143f3040bafa/jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b", size = 312262 },
|
| 180 |
+
{ url = "https://files.pythonhosted.org/packages/9c/4a/6a2397096162b21645162825f058d1709a02965606e537e3304b02742e9b/jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744", size = 320124 },
|
| 181 |
+
{ url = "https://files.pythonhosted.org/packages/2a/85/1ce02cade7516b726dd88f59a4ee46914bf79d1676d1228ef2002ed2f1c9/jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2", size = 345330 },
|
| 182 |
+
{ url = "https://files.pythonhosted.org/packages/75/d0/bb6b4f209a77190ce10ea8d7e50bf3725fc16d3372d0a9f11985a2b23eff/jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026", size = 369670 },
|
| 183 |
+
{ url = "https://files.pythonhosted.org/packages/a0/f5/a61787da9b8847a601e6827fbc42ecb12be2c925ced3252c8ffcb56afcaf/jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c", size = 489057 },
|
| 184 |
+
{ url = "https://files.pythonhosted.org/packages/12/e4/6f906272810a7b21406c760a53aadbe52e99ee070fc5c0cb191e316de30b/jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959", size = 389372 },
|
| 185 |
+
{ url = "https://files.pythonhosted.org/packages/e2/ba/77013b0b8ba904bf3762f11e0129b8928bff7f978a81838dfcc958ad5728/jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a", size = 352038 },
|
| 186 |
+
{ url = "https://files.pythonhosted.org/packages/67/27/c62568e3ccb03368dbcc44a1ef3a423cb86778a4389e995125d3d1aaa0a4/jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95", size = 391538 },
|
| 187 |
+
{ url = "https://files.pythonhosted.org/packages/c0/72/0d6b7e31fc17a8fdce76164884edef0698ba556b8eb0af9546ae1a06b91d/jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea", size = 523557 },
|
| 188 |
+
{ url = "https://files.pythonhosted.org/packages/2f/09/bc1661fbbcbeb6244bd2904ff3a06f340aa77a2b94e5a7373fd165960ea3/jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b", size = 514202 },
|
| 189 |
+
{ url = "https://files.pythonhosted.org/packages/1b/84/5a5d5400e9d4d54b8004c9673bbe4403928a00d28529ff35b19e9d176b19/jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01", size = 211781 },
|
| 190 |
+
{ url = "https://files.pythonhosted.org/packages/9b/52/7ec47455e26f2d6e5f2ea4951a0652c06e5b995c291f723973ae9e724a65/jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49", size = 206176 },
|
| 191 |
+
{ url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617 },
|
| 192 |
+
{ url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947 },
|
| 193 |
+
{ url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618 },
|
| 194 |
+
{ url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829 },
|
| 195 |
+
{ url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034 },
|
| 196 |
+
{ url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529 },
|
| 197 |
+
{ url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671 },
|
| 198 |
+
{ url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864 },
|
| 199 |
+
{ url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989 },
|
| 200 |
+
{ url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495 },
|
| 201 |
+
{ url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289 },
|
| 202 |
+
{ url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074 },
|
| 203 |
+
{ url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225 },
|
| 204 |
+
{ url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235 },
|
| 205 |
+
{ url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278 },
|
| 206 |
+
{ url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866 },
|
| 207 |
+
{ url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772 },
|
| 208 |
+
{ url = "https://files.pythonhosted.org/packages/7b/fb/d302893151caa1c2636d6574d213e4b34e31fd077af6050a9c5cbb42f6fb/jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216", size = 344534 },
|
| 209 |
+
{ url = "https://files.pythonhosted.org/packages/01/d8/5780b64a149d74e347c5128d82176eb1e3241b1391ac07935693466d6219/jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4", size = 369087 },
|
| 210 |
+
{ url = "https://files.pythonhosted.org/packages/e8/5b/f235a1437445160e777544f3ade57544daf96ba7e96c1a5b24a6f7ac7004/jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426", size = 490694 },
|
| 211 |
+
{ url = "https://files.pythonhosted.org/packages/85/a9/9c3d4617caa2ff89cf61b41e83820c27ebb3f7b5fae8a72901e8cd6ff9be/jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12", size = 388992 },
|
| 212 |
+
{ url = "https://files.pythonhosted.org/packages/68/b1/344fd14049ba5c94526540af7eb661871f9c54d5f5601ff41a959b9a0bbd/jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9", size = 351723 },
|
| 213 |
+
{ url = "https://files.pythonhosted.org/packages/41/89/4c0e345041186f82a31aee7b9d4219a910df672b9fef26f129f0cda07a29/jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a", size = 392215 },
|
| 214 |
+
{ url = "https://files.pythonhosted.org/packages/55/58/ee607863e18d3f895feb802154a2177d7e823a7103f000df182e0f718b38/jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853", size = 522762 },
|
| 215 |
+
{ url = "https://files.pythonhosted.org/packages/15/d0/9123fb41825490d16929e73c212de9a42913d68324a8ce3c8476cae7ac9d/jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86", size = 513427 },
|
| 216 |
+
{ url = "https://files.pythonhosted.org/packages/d8/b3/2bd02071c5a2430d0b70403a34411fc519c2f227da7b03da9ba6a956f931/jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357", size = 210127 },
|
| 217 |
+
{ url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527 },
|
| 218 |
+
{ url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213 },
|
| 219 |
+
]
|
| 220 |
+
|
| 221 |
+
[[package]]
|
| 222 |
+
name = "openai"
|
| 223 |
+
version = "1.99.5"
|
| 224 |
+
source = { registry = "https://pypi.org/simple" }
|
| 225 |
+
dependencies = [
|
| 226 |
+
{ name = "anyio" },
|
| 227 |
+
{ name = "distro" },
|
| 228 |
+
{ name = "httpx" },
|
| 229 |
+
{ name = "jiter" },
|
| 230 |
+
{ name = "pydantic" },
|
| 231 |
+
{ name = "sniffio" },
|
| 232 |
+
{ name = "tqdm" },
|
| 233 |
+
{ name = "typing-extensions" },
|
| 234 |
+
]
|
| 235 |
+
sdist = { url = "https://files.pythonhosted.org/packages/2f/4a/16b1b6ee8a62cbfb59057f97f6d9b7bb5ce529047d80bc0b406f65dfdc48/openai-1.99.5.tar.gz", hash = "sha256:aa97ac3326cac7949c5e4ac0274c454c1d19c939760107ae0d3948fc26a924ca", size = 505144 }
|
| 236 |
+
wheels = [
|
| 237 |
+
{ url = "https://files.pythonhosted.org/packages/e6/f2/2472ae020f5156a994710bf926a76915c71bc7b5debf7b81a11506ec8414/openai-1.99.5-py3-none-any.whl", hash = "sha256:4e870f9501b7c36132e2be13313ce3c4d6915a837e7a299c483aab6a6d4412e9", size = 786246 },
|
| 238 |
+
]
|
| 239 |
+
|
| 240 |
+
[[package]]
|
| 241 |
+
name = "pydantic"
|
| 242 |
+
version = "2.11.7"
|
| 243 |
+
source = { registry = "https://pypi.org/simple" }
|
| 244 |
+
dependencies = [
|
| 245 |
+
{ name = "annotated-types" },
|
| 246 |
+
{ name = "pydantic-core" },
|
| 247 |
+
{ name = "typing-extensions" },
|
| 248 |
+
{ name = "typing-inspection" },
|
| 249 |
+
]
|
| 250 |
+
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350 }
|
| 251 |
+
wheels = [
|
| 252 |
+
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782 },
|
| 253 |
+
]
|
| 254 |
+
|
| 255 |
+
[[package]]
|
| 256 |
+
name = "pydantic-core"
|
| 257 |
+
version = "2.33.2"
|
| 258 |
+
source = { registry = "https://pypi.org/simple" }
|
| 259 |
+
dependencies = [
|
| 260 |
+
{ name = "typing-extensions" },
|
| 261 |
+
]
|
| 262 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 }
|
| 263 |
+
wheels = [
|
| 264 |
+
{ url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 },
|
| 265 |
+
{ url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 },
|
| 266 |
+
{ url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 },
|
| 267 |
+
{ url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 },
|
| 268 |
+
{ url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 },
|
| 269 |
+
{ url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 },
|
| 270 |
+
{ url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 },
|
| 271 |
+
{ url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 },
|
| 272 |
+
{ url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 },
|
| 273 |
+
{ url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 },
|
| 274 |
+
{ url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 },
|
| 275 |
+
{ url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 },
|
| 276 |
+
{ url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 },
|
| 277 |
+
{ url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 },
|
| 278 |
+
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 },
|
| 279 |
+
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 },
|
| 280 |
+
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 },
|
| 281 |
+
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 },
|
| 282 |
+
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 },
|
| 283 |
+
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 },
|
| 284 |
+
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 },
|
| 285 |
+
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 },
|
| 286 |
+
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 },
|
| 287 |
+
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 },
|
| 288 |
+
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 },
|
| 289 |
+
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 },
|
| 290 |
+
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 },
|
| 291 |
+
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 },
|
| 292 |
+
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 },
|
| 293 |
+
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 },
|
| 294 |
+
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 },
|
| 295 |
+
]
|
| 296 |
+
|
| 297 |
+
[[package]]
|
| 298 |
+
name = "python-dateutil"
|
| 299 |
+
version = "2.9.0.post0"
|
| 300 |
+
source = { registry = "https://pypi.org/simple" }
|
| 301 |
+
dependencies = [
|
| 302 |
+
{ name = "six" },
|
| 303 |
+
]
|
| 304 |
+
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
|
| 305 |
+
wheels = [
|
| 306 |
+
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
|
| 307 |
+
]
|
| 308 |
+
|
| 309 |
+
[[package]]
|
| 310 |
+
name = "python-dotenv"
|
| 311 |
+
version = "1.1.1"
|
| 312 |
+
source = { registry = "https://pypi.org/simple" }
|
| 313 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978 }
|
| 314 |
+
wheels = [
|
| 315 |
+
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556 },
|
| 316 |
+
]
|
| 317 |
+
|
| 318 |
+
[[package]]
|
| 319 |
+
name = "sgmllib3k"
|
| 320 |
+
version = "1.0.0"
|
| 321 |
+
source = { registry = "https://pypi.org/simple" }
|
| 322 |
+
sdist = { url = "https://files.pythonhosted.org/packages/9e/bd/3704a8c3e0942d711c1299ebf7b9091930adae6675d7c8f476a7ce48653c/sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9", size = 5750 }
|
| 323 |
+
|
| 324 |
+
[[package]]
|
| 325 |
+
name = "six"
|
| 326 |
+
version = "1.17.0"
|
| 327 |
+
source = { registry = "https://pypi.org/simple" }
|
| 328 |
+
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 }
|
| 329 |
+
wheels = [
|
| 330 |
+
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 },
|
| 331 |
+
]
|
| 332 |
+
|
| 333 |
+
[[package]]
|
| 334 |
+
name = "sniffio"
|
| 335 |
+
version = "1.3.1"
|
| 336 |
+
source = { registry = "https://pypi.org/simple" }
|
| 337 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
|
| 338 |
+
wheels = [
|
| 339 |
+
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
|
| 340 |
+
]
|
| 341 |
+
|
| 342 |
+
[[package]]
|
| 343 |
+
name = "starlette"
|
| 344 |
+
version = "0.47.2"
|
| 345 |
+
source = { registry = "https://pypi.org/simple" }
|
| 346 |
+
dependencies = [
|
| 347 |
+
{ name = "anyio" },
|
| 348 |
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
| 349 |
+
]
|
| 350 |
+
sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948 }
|
| 351 |
+
wheels = [
|
| 352 |
+
{ url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984 },
|
| 353 |
+
]
|
| 354 |
+
|
| 355 |
+
[[package]]
|
| 356 |
+
name = "tqdm"
|
| 357 |
+
version = "4.67.1"
|
| 358 |
+
source = { registry = "https://pypi.org/simple" }
|
| 359 |
+
dependencies = [
|
| 360 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 361 |
+
]
|
| 362 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 }
|
| 363 |
+
wheels = [
|
| 364 |
+
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 },
|
| 365 |
+
]
|
| 366 |
+
|
| 367 |
+
[[package]]
|
| 368 |
+
name = "typing-extensions"
|
| 369 |
+
version = "4.14.1"
|
| 370 |
+
source = { registry = "https://pypi.org/simple" }
|
| 371 |
+
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 }
|
| 372 |
+
wheels = [
|
| 373 |
+
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 },
|
| 374 |
+
]
|
| 375 |
+
|
| 376 |
+
[[package]]
|
| 377 |
+
name = "typing-inspection"
|
| 378 |
+
version = "0.4.1"
|
| 379 |
+
source = { registry = "https://pypi.org/simple" }
|
| 380 |
+
dependencies = [
|
| 381 |
+
{ name = "typing-extensions" },
|
| 382 |
+
]
|
| 383 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 }
|
| 384 |
+
wheels = [
|
| 385 |
+
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 },
|
| 386 |
+
]
|
| 387 |
+
|
| 388 |
+
[[package]]
|
| 389 |
+
name = "uvicorn"
|
| 390 |
+
version = "0.35.0"
|
| 391 |
+
source = { registry = "https://pypi.org/simple" }
|
| 392 |
+
dependencies = [
|
| 393 |
+
{ name = "click" },
|
| 394 |
+
{ name = "h11" },
|
| 395 |
+
]
|
| 396 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473 }
|
| 397 |
+
wheels = [
|
| 398 |
+
{ url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406 },
|
| 399 |
+
]
|