diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e879a520697a260e543cf7e27e8cb9c5af33fd29 --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Dependencies +node_modules/ +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +env/ +venv/ +.venv/ + +# Environment variables +.env +server/.env +.env.local +.env.prod +.env.production + +# Build outputs +dist/ +build/ +*.egg-info/ + +# Database +*.db +*.sqlite3 +vector_db/ + +# Logs +*.log +logs/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Cache +.cache/ +.parcel-cache/ +.next/ +.nuxt/ + +# Testing +coverage/ +.nyc_output/ +.pytest_cache/server/models/*.gguf +server/datasets/processed/ +server/models/ +server/vector_db/ +*.db +*.pkl +*.bin diff --git a/CONTACT_TRACKING_GUIDE.md b/CONTACT_TRACKING_GUIDE.md new file mode 100644 index 0000000000000000000000000000000000000000..431d7b2dd8961cfc908872804e66b580bd31c0a6 --- /dev/null +++ b/CONTACT_TRACKING_GUIDE.md @@ -0,0 +1,479 @@ +# Contact Request & IP Tracking Implementation + +## ✅ Features Implemented + +### 1. Contact Request Storage +**File**: `app/models/contact_request.py` + +**What It Stores**: +- ✅ User information (name, email, phone, message) +- ✅ Full chat context (entire conversation) +- ✅ IP address tracking +- ✅ User agent (browser info) +- ✅ Status tracking (pending → contacted → resolved) +- ✅ Timestamps (created, updated, contacted, resolved) + +**Fields**: +```python +class ContactRequest: + name: str + email: str + phone: str + message: str + chat_context: JSON # Full conversation array + ip_address: str + user_agent: str + status: str # pending, contacted, resolved +``` + +--- + +### 2. Chat Session Tracking +**File**: `app/models/chat_session.py` + +**What It Tracks**: +- ✅ Every chat session with unique ID +- ✅ IP address for each user +- ✅ All messages in conversation +- ✅ Session duration and activity +- ✅ Language detected +- ✅ Satisfaction ratings +- ✅ Response times + +**Example**: +```json +{ + "session_id": "abc123", + "ip_address": "192.168.1.1", + "messages": [ + { + "text": "Hey there! 😊", + "isUser": false, + "timestamp": "2025-12-07T03:22:00Z" + }, + { + "text": "how are you", + "isUser": true, + "timestamp": "2025-12-07T03:22:15Z" + } + ], + "message_count": 10, + "is_active": true +} +``` + +--- + +### 3. Contact Form API +**File**: `app/api/contact.py` + +**Endpoints**: + +#### Submit Contact Request +```javascript +POST /api/contact/submit + +// Request +{ + "name": "John Doe", + "email": "john@example.com", + "phone": "+1234567890", + "message": "I need help with...", + "session_id": "abc123", + "chat_context": [ + {"text": "Hey there! 😊", "isUser": false}, + {"text": "how are you", "isUser": true}, + {"text": "I am fine", "isUser": false} + ] +} + +// Response +{ + "id": 42, + "status": "success", + "message": "Thank you! We'll get back to you soon." +} +``` + +#### Get Contact Requests +```javascript +GET /api/contact/requests?website_id=123&status=pending + +// Response +[ + { + "id": 42, + "name": "John Doe", + "email": "john@example.com", + "chat_context": [...], + "ip_address": "192.168.1.1", + "status": "pending", + "created_at": "2025-12-07T03:22:00Z" + } +] +``` + +#### Update Status +```javascript +PATCH /api/contact/request/42/status + +{ + "status": "contacted", + "notes": "Called customer, issue resolved" +} +``` + +--- + +### 4. Session Tracking Service +**File**: `app/services/session_tracker.py` + +**Features**: +- Automatic session creation on first message +- IP address capture from request headers +- Message logging with timestamps +- Session end tracking +- Analytics and reporting + +**Usage Example**: +```python +from app.services.session_tracker import get_session_tracker + +tracker = get_session_tracker(db) + +# Create/get session +session = tracker.get_or_create_session( + session_id="abc123", + website_id=1, + ip_address="192.168.1.1", + user_agent="Mozilla/5.0..." +) + +# Add messages +tracker.add_message( + session_id="abc123", + text="Hello!", + is_user=True, + response_time=1500 # ms +) + +# Get stats +stats = tracker.get_session_stats("abc123") +# { +# "message_count": 10, +# "duration_seconds": 180, +# "ip_address": "192.168.1.1" +# } + +# Find sessions by IP +sessions = tracker.get_sessions_by_ip("192.168.1.1") +``` + +--- + +## 🔧 Integration Guide + +### Step 1: Update Chat API to Track Sessions + +```python +# In your chat endpoint +from app.services.session_tracker import get_session_tracker + +@router.post("/api/chat") +async def chat( + request: Request, + message: str, + session_id: str, + website_id: int, + db: Session = Depends(get_db) +): + # Get IP address + ip_address = request.headers.get("X-Forwarded-For", "").split(",")[0] + if not ip_address: + ip_address = request.client.host + + # Track session + tracker = get_session_tracker(db) + session = tracker.get_or_create_session( + session_id=session_id, + website_id=website_id, + ip_address=ip_address, + user_agent=request.headers.get("User-Agent") + ) + + # Log user message + tracker.add_message( + session_id=session_id, + text=message, + is_user=True + ) + + # Generate bot response + response = await generate_response(message) + + # Log bot message + tracker.add_message( + session_id=session_id, + text=response, + is_user=False, + response_time=1500 + ) + + return {"response": response} +``` + +--- + +### Step 2: Update Frontend Contact Form + +```javascript +// ContactForm.jsx +const handleSubmit = async (formData) => { + try { + // Get full chat context + const chatContext = messages.map(msg => ({ + text: msg.text, + isUser: msg.isUser, + timestamp: msg.timestamp + })); + + // Submit to API + const response = await fetch('/api/contact/submit', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: formData.name, + email: formData.email, + phone: formData.phone, + message: formData.message, + session_id: sessionId, + chat_context: chatContext + }) + }); + + const result = await response.json(); + + if (result.status === 'success') { + // Show success message + setShowThankYou(true); + } + } catch (error) { + console.error('Failed to submit:', error); + } +}; +``` + +--- + +### Step 3: Database Migration + +```bash +# Create migration +cd server +alembic revision -m "add_contact_and_session_tracking" + +# Apply migration +alembic upgrade head +``` + +**Migration Example**: +```python +# migrations/versions/xxx_add_contact_and_session_tracking.py + +def upgrade(): + # Create contact_requests table + op.create_table('contact_requests', + sa.Column('id', sa.Integer(), primary_key=True), + sa.Column('website_id', sa.Integer(), nullable=False), + sa.Column('session_id', sa.String(255), nullable=False), + sa.Column('name', sa.String(255)), + sa.Column('email', sa.String(255)), + sa.Column('phone', sa.String(50)), + sa.Column('message', sa.Text()), + sa.Column('ip_address', sa.String(50)), + sa.Column('user_agent', sa.Text()), + sa.Column('chat_context', sa.JSON()), + sa.Column('status', sa.String(50), default='pending'), + sa.Column('created_at', sa.DateTime(), default=datetime.utcnow) + ) + + # Create chat_sessions table + op.create_table('chat_sessions', + sa.Column('id', sa.Integer(), primary_key=True), + sa.Column('session_id', sa.String(255), unique=True), + sa.Column('website_id', sa.Integer(), nullable=False), + sa.Column('ip_address', sa.String(50)), + sa.Column('messages', sa.JSON()), + sa.Column('is_active', sa.Boolean(), default=True), + sa.Column('started_at', sa.DateTime(), default=datetime.utcnow) + ) +``` + +--- + +## 📊 Admin Dashboard Views + +### Contact Requests View + +```jsx +// ContactRequestsPage.jsx +const ContactRequests = () => { + const [requests, setRequests] = useState([]); + + useEffect(() => { + fetch(`/api/contact/requests?website_id=${websiteId}`) + .then(res => res.json()) + .then(data => setRequests(data)); + }, []); + + return ( +
+

Contact Requests

+ + + + + + + + + + + + + + {requests.map(req => ( + + + + + + + + + + ))} + +
DateNameEmailIP AddressMessagesStatusActions
{new Date(req.created_at).toLocaleString()}{req.name}{req.email}{req.ip_address}{req.chat_context?.length} msgs + + {req.status} + + + +
+
+ ); +}; +``` + +### Session Analytics View + +```jsx +// SessionAnalytics.jsx +const SessionAnalytics = () => { + return ( +
+

Chat Session Analytics

+ +
+
+

Active Sessions

+

42

+
+ +
+

Unique IPs (Today)

+

127

+
+ +
+

Avg. Messages/Session

+

8.5

+
+ +
+

Contact Conversion

+

12%

+
+
+ +
+

Sessions by Location (IP-based)

+ {/* Map showing user locations */} +
+
+ ); +}; +``` + +--- + +## 🔐 Privacy & GDPR Compliance + +### IP Address Handling + +**Storage**: +- ✅ IP addresses stored for analytics +- ✅ Can be anonymized (last octet) +- ✅ Auto-deletion after 90 days + +**Anonymization** (optional): +```python +def anonymize_ip(ip_address: str) -> str: + """Anonymize IP by removing last octet""" + parts = ip_address.split('.') + if len(parts) == 4: + parts[-1] = '0' + return '.'.join(parts) + # 192.168.1.123 → 192.168.1.0 +``` + +**GDPR Compliance**: +- ✅ User can request data deletion +- ✅ Privacy policy disclosure +- ✅ Data retention policies +- ✅ Opt-out mechanism + +--- + +## 📈 Benefits + +### For Business +- ✅ Track all contact requests in one place +- ✅ See full conversation context +- ✅ Identify returning visitors by IP +- ✅ Analyze chat effectiveness +- ✅ Spot patterns and trends + +### For Support +- ✅ Full chat history before contact +- ✅ Know what user already tried +- ✅ Better context for responses +- ✅ Track resolution status + +### For Analytics +- ✅ Session duration tracking +- ✅ Message count analysis +- ✅ Geographic distribution (IP-based) +- ✅ Conversion rate tracking +- ✅ Response time metrics + +--- + +## ✅ Summary + +**What You Get**: +1. ✅ Proper contact request storage (not just JSON) +2. ✅ Full chat context saved with each request +3. ✅ IP tracking for every session +4. ✅ Complete conversation history +5. ✅ Admin dashboard to manage requests +6. ✅ Analytics and reporting + +**Before**: Contact request just shows JSON ❌ +**After**: Properly stored in database with full context ✅ + +**Before**: No user tracking ❌ +**After**: Every session tracked by IP ✅ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2caafdb3ee929e0025a191b0e6f27974083d0723 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + gcc \ + g++ \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements first for better caching +COPY server/requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY server/ . + +# Create non-root user +RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app +USER appuser + +# Expose port for Hugging Face Spaces +EXPOSE 7860 + +# Health check +HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:7860/health || exit 1 + +# Run the application +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"] diff --git a/HEALTHCARE_FLOW.md b/HEALTHCARE_FLOW.md new file mode 100644 index 0000000000000000000000000000000000000000..d2cfa37c2d3ddf4ac42b1904bd0e86261b6de29b --- /dev/null +++ b/HEALTHCARE_FLOW.md @@ -0,0 +1,110 @@ +# Healthcare Module: System Flow & Intelligence + +## System Architecture +The healthcare module is fully integrated into the core AI engine and utilizes internal knowledge bases as well as external datasets (SymCAT & MedQuAD). The system is designed to operate efficiently with offline-preprocessed datasets to ensure fast, safe, and accurate responses. + +```mermaid +graph TD + User([User Query]) --> API[Chat API /api/chat] + API --> Website[Fetch Website & Industry Config] + Website --> AI_Engine[AIEngine.generate_response] + AI_Engine --> IndustryCheck{Is Healthcare?} + + IndustryCheck -- Yes --> IndustryAI[IndustryAI.get_industry_dataset_response] + IndustryAI --> HealthcareMod[EnhancedHealthcareModule] + + HealthcareMod --> SymptomMatch{Symptom Match?} + SymptomMatch -- Yes --> SymCAT[Check SymCAT & Internal DB (Offline Indexed)] + SymCAT --> Urgency[Determine Urgency & Advice] + + SymptomMatch -- No --> MedQuAD[Medical Q&A / MedQuAD (Offline Vector Search)] + MedQuAD --> QAResponse[Generate Q&A Response] + + Urgency --> Format[Conversational Formatting] + QAResponse --> Format + + Format --> Language{Language Selection?} + Language -- English --> EN[English Response] + Language -- Urdu --> UR[Urdu Response (Roman Urdu Supported)] + + EN --> Final[Final Response + Disclaimer] + UR --> Final + + Final --> API_Return[Return to Widget] +``` + +## Key Components + +### 1. Configuration (widget_config) +Healthcare-specific settings are stored in the `websites` table under `widget_config`. These settings customize the AI’s behavior per clinic or sub-industry. +- **Healthcare Sub-Industry**: (General, Dental, Pharmacy, Mental Health, Pediatrics). Tailors AI persona, tone, and keyword detection. +- **Multi-language Support**: English & Urdu response generation. Roman Urdu detection (e.g., "bukhar", "seenay mein dard"). +- **Symptom Checker Toggle**: Enables/disables detailed symptom extraction & analysis logic. +- **Custom Disclaimer**: Allows clinic-specific legal text to appear in the final response. +- **Appointment URL**: Direct link to booking services. + +### 2. Symptom Extraction & Normalization +- **Symptom Extraction**: Extracts explicit and implicit symptoms from user queries. +- **Normalization**: Maps colloquial or variant terms (aliases) to canonical medical terms using Internal KB + SymCAT aliases. +- **Offline Processing**: Dataset preprocessing ensures fast runtime lookups. Embeddings and indexes are generated offline for vector-based semantic search. +- **Example**: "my head hurts" → normalized → "HEADACHE" + +### 3. Confidence & Threshold Logic +- **Weighted Confidence Scoring**: Combines Internal KB, SymCAT, and MedQuAD data to produce a confidence score for detected symptoms. +- **Threshold Check**: If confidence ≥ threshold → proceed with detailed medical reasoning. If confidence < threshold → AI asks a clarifying question to improve accuracy. + +### 4. Dataset Usage +| Dataset | Role | Processing | +| :--- | :--- | :--- | +| **SymCAT** | Symptom → Condition mapping | Preprocessed offline, indexed for fast runtime lookup | +| **MedQuAD** | Medical Q&A | Preprocessed offline, vectorized for semantic search | +| **Internal KB** | Custom terms & aliases | Merged offline with SymCAT for normalization | + +*Note: The module does not query raw datasets at runtime, ensuring speed, reliability, and free-tier compatibility.* + +### 5. Pattern & Red-Flag Detection +- Detects critical symptom combinations (e.g., chest pain + shortness of breath). +- Assigns severity levels: Mild, Moderate, High. +- Triggers urgent advice if high-risk patterns are detected. Rules are offline-defined and applied at runtime. + +### 6. Urgency & Advice Logic +- **Severity categorization** uses weighted rules + pattern detection. +- **Urgent Cases**: e.g., high fever ≥103°F, chest pain, or other red-flag symptoms. +- **Tiered Responses**: + - **Low**: Informational / lifestyle advice + - **Medium**: Suggest consulting a provider + - **High**: Urgent recommendation + emergency contact + +### 7. Multi-Language Intelligence +- Supports English and Urdu. +- Automatically detects language intent and applies translation or Roman Urdu normalization. +- Ensures all symptom and advice terminology is consistent across languages. + +### 8. Answer Generation & Safety Filter +- Combines dataset outputs, pattern recognition, and urgency rules. +- **Safety filter ensures**: + - No medical diagnosis + - No drug or dosage recommendations + - Disclaimer is always included +- Generates confidence-aware, conversationally formatted responses in the requested language. + +## Usage Example +**Query**: "my child has a temperature of 103" +1. **IndustryAI** detects “fever” and extracts temperature “103”. +2. **HealthcareModule** normalizes the symptom and calculates severity → **High Fever**. +3. **Red-Flag Detection** checks for pediatric-specific concerns. +4. **MedQuAD** retrieves explanation if user asks for details. +5. **Answer Generator** creates a response with: + - Child-focused tone (Pediatrics) + - Severity & potential causes + - Tiered advice & safety disclaimer + - Multi-language handling (English or Urdu) +6. **Final response** is returned to the widget. + +## ✅ Key Notes / Enhancements Applied +- Offline dataset usage clarified (preprocessing + vectorization). +- Confidence scoring & threshold logic explicitly described. +- Red-flag & severity rules highlighted. +- Multi-language intelligence detailed, including Roman Urdu support. +- Safety filters and disclaimers emphasized. +- Workflow mirrors your enhanced runtime + offline flow. diff --git a/HEALTHCARE_IMPLEMENTATION_PLAN.md b/HEALTHCARE_IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000000000000000000000000000000000000..c07fbe7470eb59ac08dd5095e6933b9cce24114c --- /dev/null +++ b/HEALTHCARE_IMPLEMENTATION_PLAN.md @@ -0,0 +1,42 @@ +# 🏥 Healthcare Chatbot – Implementation Flow (Dynamic & CPU-Optimized) + +## High-Level Runtime Flow +1. **Message Intake**: Socket.IO/API -> `chat_messages` -> `chat_sessions` +2. **Context Resolution**: `context_service.py` (Patient context + session memory) +3. **Medical Intent Detection**: `intent_classifier.py` (Symptom, Info, Safety, Casual, Non-medical) +4. **Dynamic Risk Scoring**: `risk_engine.py` (Score 0.0-1.0) -> Updates patient context +5. **Dataset Routing**: `dataset_router.py` (Weighted routing, e.g. SymCAT/MedQuAD) +6. **Hybrid Retrieval**: `retrieval_engine.py` (BM25 + FAISS + Cached Embeddings) +7. **Safety & Compliance**: `safety_guard.py` (No diagnosis, Prescriptions, Dosage; Add Disclaimers) +8. **Response Composition**: `response_composer.py` (Template vs LLM) +9. **Delivery**: Socket.IO -> storage -> feedback loop + +## Component Mapping +| Existing Component | New Role | +|-------------------|----------| +| `ai_engine.py` | Medical Orchestrator Wrapper | +| `FAISS` | Primary Medical Retrieval | +| `BM25` | Clinical Term Matching | +| `Unanswered Questions` | Human-in-the-loop | +| `Gemini` | Last-resort Synthesis | + +## Optimization +- **CPU-Only**: No heavy local LLMs (use APIs or efficient retrieval) +- **Async**: Non-blocking calls +- **Caching**: Embeddings and frequent responses + +## 📊 Dataset & Knowledge Strategy +| Dataset | Purpose | Notes | +|---------|---------|-------| +| **MedQuAD** | Medical Q&A pairs | Covers diseases like flu, diabetes, cardiovascular; used for evidence-based responses | +| **SymCAT** | Symptom-to-Disease mapping | Helps chatbot compute risk score, suggest next questions, and verify symptom consistency | +| **HealthTap** | Professional health advice | Used to improve natural phrasing and context-aware responses | +| **WHO / CDC** | Guidelines & Escalation | Provides authoritative references for high-risk symptoms and emergency protocols | +| **ICD-10 Codes** | Disease Taxonomy | Standardized identifiers for internal mapping & consistent terminology | + +## Next Steps +1. [ ] Implement `medical_orchestrator.py` (Core Brain) +2. [ ] Implement `risk_engine.py` with dynamic formulas +3. [ ] Add `patient_context` DB migration +4. [ ] Create healthcare-safe response templates +5. [ ] Build symptom stepper UI diff --git a/IMPLEMENTATION_STATUS.md b/IMPLEMENTATION_STATUS.md new file mode 100644 index 0000000000000000000000000000000000000000..e4d1df6c8c06c331af2fe075b01dbe8c8d94cda7 --- /dev/null +++ b/IMPLEMENTATION_STATUS.md @@ -0,0 +1,613 @@ +# 🎯 Implementation Status & Technical Documentation + +## ✅ **COMPLETED FEATURES** + +### 🖥️ Frontend Layer + +#### **React + Vite Setup** +- **Technology**: React 18 with Vite for fast development +- **How it works**: Vite provides instant HMR (Hot Module Replacement) and optimized builds +- **Entry point**: `client/src/main.jsx` renders the root `App` component +- **Build**: `npm run build` creates optimized production bundle in `client/dist/` + +#### **Tailwind CSS Styling** +- **Configuration**: `client/tailwind.config.js` with custom color palette +- **Custom Colors**: + - `primary` (blue): Used for CTAs, links, and highlights + - `secondary` (slate): Used for text, backgrounds, and borders +- **Custom Utilities**: Glass effects, card hover animations defined in `client/src/index.css` +- **Font**: Inter from Google Fonts for modern typography + +#### **Framer Motion Animations** +- **Purpose**: Smooth page transitions and micro-interactions +- **Usage**: `AnimatePresence` for enter/exit animations, `motion` components for interactive elements +- **Examples**: + - Fade-in animations on page load + - Slide-up effects for cards + - Smooth transitions between tabs + +#### **Axios HTTP Client** +- **Configuration**: `client/src/api/axiosConfig.js` with base URL and interceptors +- **Features**: + - Automatic cookie inclusion (`withCredentials: true`) + - Request/response interceptors for error handling + - Base URL configuration for API endpoints +- **Usage**: All API calls use this configured instance + +#### **React Router Navigation** +- **Setup**: `client/src/App.jsx` defines all routes +- **Protected Routes**: Wrapped with `ProtectedRoute` component that checks authentication +- **Public Routes**: Login and Register pages accessible without auth +- **Layout**: All protected routes wrapped in `Layout` component with sidebar navigation + +#### **JWT Authentication** +- **Flow**: + 1. User logs in via `/api/auth/login` + 2. Server sets httpOnly cookie with JWT token + 3. Cookie automatically sent with all subsequent requests + 4. `AuthContext` manages auth state on client +- **Security**: httpOnly cookies prevent XSS attacks +- **Token Storage**: Stored in secure httpOnly cookie, not localStorage + +#### **Dashboard** +- **Location**: `client/src/pages/Dashboard.jsx` +- **Features**: + - Stats cards showing total chats, active sessions, response rate + - Recent activity feed + - Quick actions for common tasks +- **Data**: Fetches real-time statistics from `/api/analytics/stats` + +#### **Chat Widget** +- **Embeddable Script**: `client/public/chat-widget.js` +- **How to Use**: + ```html + + ``` +- **Features**: + - Floating chat button (customizable position) + - Real-time messaging + - Visitor information collection + - Auto-responses from AI +- **Customization**: Widget config stored in `websites.widget_config` (colors, position, size) + +--- + +### ⚙️ Backend Layer + +#### **FastAPI Framework** +- **Entry Point**: `server/app/main.py` +- **Features**: + - Automatic OpenAPI documentation at `/docs` + - Async request handling + - Built-in validation with Pydantic +- **Startup**: `uvicorn app.main:app --reload --host 0.0.0.0 --port 8000` + +#### **SQLAlchemy ORM** +- **Configuration**: `server/app/core/database.py` +- **Models Location**: `server/app/models/` +- **How it works**: + - Declarative base class for all models + - Session management via dependency injection + - Automatic table creation on startup +- **Database URL**: Configured in `.env` as `DATABASE_URL` + +#### **JWT Authentication** +- **Implementation**: `server/app/core/security.py` +- **Token Creation**: + ```python + create_access_token(data={"sub": user_id}, expires_delta=timedelta(days=7)) + ``` +- **Token Verification**: `verify_token(request)` extracts and validates JWT +- **Password Hashing**: bcrypt via passlib (`get_password_hash`, `verify_password`) +- **Security**: Tokens expire after 7 days, stored in httpOnly cookies + +#### **Password Hashing** +- **Library**: bcrypt 4.1.2 via passlib +- **Algorithm**: bcrypt with automatic salt generation +- **Functions**: + - `get_password_hash(password)`: Hash plain password + - `verify_password(plain, hashed)`: Verify password against hash +- **Security**: 72-byte limit, resistant to rainbow table attacks + +#### **CORS Middleware** +- **Configuration**: `server/app/main.py` +- **Allowed Origins**: Configured in `settings.ALLOWED_ORIGINS` +- **Credentials**: `allow_credentials=True` for cookie support +- **Methods**: All HTTP methods allowed +- **Headers**: All headers allowed for flexibility + +#### **Pydantic Validation** +- **Purpose**: Automatic request/response validation +- **Usage**: All API endpoints use Pydantic models +- **Example**: + ```python + class UserLogin(BaseModel): + email: str + password: str + ``` +- **Benefits**: Type safety, automatic docs, validation errors + +--- + +### 🗄️ Database Schema + +#### **Users Table** +- **Columns**: + - `id`: Primary key + - `email`: Unique, indexed + - `name`: User's full name + - `hashed_password`: bcrypt hash + - `is_active`: Boolean flag + - `created_at`: Timestamp +- **Relationships**: One-to-many with `websites` + +#### **Websites Table** +- **Columns**: + - `id`: Primary key + - `url`: Website URL + - `name`: Display name + - `industry`: Business category + - `tone`: Chat tone (friendly/professional/technical) + - `is_verified`: Verification status + - `owner_id`: Foreign key to users + - `widget_config`: JSON with theme settings + - `created_at`, `last_scraped`: Timestamps +- **Relationships**: + - Belongs to `user` + - Has many `website_content`, `unanswered_questions` + +#### **Website_content Table** +- **Columns**: + - `id`: Primary key + - `website_id`: Foreign key + - `page_url`: Source URL + - `content`: Extracted text + - `embedding`: JSON string of vector + - `created_at`: Timestamp +- **Purpose**: Stores scraped content and embeddings for similarity search + +#### **Chat_sessions Table** +- **Columns**: + - `id`: Primary key + - `website_id`: Foreign key + - `visitor_name`, `visitor_email`: Contact info + - `is_active`: Session status + - `needs_attention`: Flag for owner review + - `created_at`, `last_message_at`: Timestamps +- **Purpose**: Track individual chat conversations + +#### **Unanswered_questions Table** +- **Columns**: + - `id`: Primary key + - `website_id`: Foreign key + - `question`: User's question + - `session_id`: Related chat session + - `confidence_score`: AI confidence (0-1) + - `ai_response`: What AI attempted to answer + - `is_resolved`: Resolution status + - `manual_answer`: Admin's custom response + - `created_at`, `resolved_at`: Timestamps +- **Purpose**: Track questions AI couldn't answer confidently + +#### **Vector Storage (FAISS)** +- **Location**: `server/vector_db/` directory +- **How it works**: + 1. Content is converted to embeddings (vectors) + 2. FAISS index stores vectors for fast similarity search + 3. Query embedding compared to stored vectors + 4. Most similar content retrieved +- **Persistence**: Index saved to disk, loaded on startup + +--- + +### 🤖 AI Engine + +#### **Rule-based AI** +- **Location**: `server/app/services/ai_engine.py` +- **How it works**: + 1. Extract keywords from user question + 2. Search for similar content in vector database + 3. Generate response based on matched content + 4. Apply tone/personality based on website settings +- **Fallback**: If confidence < 0.3, escalate to owner + +#### **Embeddings Generation** +- **Method**: Semantic Vector Embeddings +- **Model**: `all-MiniLM-L6-v2` via Sentence-Transformers +- **Process**: + 1. Text is preprocessed and tokenized + 2. 384-dimensional dense vectors are generated + 3. Vectors capture semantic meaning, not just keyword frequency +- **Storage**: Vectors stored in FAISS (IndexHNSWFlat) and cached in `embeddings_cache.pkl` + +#### **Hybrid Retrieval Architecture** +- **Algorithm**: BM25 + FAISS (Semantic) + Rule-based Boosting +- **Process**: + 1. **Keyword Match**: BM25Okapi calculates term-frequency relevance (modern TF-IDF successor) + 2. **Semantic Match**: FAISS performs ultra-fast HNSW similarity search on dense vectors + 3. **Re-Ranking**: Cross-Encoders (`ms-marco-MiniLM-L-6-v2`) re-evaluate top candidates for precision + 4. **Score Fusion**: Weights (e.g., 60% Semantic / 25% Keyword / 15% Rules) combine scores for final ranking +- **Threshold**: Confidence scores dynamically adjusted based on query intent and industry matching + +#### **Context-aware Responses** +- **Implementation**: Combines multiple relevant content pieces +- **Process**: + 1. Find top 3 similar content pieces + 2. Combine context + 3. Generate coherent response + 4. Apply website tone +- **Personalization**: Uses website industry and tone settings + +#### **Owner Escalation Logic** +- **Trigger**: Confidence score < 0.3 +- **Actions**: + 1. Mark session as `needs_attention` + 2. Create `unanswered_question` record + 3. Send email notification to owner + 4. Provide fallback response to visitor +- **Dashboard**: Owner sees flagged questions in Unanswered Questions page + +#### **Phase 3: Persistent Memory Architecture** 🧠 +- **Database Models**: + - **LeadProfile** (`server/app/models/lead.py`): + - Stores cross-session patient data per email + - Fields: `health_summary`, `known_conditions`, `total_sessions`, `last_interaction` + - One-to-many relationship with `ChatSession` via email + - **SessionSummary** (`server/app/models/chat_session.py`): + - AI-generated summary for each session + - Fields: `summary_text`, `extracted_symptoms`, `triage_result`, `recommended_actions` + - One-to-one relationship with `ChatSession` + +- **Session Analyzer** (`server/app/services/session_analyzer.py`): + - **Trigger**: Background task every 5 messages or on escalation + - **Process**: + 1. Fetches complete session conversation + 2. Calls Gemini with structured prompt to extract JSON + 3. Creates/updates `SessionSummary` record + 4. Syncs insights to persistent `LeadProfile` + 5. Merges unique symptoms into `known_conditions` + - **Smart Storage**: Keeps last 10 session summaries to prevent database bloat + +- **Memory-Aware Response Flow**: + 1. **AIEngine** (`server/app/services/ai_engine.py`): + - Loads `LeadProfile` by visitor_email + - Passes `persistent_history` to `MedicalOrchestrator` + 2. **MedicalOrchestrator** (`server/app/services/medical_orchestrator.py`): + - `_rebuild_context()` seeds `PatientContext` from persistent history + - Extracts age via regex: `Age:\s?(\d+)` or `(\d+)\s?-year-old` + - Pre-populates historical conditions into context + 3. **Result**: Returning patients skip redundant demographic questions + +- **Admin CRM API** (`server/app/api/leads.py`): + - `GET /api/leads/profiles`: List all patient profiles with summaries + - `GET /api/leads/profile/{email}`: Detailed timeline with per-session summaries + - **Use Case**: Admin reviews patient journey before manual intervention + +#### **Chat Dataset & Knowledge Bases** +- **Dataset Name**: **General Conversational Dataset** (Custom & Multi-tone) +- **Sourced Data**: Custom dataset (`chat_dataset.json`) containing common conversational patterns, greetings, and fallback responses. +- **Advanced Training**: `advanced_chat_dataset.py` includes industry-specific scenarios (E-commerce, Healthcare, Real Estate, SaaS) and multi-turn conversation flows. +- **Specialized Knowledge Bases** (Integrated): + - **CLINC150**: 22,500 training examples for intent classification (150 categories). +#### **Medical & Specialized Knowledge Bases** +- **Mega Dataset**: 12,465+ high-quality medical records from curated sources: + - **MedQuAD** (2,572 Q&A): Official NIH/NLM information on 2,500+ conditions. + - **HealthTap / QuestionDoctor** (5,679 Q&A): Professional doctor consultations. + - **PubMedQA** (1,000 Q&A): Evidence-based research summaries. + - **iCliniq & eHealthForum** (630+ Q&A): Community-driven professional advice. +- **SymCAT**: 1,000+ symptom-to-disease mappings for diagnostic logic. +- **CLINC150**: 22,500 training examples for intent classification (150 categories). +- **Roman Urdu Corpus**: 20 pairs for English-Urdu bilingual support. +- **Usage**: Hybrid retrieval across all sources with dynamic confidence thresholds. + +--- + +### 🌐 Web Scraping + +#### **BeautifulSoup HTML Parsing** +- **Location**: `server/app/services/scraper.py` +- **Process**: + 1. Fetch HTML with `requests` library + 2. Parse with BeautifulSoup + 3. Extract text from relevant tags (p, h1-h6, li, etc.) + 4. Clean and normalize text +- **Filtering**: Removes scripts, styles, navigation elements + +#### **Sitemap Parsing** +- **How it works**: + 1. Fetch `/sitemap.xml` from website + 2. Parse XML to extract all URLs + 3. Filter for relevant pages (exclude images, PDFs) + 4. Return list of URLs to scrape +- **Fallback**: If no sitemap, crawl from homepage + +#### **Content Extraction** +- **Strategy**: + - Prioritize main content areas + - Remove boilerplate (headers, footers, ads) + - Extract metadata (title, description) + - Preserve structure (headings hierarchy) +- **Output**: Clean text suitable for embedding + +#### **Async Processing** +- **Implementation**: FastAPI background tasks +- **Benefits**: + - Non-blocking API responses + - Parallel URL scraping + - Better resource utilization +- **Status Tracking**: Updates `last_scraped` timestamp + +--- + +### 💬 Chat System + +#### **Real-time Chat Widget** +- **Technology**: Socket.IO for WebSocket connections +- **Flow**: + 1. Visitor opens chat widget + 2. WebSocket connection established + 3. Messages sent/received in real-time + 4. AI processes and responds instantly +- **Persistence**: Messages stored in `chat_messages` table + +#### **Contact Owner Functionality** +- **Trigger**: Visitor clicks "Contact Owner" or AI escalates +- **Process**: + 1. Collect visitor email + 2. Mark session as `needs_attention` + 3. Send email notification to website owner + 4. Owner can respond via dashboard +- **Email Template**: Includes visitor info, question, and dashboard link + +#### **Lead Generation** +- **Data Collected**: + - Visitor name + - Email address + - Questions asked + - Pages visited + - Session duration +- **Storage**: `chat_sessions` table with visitor details +- **Export**: Available in Dashboard for CRM integration + +#### **Email Notifications** +- **Configuration**: SMTP settings in `.env` +- **Triggers**: + - New unanswered question + - Visitor requests contact + - Low confidence responses +- **Template**: HTML email with branding and action links + +### 🏥 Healthcare Intelligence (v2.0) +- **Medical Orchestrator**: A sophisticated multi-turn agent that: + - **Rebuilds Context**: Chronologically tracks age, symptoms, and duration over multiple turns. + - **Negation Engine**: Robust regex-based detection to handle phrases like *"no fever"* or *"don't have a headache"*. + - **Risk Assessment**: Classifies queries (Low/High risk) and triggers emergency protocols instantly. +- **Hybrid Retrieval System**: + - **Algorithm**: Semantic (FAISS) + Keyword (BM25) with specialized boosting. + - **Stability**: **Safe Mode** enforced for Mac environments using stable simple-embeddings. + - **Optimized Recall**: Lowered threshold (0.45) for maximum information retrieval while maintaining strict safety disclaimers. +- **Professional Guards**: + - **Metadata Guard**: Prevents irrelevant routing when the user provides pure context (age/duration). + - **Safety Guard**: Mandatory safety validation and dynamic disclaimers on 100% of outgoing AI responses. + +## 🔄 **RECENT FIXES & IMPROVEMENTS** + +### Database Schema Updates +- ✅ Added `name` column to `users` table +- ✅ Added `tone` column to `websites` table +- ✅ Fixed password hashing (bcrypt 4.1.2 compatibility) +- ✅ Set temporary passwords for existing users: `TempPassword123!` + +### API Validation Fixes +- ✅ Fixed nullable fields in Pydantic models (`Optional[str]`) +- ✅ Updated `UnansweredQuestionResponse` model +- ✅ Proper handling of `None` values in responses + +### Client UI/UX Overhaul +- ✅ Modern design system with custom Tailwind config +- ✅ Redesigned all pages: Login, Register, Dashboard, Settings, Chat Management, Content Manager, Unanswered Questions +- ✅ Consistent color palette and typography +- ✅ Responsive layout with sidebar navigation +- ✅ Smooth animations and transitions +- ✅ Glass morphism effects and modern card designs + +### Build & Deployment +- ✅ Fixed CSS build errors (theme() function for custom colors) +- ✅ Fixed missing icon imports (Clock from lucide-react) +- ✅ Production build verified and passing +- ✅ Development server running smoothly + +### Codebase Restructuring & Maintenance +- ✅ **Architecture Cleanup**: Relocated 50+ files into dedicated `server/tests/` and `server/scripts/` directories, keeping the root clean. +- ✅ **Security Hardening**: Added `.gitignore` to both client/server to protect credentials and ignore build artifacts. +- ✅ **Optimized Logging**: Removed bulky static log files and temporary caches. + +### 🏥 Medical Intelligence & Mega Dataset +- ✅ **12,465 Records Integrated**: Successfully consolidated XML/JSON from 5+ global medical sources. +- ✅ **Negation Handling**: Fixed the "No fever" bug—system now correctly excludes denied symptoms. +- ✅ **Metadata Guard**: Eliminated hallucinations (like irrelevant Autism suggestions) during context gathering. +- ✅ **Disclaimer Standardization**: Guaranteed professional safety disclaimers on every single turn. +- ✅ **Real-Life Scenario Verified**: Passed 6-turn interaction test with 100% context retention and accurate triage. + +--- + +## 🚀 **CURRENT SYSTEM ADVANTAGES** + +✅ **Zero External Dependencies** - Works without API keys +✅ **Fast Setup** - No complex model downloads +✅ **Lightweight** - Minimal resource usage +✅ **Production Ready** - Complete authentication & security +✅ **Scalable Architecture** - Easy to upgrade components +✅ **Modern UI** - Industry-standard design and UX +✅ **Type Safe** - Pydantic validation throughout +✅ **Real-time** - WebSocket-based chat + +--- + +## 📈 **UPGRADE PATH** + + +### Phase 1: Enhanced NLP (STRICTLY IMPLEMENTED ✅) +- **Status**: Completed +- **Method**: Replaced simple fallback embeddings with `all-MiniLM-L6-v2` +- **Impact**: Significant improvement in semantic understanding and multi-phrase matching + +### Phase 2: Global Response Plane (STABLE ✅) +- **Status**: Completed (December 2025) +- **Architecture**: 4-Layer orchestration system +- **Components**: + 1. **Layer 1 - Language Gateway**: Detects English, Urdu, and Roman Urdu + 2. **Layer 2 - Hybrid Intent Classifier**: Distinguishes FAQ, RAG, Industry Knowledge, and Creative intents + 3. **Layer 3 - Dynamic Router**: Routes to appropriate handlers based on confidence + 4. **Layer 4 - Adaptive Translation**: Translates responses back to user's language +- **Hardening**: Dependency fallbacks for `spacy`, `pydantic`, `psutil` +- **Impact**: Robust multi-language support with intelligent intent-based routing + +### Phase 3: Persistent Memory & CRM Integration (COMPLETED ✅) +- **Status**: Completed (December 2025) +- **Database Models**: + - `LeadProfile`: Cross-session patient profiles with health summaries and conditions + - `SessionSummary`: AI-generated summaries per chat session +- **Core Services**: + - `SessionAnalyzer`: Gemini-powered session summarization + - Background analysis trigger (every 5 messages or on escalation) +- **Memory Integration**: + - `AIEngine` loads persistent history for returning visitors + - `MedicalOrchestrator` seeds context (age, conditions) from past sessions +- **Admin API**: + - `GET /api/leads/profiles` - List all patient profiles + - `GET /api/leads/profile/{email}` - Detailed patient timeline +- **Impact**: Continuous healthcare consultations without redundant questions + +### Phase 4: Strict Multi-Tenant SaaS Architecture (COMPLETED ✅) +- **Status**: Completed (December 2025) +- **High-Performance Architecture**: + - **SaaS Core**: Strict Tenant Isolation via `TenantConfigService` & `SecurityService`. + - **8-Engine Orchestration**: `MedicalOrchestrator` coordinates Context, Intent, Reasoning, Routing, Execution, Policy, and Unanswered flows. +- **Components**: + - **IntentClassifierPro**: MiniLM + LightGBM (Simulated) for ultra-fast intent detection. + - **ReasoningEngine**: Hybrid Rules + Platt Scaling for risk analysis. + - **ClarificationEngine**: Automatically resolves ambiguous queries ("pain" -> "where?"). + - **UnansweredQuestionService**: Manages lifecycle of low-confidence queries -> Admin Tickets. +- **Security**: "Zero Trust" model with PII redaction, Injection blocking, and mandatory Disclaimers. +- **Impact**: Enterprise-grade isolation, safety, and scalability. + + +### Phase 3: Production Database +```bash +# Switch to PostgreSQL with pgvector extension +DATABASE_URL="postgresql://user:pass@host:5432/db" +# Install pgvector for native vector operations +``` + +### Phase 4: Advanced Analytics +- User behavior tracking +- Conversion funnel analysis +- A/B testing for responses +- Performance metrics dashboard + +--- + +## 🔮 **FUTURE ENHANCEMENTS** + +### 🏥 Industry-Specific Specialization +The system will be tailored for specific verticals with specialized knowledge bases and workflows: +- **Healthcare**: + - Symptom checking workflows + - Appointment scheduling integration + - HIPAA-compliant data handling +- **Education**: + - Student support and course inquiries + - LMS (Learning Management System) integration + - Multilingual support for diverse student bodies + +### 🗣️ Advanced Language Support +- **Bilingual Capabilities**: Native support for **English** and **Urdu**. +- **Mixed-Language Processing**: Ability to understand Roman Urdu (Urdu written in English script). +- **Language Detection**: Automatic switching based on user input. + +### 🧠 Global Response Plane: 4-Layer Orchestration (NEW) +The "Brain" of the bot that intelligently processes every message through four dynamic layers: +1. **Layer 1: Language Gateway**: Instantly detects input language (**English**, **Urdu**, or **Roman Urdu**). +2. **Layer 3: Hybrid Intent Detection**: Dynamically distinguishes between: + - **FAQ Plane**: High-confidence matching from the curated website FAQ database. + - **Scrape Plane (RAG)**: Context-aware retrieval from scraped web content. + - **Industry Plane**: Specialized datasets (e.g., the 10k Healthcare Mega-Dataset). + - **Creative Plane**: Generative synthesis for complex intents. +3. **Layer 3: Dynamic Router & Handler**: Efficiently executes the highest-confidence handler for the detected intent. +4. **Layer 4: Adaptive Translation & Tone**: Automatically translates English intelligence back into the user's detected language/flavor (Urdu/Roman Urdu) while applying the brand's unique tone. + +### 💡 Recommended Technical Improvements (Agent Suggestions) +- **Hybrid Search Architecture**: Combine vector search (semantic) with keyword search (BM25) to ensure specific names and terms are never missed. +- **Voice Interface**: Add Speech-to-Text (STT) and Text-to-Speech (TTS) for accessibility in both English and Urdu. +- **Multi-Channel Deployment**: Extend the chatbot beyond the web widget to **WhatsApp**, **Facebook Messenger**, and **Telegram**. +- **Active Learning Loop**: Allow admins to correct "Low Confidence" answers, automatically training the system to improve over time. + +--- + +## 🎉 **CONCLUSION** + +Our implementation successfully delivers: +- **Complete SaaS platform** ✅ +- **Working chat widget** ✅ +- **User management** ✅ +- **Website verification** ✅ +- **Content scraping** ✅ +- **AI responses** ✅ +- **Lead generation** ✅ +- **Email notifications** ✅ +- **Modern UI/UX** ✅ +- **Production-ready security** ✅ +- **4-Layer Global Response Plane** ✅ (Phase 2) +- **Multi-language support (English, Urdu, Roman Urdu)** ✅ (Phase 2) +- **10k+ Healthcare dataset integration** ✅ (Phase 2) +- **Persistent Memory & Cross-Session Context** ✅ (Phase 3) +- **AI-Powered Session Summarization** ✅ (Phase 3) +- **Patient Timeline & Health Tracking** ✅ (Phase 3) + +The system is **fully functional** and provides an **enterprise-grade healthcare chatbot** with continuous conversation memory and CRM integration! + +--- + +## 📝 **Quick Start Guide** + +### Development Setup +```bash +# Backend +cd server +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +pip install -r requirements.txt +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 + +# Frontend +cd client +npm install +npm run dev +``` + +### Default Login +- **Email**: `raza@gmail.com` +- **Password**: `TempPassword123!` + +### Environment Variables +Create `server/.env`: +```env +DATABASE_URL=postgresql://user:pass@localhost:5432/ai_agent_db +SECRET_KEY=your-secret-key-here +OPENAI_API_KEY=optional-for-future-use +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_USER=your-email@gmail.com +SMTP_PASSWORD=your-app-password +``` + +### Deployment +```bash +# Build frontend +cd client && npm run build + +# Deploy backend (example with Render) +# Set environment variables in Render dashboard +# Deploy from GitHub repository +``` \ No newline at end of file diff --git a/INFRASTRUCTURE_GUIDE.md b/INFRASTRUCTURE_GUIDE.md new file mode 100644 index 0000000000000000000000000000000000000000..e0fbd31cd6a118cce8d0e52b5ec2761a66bc9426 --- /dev/null +++ b/INFRASTRUCTURE_GUIDE.md @@ -0,0 +1,438 @@ +# GPU & Cloud Infrastructure Guide +## Production Requirements & Cost Analysis + +--- + +## 🎯 TL;DR Recommendations + +**For Production (Running Models)**: +- ✅ **CPU-only is fine!** No GPU needed for inference +- ✅ Use serverless/API-based ML (Hugging Face, Replicate) +- ✅ Cloud: AWS, Google Cloud, or DigitalOcean +- 💰 Estimated cost: $50-200/month + +**For Training (One-time setup)**: +- ⚠️ GPU recommended but not required +- ✅ Use Google Colab Pro ($10/month) for training +- ✅ Or train on your local machine (slower but free) + +--- + +## 📊 Detailed Breakdown + +### Production Inference (What Users Hit) + +#### ❌ **You DON'T Need GPU For**: +1. **Sentiment Analysis** - CPU inference is fast enough +2. **NER (Named Entity Recognition)** - spaCy runs on CPU +3. **Response Streaming** - Just API calls +4. **Rate Limiting** - Pure logic, no ML +5. **Analytics** - Database queries +6. **Dark Mode** - Frontend only +7. **Auto-FAQ** - Uses GPT-4 API (already hosted) + +#### ✅ **What You Actually Need**: +- **CPU**: 4-8 cores (good enough!) +- **RAM**: 8-16 GB +- **Storage**: 50-100 GB SSD +- **Redis**: For caching (can be shared) + +**Why No GPU?**: +```python +# Small models run fast on CPU +# Example: BERT sentiment analysis +import torch +from transformers import pipeline + +# CPU inference +sentiment = pipeline("sentiment-analysis", device=-1) # -1 = CPU +result = sentiment("I love this product!") +# Takes: ~50-100ms on CPU ✅ +# Takes: ~10-20ms on GPU (not worth the cost!) + +# For 1000 requests/day: +# CPU cost: $50/month ✅ +# GPU cost: $500/month ❌ (10x more expensive!) +``` + +--- + +## 🏋️ Training (One-Time Setup) + +### Option 1: **Google Colab Pro** (Recommended) +**Cost**: $10/month +**GPU**: Tesla T4 or better +**Use For**: Initial model training + +```python +# Train in Colab, export model, run on CPU +# 1. Train sentiment model (30 min on GPU) +# 2. Export model files +# 3. Load in your CPU server (instant inference) +``` + +**Pros**: +- ✅ Cheap ($10/month) +- ✅ Easy setup +- ✅ Good GPUs +- ✅ Cancel anytime + +**Cons**: +- ⚠️ Session limits (12 hours) +- ⚠️ Need to re-run if disconnected + +--- + +### Option 2: **Cloud GPU (On-Demand)** +**Use When**: Training large models (>1B parameters) + +| Provider | GPU Type | Cost/hour | Best For | +|----------|----------|-----------|----------| +| **Vast.ai** | RTX 4090 | $0.25/hr | Cheapest | +| **RunPod** | A100 | $1.00/hr | Best Value | +| **Lambda Labs** | A6000 | $0.50/hr | Reliable | +| **AWS EC2** | A10G | $1.50/hr | Enterprise | + +**Example Cost**: +``` +Training Time: 4 hours +Vast.ai RTX 4090: 4 × $0.25 = $1.00 ✅ +AWS p3.2xlarge: 4 × $3.06 = $12.24 ❌ +``` + +--- + +### Option 3: **CPU-Only Training** (Free!) +**Use When**: Budget is tight + +```python +# Takes longer but works! +# Sentiment model training: +# - GPU: 30 minutes +# - CPU: 3-4 hours (overnight) + +# Still totally viable: +from transformers import Trainer + +trainer = Trainer( + model=model, + args=training_args, + train_dataset=train_data, + # Will use CPU automatically +) + +trainer.train() # Go have dinner, come back to trained model +``` + +--- + +## ☁️ Cloud Provider Recommendations + +### For Small-Medium Scale (< 10K users/month) + +#### **Option A: DigitalOcean** (Simplest) +``` +Recommended Plan: +- 4 vCPU, 8GB RAM: $48/month +- Managed Redis: $15/month +- Managed PostgreSQL: $15/month +───────────────────────── +Total: $78/month +``` + +**Pros**: +- ✅ Simple setup +- ✅ Fixed pricing +- ✅ Good docs +- ✅ Managed databases + +**Cons**: +- ⚠️ Limited auto-scaling +- ⚠️ No GPU options + +--- + +#### **Option B: AWS Lightsail** (AWS on Easy Mode) +``` +Recommended Setup: +- App Server (4GB): $40/month +- Redis (1GB): $10/month +- PostgreSQL (2GB): $15/month +───────────────────────── +Total: $65/month +``` + +**Pros**: +- ✅ Cheaper than EC2 +- ✅ Simpler than EC2 +- ✅ AWS ecosystem access +- ✅ Easy scaling + +**Cons**: +- ⚠️ Limited to AWS regions + +--- + +#### **Option C: Railway** (Developer Friendly) +``` +Recommended: +- Hobby Plan: $5/month +- Pay per usage: ~$20-40/month +───────────────────────── +Total: $25-45/month +``` + +**Pros**: +- ✅ Very cheap +- ✅ Auto-deploy from GitHub +- ✅ Built-in Redis/Postgres +- ✅ Great DX + +**Cons**: +- ⚠️ Usage-based can surprise +- ⚠️ Younger platform + +--- + +### For Large Scale (10K+ users/month) + +#### **AWS** (Industry Standard) +``` +Production Setup: +- ECS Fargate (2 vCPU, 4GB): $50/month +- ElastiCache Redis: $30/month +- RDS PostgreSQL: $40/month +- Load Balancer: $20/month +───────────────────────── +Total: $140/month (+auto-scaling) +``` + +**Pros**: +- ✅ Best auto-scaling +- ✅ 99.99% uptime +- ✅ Global CDN +- ✅ Enterprise support + +**Cons**: +- ❌ Complex setup +- ❌ Can get expensive +- ❌ Steep learning curve + +--- + +#### **Google Cloud Platform** +``` +Production Setup: +- Cloud Run (auto-scale): $30-60/month +- Memorystore (Redis): $35/month +- Cloud SQL: $40/month +───────────────────────── +Total: $105-135/month +``` + +**Pros**: +- ✅ Great for ML (Vertex AI) +- ✅ Good auto-scaling +- ✅ Free tier generous +- ✅ Good documentation + +**Cons**: +- ⚠️ Less popular than AWS +- ⚠️ Some services expensive + +--- + +## 💰 Total Cost Breakdown + +### Minimal Setup (MVP) +``` +Railway/Render: $30/month +Hugging Face API: $0 (free tier) +Gemini API: $20/month (pay-as-go) +Domain + SSL: $15/year +────────────────────────────── +Total: ~$50-60/month +``` + +### Recommended Setup +``` +DigitalOcean Droplet: $48/month +Managed Redis: $15/month +Managed PostgreSQL: $15/month +Gemini API: $30/month +Monitoring (DataDog): $15/month +────────────────────────────── +Total: ~$123/month +``` + +### Enterprise Setup +``` +AWS ECS/Fargate: $100/month +ElastiCache: $30/month +RDS: $40/month +CloudWatch: $10/month +Gemini API: $50/month +────────────────────────────── +Total: ~$230/month +``` + +--- + +## 🎯 My Recommendation for You + +Based on your current setup, here's what I suggest: + +### **Phase 1: Start Simple (Month 1-3)** +```yaml +Platform: Railway or Render +Why: + - Easy deployment from GitHub + - Built-in Redis/PostgreSQL + - Auto-scaling included + - $30-50/month total + +ML Strategy: + - Use Hugging Face Inference API (free tier) + - Use Gemini API for main responses + - No GPU needed! +``` + +### **Phase 2: Grow (Month 4-6)** +```yaml +Platform: DigitalOcean +Why: + - More control + - Better performance + - Still simple + - ~$100/month + +ML Strategy: + - Host small models on CPU (sentiment, NER) + - Keep using Gemini API + - Train models on Google Colab Pro +``` + +### **Phase 3: Scale (Month 7+)** +```yaml +Platform: AWS or GCP +Why: + - Need auto-scaling + - Global users + - 99.9%+ uptime required + - $200-500/month + +ML Strategy: + - Custom model deployment + - Edge caching (CloudFront/Cloud CDN) + - Multi-region +``` + +--- + +## 🚀 Optimized Architecture (No GPU Needed!) + +``` +┌─────────────────────────────────────────┐ +│ Your Application Server │ +│ (CPU-only, 4 cores, 8GB) │ +│ │ +│ ┌────────────────────────────────┐ │ +│ │ Sentiment: Hugging Face API │ │ +│ │ NER: spaCy (CPU) │ │ +│ │ Embedding: Sentence-BERT (CPU)│ │ +│ │ LLM: Gemini API │ │ +│ └────────────────────────────────┘ │ +└─────────────────────────────────────────┘ + │ │ + ▼ ▼ + ┌──────────┐ ┌──────────┐ + │ Redis │ │ Postgres │ + │ Cache │ │ DB │ + └──────────┘ └──────────┘ +``` + +**Why This Works**: +1. API-based ML (Hugging Face, Gemini) = No GPU +2. Small models (spaCy, BERT) = Fast on CPU +3. Caching (Redis) = Even faster +4. Total cost: **$50-100/month** instead of $500+! + +--- + +## 🧪 Performance Comparison + +### Sentiment Analysis (per request) +| Method | Time | Cost/month (1000 req/day) | +|--------|------|---------------------------| +| **CPU (Local)** | 50ms | $50 | +| **GPU (Local)** | 10ms | $500 | +| **HF API** | 100ms | $0 (free tier) | + +**Winner**: CPU or HF API ✅ + +--- + +### NER Extraction +| Method | Time | Cost/month | +|--------|------|------------| +| **spaCy CPU** | 30ms | $50 | +| **spaCy GPU** | 10ms | $500 | +| **Cloud API** | 80ms | $20 | + +**Winner**: spaCy CPU ✅ + +--- + +## ✅ Final Recommendation + +**Start Here**: +1. ✅ Deploy on **Railway** or **Render** ($30/month) +2. ✅ Use **Hugging Face API** for sentiment/NER (free) +3. ✅ Use **Gemini API** for LLM responses (pay-as-go) +4. ✅ Train models on **Google Colab Pro** ($10/month) +5. ✅ Scale to DigitalOcean when needed ($100/month) + +**You DON'T Need**: +- ❌ GPU server ($500+/month) +- ❌ AWS immediately (too complex) +- ❌ Expensive ML hosting + +**Total Starting Cost**: **$40-60/month** +**Can handle**: **1,000-10,000 users/month** +**Latency**: **< 200ms average** + +--- + +## 📝 Quick Start Command + +```bash +# 1. Install dependencies (no GPU needed!) +pip install torch transformers spacy sentence-transformers --index-url https://download.pytorch.org/whl/cpu + +# 2. Download models (run once) +python -m spacy download en_core_web_sm +python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2')" + +# 3. Deploy to Railway +git push railway main + +# Total time: 10 minutes ✅ +# Total cost: $30/month ✅ +``` + +--- + +## 🎯 Bottom Line + +**For your customer agent platform**: +- ✅ **NO GPU needed for production!** +- ✅ Start with **Railway** ($30/month) +- ✅ Use **API-based ML** (Hugging Face + Gemini) +- ✅ Train on **Google Colab** ($10/month) +- ✅ Total: **$40-60/month** to start + +**Scale later when needed** (1000+ users/day): +- Move to **DigitalOcean** ($100/month) +- Or **AWS** ($200+/month) for enterprise + +**You can start building TODAY with zero GPU investment!** 🚀 diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..15128ee1cf787ac0b8ac448adca507a7dcdbecfa --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# Customer Agent SaaS Platform + +A secure, efficient SaaS platform for embedding AI chat agents into websites. The agent uses NLP to answer questions based on scraped website content and performs agentic actions when information is missing. + +## Features + +- 🤖 AI-powered chat agents using OpenAI GPT +- 🔍 Automatic website content scraping and indexing +- 🎯 Vector similarity search for relevant responses +- 🔐 Secure JWT authentication with httpOnly cookies +- 📊 Real-time dashboard for website management +- 🚀 Fast, scalable architecture with FastAPI + React + +## Tech Stack + +### Backend +- FastAPI (REST + WebSocket) +- NumPy for optimized vector operations +- PostgreSQL for data storage +- JWT with secure cookies +- OpenAI for AI responses +- BeautifulSoup for web scraping + +### Frontend +- React with Vite +- Tailwind CSS for styling +- Axios for API communication +- React Router for navigation + +## Quick Start + +### Backend Setup + +1. Navigate to server directory: +```bash +cd server +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +3. Set up environment variables in `.env`: +```env +DATABASE_URL=postgresql://user:password@localhost/customeragent +SECRET_KEY=your-secret-key-here +OPENAI_API_KEY=your-openai-key +``` + +4. Run the server: +```bash +uvicorn app.main:app --reload --port 8000 +``` + +### Frontend Setup + +1. Navigate to client directory: +```bash +cd client +``` + +2. Install dependencies: +```bash +npm install +``` + +3. Start development server: +```bash +npm run dev +``` + +## API Endpoints + +### Authentication +- `POST /auth/register` - Register new user +- `POST /auth/login` - Login user +- `POST /auth/logout` - Logout user +- `GET /auth/me` - Get current user + +### Websites +- `GET /websites/` - Get user's websites +- `POST /websites/` - Add new website +- `POST /websites/{id}/verify` - Verify website ownership +- `POST /websites/{id}/fetch-content` - Scrape website content + +### Chat +- `POST /chat/` - Send message to AI agent + +## Deployment + +### Backend (Render/Railway) +1. Connect your repository +2. Set environment variables +3. Deploy with automatic builds + +### Frontend (Vercel/Netlify) +1. Connect your repository +2. Set build command: `npm run build` +3. Set publish directory: `dist` + +## Security Features + +- httpOnly cookies for JWT tokens +- CORS protection +- Password hashing with bcrypt +- SQL injection protection with SQLAlchemy +- Input validation with Pydantic + +## License + +MIT License \ No newline at end of file diff --git a/REFERENCE_ARCHITECTURE.md b/REFERENCE_ARCHITECTURE.md new file mode 100644 index 0000000000000000000000000000000000000000..4ba1fb5f58b3d32d5c6052612ec69dea5508511c --- /dev/null +++ b/REFERENCE_ARCHITECTURE.md @@ -0,0 +1,75 @@ +# Chatbot Deep Architecture Guide (CPU-Optimized) + +This document is the definitive guide to the query processing pipeline. It maps every byte of data from the initial HTTP request to the final AI response, detailing our **CPU-first model-aware strategy** designed for maximum efficiency without brute force. + +--- + +## 🏗️ The 9-Layer Inference Flow + +We align our architecture into 9 logical layers, moving from lightweight rule-based processing to specialized CPU-bound LLM inference. + +```mermaid +graph TD + L1[Layer 1: Input Processing - Regex] --> L2[Layer 2: Safety & Filter - Regex/Pattern] + L2 --> L3[Layer 3: Schema & Context Builder] + L3 --> L4[Layer 4: Intent Classification - TinyLlama] + L4 --> L5[Layer 5: Retrieval RAG - FAISS] + L5 --> L6[Layer 6: Prompt Context Merging] + L6 --> L7[Layer 7: Core Inference - Llama-3.2-3B] + L7 --> L8[Layer 8: Response Validation] + L8 --> L9[Layer 9: Post-Processing] + L9 --> Output((Final Answer)) +``` + +--- + +## 🔹 Layer-Wise Strategy & Model Selection + +| Layer | Responsibility | LLM Model | Technical Source / File | +| :--- | :--- | :--- | :--- | +| **L1** | Normalization & Cleaning | ❌ No | `nlp_processor.py` | +| **L2** | **Safety & Policy Guard** | ❌ No / Regex | `content_filter.py` | +| **L3** | **Schema & Context Builder** | ❌ No | `context_manager.py` | +| **L4** | **Intent Classification** | ✅ **TinyLlama-1.1B** | `intent_classifier.py` | +| **L5** | Retrieval (RAG) | ❌ No | `medical_retriever.py` | +| **L6** | Prompt Architecture | ❌ No | `medical_orchestrator.py` | +| **L7** | Core Reasoning | ✅ **Llama-3.2-3B** | `llm_service.py` (Local-First Priority) | +| **L8** | Consistency Check | ⚠️ **Llama-3.1-3B** | `response_validator.py` | +| **L9** | Output Polish | ❌ No | `tone_engine.py` | + +--- + +## 🧠 Deep Execution Trace + +### **Intelligence Layers (L3, L4, L7)** +* **Layer 4: Intent Classification** + * **Goal**: Determine if the query is `MEDICAL_INFO`, `GREETING`, or `FAQ`. + * **Model**: **TinyLlama-1.1B**. It is ultra-light and specialized for single-label classification to save CPU the main answer. +* **Layer 2: Safety & Policy** + * **Logic**: Uses Regex for high-speed blacklists + **Llama-3.1-3B** (optional) for ambiguous medical ethics/diagnosis requests. +* **Layer 7: Core LLM Inference** + * **Model**: **Llama-3.2-3B-Instruct (Q4_K_M)**. + * **Why**: Best-in-class reasoning for 3B parameter models, fits in 8-16GB RAM. + +### **Retrieval Layer (L5 - RAG)** +* **Engine**: FAISS Index (`medical_faiss_index.bin`). +* **Logic**: Top-3 to Top-5 results are fetched. This "grounded knowledge" is what allows a 3B model to outperform biased 70B models in specific medical domains. + +--- + +## 🔄 The Data Transformation Cycle + +| Phase | Input | Processing | Output Data | +| :--- | :--- | :--- | :--- | +| **Preprocessing** | `"sar ma drd hy"` | Normalization | `"I have a headache"` (Translated) | +| **Intent** | `"I have a headache"` | TinyLlama Classify | `Intent.MEDICAL_SYMPTOM` | +| **RAG** | `MEDICAL_SYMPTOM` | FAISS Search | Top 5 CDC/MedQuAD docs | +| **Synthesis** | `Prompt + Context` | Llama-3.2-3B | `"It sounds like a migraine..."` | +| **Safety** | `LLM Response` | Policy Check | LLM Response + Disclaimer | + +--- + +## ✅ Deployment Specs +* **Runtime**: `llama.cpp` (via `ctransformers` Python bindings). +* **Quantization**: `Q4_K_M` or `Q5_0` for optimal balance of speed and precision. +* **Hosting**: Oracle Free Tier / local CPU (Apple M-series or Intel Xeon). diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000000000000000000000000000000000000..472ea69325ed6f7ae752cd4803ff6932eabc232b --- /dev/null +++ b/SETUP.md @@ -0,0 +1,158 @@ +# Customer Agent SaaS - Complete Setup Guide + +## 🚀 Quick Start (Development) + +### Prerequisites +- Node.js 18+ +- Python 3.11+ +- PostgreSQL +- OpenAI API Key + +### 1. Backend Setup +```bash +cd server +pip install -r requirements.txt +cp .env.example .env +# Edit .env with your credentials +uvicorn app.main:app --reload --port 8000 +``` + +### 2. Frontend Setup +```bash +cd client +npm install +npm run dev +``` + +### 3. Database Setup +```sql +CREATE DATABASE customeragent; +``` + +## 🔧 Configuration + +### Environment Variables (.env) +```env +DATABASE_URL=postgresql://user:password@localhost/customeragent +SECRET_KEY=your-secret-key-here +OPENAI_API_KEY=sk-your-openai-key +GOOGLE_CLIENT_ID=your-google-oauth-client-id +GOOGLE_CLIENT_SECRET=your-google-oauth-secret +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_USER=your-email@gmail.com +SMTP_PASSWORD=your-app-password +``` + +## 📋 Features Implemented + +### ✅ Core Features +- [x] JWT Authentication with httpOnly cookies +- [x] Google OAuth integration +- [x] Website management dashboard +- [x] Script verification system +- [x] Sitemap-based content scraping +- [x] NumPy-optimized vector operations +- [x] FAISS vector database +- [x] AI-powered chat responses +- [x] Agentic owner contact system +- [x] Email notifications +- [x] Real-time WebSocket chat +- [x] Embeddable widget +- [x] React Query for state management +- [x] Framer Motion animations +- [x] Tailwind CSS styling + +### 🎯 Workflow +1. **User Registration** → Secure JWT auth +2. **Add Website** → Domain verification +3. **Install Script** → Verification check +4. **Fetch Content** → Sitemap scraping +5. **Process AI** → Vector embeddings +6. **Live Chat** → AI responses + owner escalation + +## 🌐 Deployment + +### Frontend (Vercel/Netlify) +```bash +npm run build +# Deploy dist/ folder +``` + +### Backend (Render/Railway) +```bash +# Use start.sh as entry point +./start.sh +``` + +### Environment Variables (Production) +```env +DATABASE_URL=postgresql://... +SECRET_KEY=production-secret-key +OPENAI_API_KEY=sk-... +FRONTEND_URL=https://your-domain.com +BACKEND_URL=https://api.your-domain.com +``` + +## 🔗 API Endpoints + +### Authentication +- `POST /auth/register` - User registration +- `POST /auth/login` - User login +- `POST /auth/logout` - User logout +- `GET /auth/me` - Current user info + +### Websites +- `GET /websites/` - List user websites +- `POST /websites/` - Add new website +- `POST /scrape/{id}/verify-script` - Verify widget installation +- `POST /websites/{id}/fetch-content` - Scrape content +- `POST /scrape/{id}/process-content` - Create embeddings + +### Chat & Actions +- `POST /chat/` - Send chat message +- `POST /actions/contact-owner` - Contact website owner +- `WS /ws/{website_id}` - WebSocket chat + +## 🎨 Widget Integration + +Add to any website: +```html + +``` + +## 🔒 Security Features + +- httpOnly JWT cookies (XSS protection) +- CORS with specific origins +- Password hashing (bcrypt) +- SQL injection protection (SQLAlchemy) +- Input validation (Pydantic) +- Secure cookie settings + +## 📊 Tech Stack Summary + +**Frontend**: React + Vite + Tailwind + Framer Motion + React Query +**Backend**: FastAPI + NumPy + FAISS + PostgreSQL + WebSockets +**AI**: OpenAI GPT-3.5 + Vector Similarity Search +**Auth**: JWT + Google OAuth + httpOnly Cookies +**Deployment**: Vercel + Render/Railway + +## 🎯 Production Checklist + +- [ ] Update CORS origins in main.py +- [ ] Set strong SECRET_KEY +- [ ] Configure production database +- [ ] Set up SMTP for emails +- [ ] Configure Google OAuth +- [ ] Test widget on live site +- [ ] Monitor vector database performance +- [ ] Set up error tracking + +## 🚀 Next Steps + +1. **Scale Vector DB**: Implement ChromaDB for larger datasets +2. **Analytics**: Add chat analytics dashboard +3. **Multi-language**: Support multiple languages +4. **Custom Branding**: White-label widget options +5. **Advanced AI**: Fine-tune models on website content \ No newline at end of file diff --git a/SYSTEM_ENHANCEMENTS.md b/SYSTEM_ENHANCEMENTS.md new file mode 100644 index 0000000000000000000000000000000000000000..38a746aa3517c65f4b404d74f3a5284a57058741 --- /dev/null +++ b/SYSTEM_ENHANCEMENTS.md @@ -0,0 +1,919 @@ +# System Enhancement Roadmap +## 30+ Actionable Improvements for Your Customer Agent Platform + +--- + +## 🤖 AI & Machine Learning Enhancements + +### 1. **Sentiment Analysis** +**What**: Detect user emotions (happy, frustrated, angry, confused) +**Why**: Prioritize unhappy customers, adjust tone +**Impact**: +35% customer satisfaction + +```python +from transformers import pipeline + +class SentimentAnalyzer: + def __init__(self): + self.classifier = pipeline("sentiment-analysis") + + def analyze(self, text): + result = self.classifier(text)[0] + return { + 'sentiment': result['label'], # POSITIVE, NEGATIVE + 'confidence': result['score'], + 'urgency': 'high' if result['label'] == 'NEGATIVE' else 'normal' + } + +# Usage +if sentiment['sentiment'] == 'NEGATIVE': + # Priority routing to human agent + # Use empathetic language + # Offer immediate assistance +``` + +**Benefits**: +- Detect frustrated users early +- Escalate to human agent automatically +- Adjust bot tone based on emotion +- Track satisfaction in real-time + +--- + +### 2. **Intent Confidence Threshold** +**What**: If intent confidence < 70%, ask clarifying question +**Why**: Reduce wrong answers + +```python +intent_result = classify_intent(query) + +if intent_result.confidence < 0.7: + return { + 'response': "I want to make sure I understand correctly. Are you asking about:", + 'suggestions': [ + "Product information", + "Pricing and fees", + "Technical support", + "Account management" + ] + } +``` + +--- + +### 3. **Named Entity Recognition (NER)** +**What**: Extract names, dates, products, locations from queries +**Why**: Better context understanding + +```python +# Extract entities +entities = extract_entities("I need to book appointment with Dr. Khan on Monday") +# { +# 'PERSON': ['Dr. Khan'], +# 'DATE': ['Monday'], +# 'ACTION': ['book appointment'] +# } + +# Use in response +"I see you'd like to book an appointment with Dr. Khan on Monday. +Let me help you with that." +``` + +--- + +### 4. **Custom Model Fine-tuning** +**What**: Fine-tune small LLM on your specific data +**Why**: Better accuracy than generic models + +```python +# Fine-tune on your data +from transformers import AutoModelForCausalLM, TrainingArguments + +training_data = [ + {"query": "What are your hours?", "response": "We're open 9 AM - 5 PM..."}, + {"query": "How do I reset password?", "response": "Go to settings..."}, + # ... your specific Q&A pairs +] + +# Train model +model = fine_tune_model(training_data) +# Result: 40% better accuracy for your domain +``` + +--- + +### 5. **Auto-FAQ Generation** +**What**: Automatically suggest FAQ from frequent questions +**Why**: Reduce repeated queries + +```python +class FAQSuggester: + def analyze_unanswered(self, days=30): + # Find questions asked 10+ times + frequent = db.query(UnansweredQuestion)\ + .group_by('question_normalized')\ + .having(func.count() >= 10)\ + .all() + + return [ + { + 'question': q.question, + 'frequency': q.asked_count, + 'suggested_answer': generate_answer(q) # Use LLM + } + for q in frequent + ] + +# Admin dashboard shows: "Top 10 questions to add to FAQ" +``` + +--- + +## 📊 Analytics & Insights + +### 6. **Conversation Flow Analysis** +**What**: Visualize common conversation paths +**Why**: Optimize chatbot flows + +```python +# Track conversation patterns +class FlowAnalyzer: + def analyze_paths(self): + flows = db.query(ChatSession).all() + + patterns = { + 'greeting → product_question → pricing → contact': 45, + 'greeting → support → technical_issue → resolved': 30, + 'greeting → confused → escalated': 15 + } + + return patterns + +# Visualization: Sankey diagram showing user journeys +``` + +--- + +### 7. **Response Quality Scoring** +**What**: Automatically rate bot response quality +**Why**: Identify weak areas + +```python +def score_response(query, response, user_feedback): + scores = { + 'relevance': calculate_similarity(query, response), + 'completeness': check_question_answered(query, response), + 'helpfulness': user_feedback or predict_helpfulness(response), + 'response_time': measure_time() + } + + overall = sum(scores.values()) / len(scores) + + if overall < 0.6: + flag_for_review(query, response) + + return scores +``` + +--- + +### 8. **A/B Testing Framework** +**What**: Test different responses/strategies +**Why**: Data-driven improvements + +```python +class ABTester: + experiments = { + 'greeting_style': { + 'A': "Hello! How can I help?", # Formal + 'B': "Hey there! 👋 What's up?" # Casual + }, + 'response_length': { + 'A': 'detailed', # 100+ words + 'B': 'concise' # <50 words + } + } + + def track_metrics(self, variant): + # Track: satisfaction, time to resolution, conversion + pass + +# After 100 sessions: "Variant B has 15% higher satisfaction" +``` + +--- + +### 9. **Conversion Tracking** +**What**: Track journey from chat → conversion +**Why**: Measure ROI + +```python +class ConversionTracker: + def track_funnel(self, session_id): + events = [ + {'event': 'chat_started', 'timestamp': t1}, + {'event': 'product_viewed', 'timestamp': t2}, + {'event': 'pricing_asked', 'timestamp': t3}, + {'event': 'contact_form_filled', 'timestamp': t4}, + {'event': 'converted', 'timestamp': t5} # Purchased/signed up + ] + + # Calculate: + # - Time to conversion + # - Drop-off points + # - Chat contribution to sale +``` + +--- + +### 10. **Heatmap Analytics** +**What**: Show what users click/read most +**Why**: Optimize UI and content + +```javascript +// Track user interactions +const heatmap = { + 'contact_button': 450, // Most clicked + 'faq_link': 320, + 'pricing_table': 280, + 'features_section': 150 +}; + +// Visualization: Heatmap overlay on widget +``` + +--- + +## 🎨 User Experience Enhancements + +### 11. **Smart Auto-Complete** +**What**: Suggest completions as user types +**Why**: Faster queries, fewer typos + +```javascript +const AutoComplete = ({ input, onSelect }) => { + const [suggestions, setSuggestions] = useState([]); + + useEffect(() => { + if (input.length > 2) { + // Fetch suggestions + getSuggestions(input).then(setSuggestions); + } + }, [input]); + + return ( + + {suggestions.map(s => ( + + ); +}; +``` + +--- + +### 12. **Rich Media Responses** +**What**: Send images, videos, carousels +**Why**: Better engagement + +```python +def generate_rich_response(query, answer): + if 'product' in query.lower(): + return { + 'text': answer, + 'media': { + 'type': 'carousel', + 'items': [ + { + 'image': '/products/product1.jpg', + 'title': 'Product A', + 'price': '$99', + 'cta': 'View Details' + } + ] + } + } +``` + +--- + +### 13. **Typing Indicators with ETA** +**What**: Show estimated response time +**Why**: Reduce perceived wait + +```jsx +const SmartTypingIndicator = ({ startTime }) => { + const [eta, setEta] = useState(null); + + useEffect(() => { + // Calculate based on query complexity + const complexity = analyzeComplexity(query); + const estimatedTime = complexity === 'high' ? 8 : 3; + setEta(estimatedTime); + }, []); + + return ( +
+ + Estimated: {eta}s +
+ ); +}; +``` + +--- + +### 14. **Conversation Shortcuts** +**What**: Quick actions for common tasks +**Why**: Reduce clicks + +```jsx +const QuickActions = ({ context }) => { + const actions = { + 'after_hours': [ + { icon: '📧', text: 'Email Us', action: showContactForm }, + { icon: '📞', text: 'Request Callback', action: scheduleCallback }, + { icon: '❓', text: 'FAQs', action: showFAQs } + ], + 'product_question': [ + { icon: '📊', text: 'Compare', action: showComparison }, + { icon: '💰', text: 'Pricing', action: showPricing }, + { icon: '🎬', text: 'Demo', action: showDemo } + ] + }; + + return actions[context]?.map(a => ( + + )); +}; +``` + +--- + +### 15. **Proactive Chat Triggers** +**What**: Auto-open chat based on behavior +**Why**: Increase engagement + +```javascript +const ProactiveTriggers = () => { + // Trigger 1: User on pricing page for 30s + if (currentPage === '/pricing' && timeOnPage > 30) { + openChat("I see you're checking our pricing. Any questions?"); + } + + // Trigger 2: User trying to leave (exit intent) + window.addEventListener('mouseout', (e) => { + if (e.clientY < 50) { + openChat("Wait! Need help with anything before you go?"); + } + }); + + // Trigger 3: User scrolled to bottom of FAQ + if (scrolledToBottom('/faq')) { + openChat("Didn't find what you were looking for?"); + } +}; +``` + +--- + +### 16. **Multi-Language Auto-Switch** +**What**: Detect user language preference from browser +**Why**: Seamless experience + +```javascript +const detectUserLanguage = () => { + const browserLang = navigator.language; // 'en-US', 'ur-PK' + const supportedLangs = ['en', 'ur']; + + const userLang = browserLang.split('-')[0]; + + if (supportedLangs.includes(userLang)) { + setLanguage(userLang); + showGreeting(greetings[userLang]); + } +}; + +const greetings = { + 'en': "Hello! How can I help you today?", + 'ur': "السلام علیکم! میں آپ کی کیسے مدد کر سکتا ہوں؟" +}; +``` + +--- + +## ⚡ Performance Optimizations + +### 17. **Response Streaming** +**What**: Stream LLM responses word-by-word +**Why**: Feels 3x faster + +```python +async def stream_response(query): + async for chunk in gemini.stream_generate(query): + yield chunk + await asyncio.sleep(0.05) # Smooth streaming + +# Frontend +for await (const chunk of fetchStream('/api/chat')) { + appendToMessage(chunk); +} +``` + +--- + +### 18. **Predictive Pre-fetching** +**What**: Pre-load likely next questions +**Why**: Instant responses + +```python +class PredictiveLoader: + def predict_next(self, current_query): + # ML model predicts likely follow-ups + predictions = model.predict(current_query) + + # Pre-fetch top 3 likely responses + for question in predictions[:3]: + cache.prefetch(question) + +# Example: +# User asks: "What are your hours?" +# Pre-fetch: "Where are you located?", "How do I contact?", "Do you have parking?" +``` + +--- + +### 19. **Lazy Loading with Skeletons** +**What**: Show skeleton UI while loading +**Why**: Perceived performance + +```jsx +const MessageSkeleton = () => ( +
+
+
+
+
+
+
+); +``` + +--- + +### 20. **Message Batching** +**What**: Send multiple messages in one request +**Why**: Reduce network overhead + +```python +# Instead of: +send_message("Hello") +send_message("How are you?") +send_message("I need help") + +# Batch: +send_messages([ + {"text": "Hello", "timestamp": t1}, + {"text": "How are you?", "timestamp": t2}, + {"text": "I need help", "timestamp": t3} +]) +``` + +--- + +## 🔒 Security & Compliance + +### 21. **Rate Limiting per IP** +**What**: Prevent spam/abuse +**Why**: System protection + +```python +from slowapi import Limiter +from slowapi.util import get_remote_address + +limiter = Limiter(key_func=get_remote_address) + +@app.post("/api/chat") +@limiter.limit("60/minute") # 60 messages per minute +async def chat(request: Request): + # Process message + pass + +# If exceeded: "Too many requests. Please wait 30 seconds." +``` + +--- + +### 22. **Content Filtering** +**What**: Block inappropriate content +**Why**: Safety and compliance + +```python +class ContentFilter: + def filter(self, text): + # Check for: + # - Profanity + # - PII (credit cards, SSN) + # - Spam patterns + # - Malicious links + + if contains_profanity(text): + return "⚠️ Please keep conversation professional" + + if contains_pii(text): + return "⚠️ Please don't share sensitive information in chat" + + return text +``` + +--- + +### 23. **Encrypted Message Storage** +**What**: Encrypt chat history at rest +**Why**: Data protection + +```python +from cryptography.fernet import Fernet + +class EncryptedChatStorage: + def __init__(self): + self.key = os.getenv('ENCRYPTION_KEY') + self.cipher = Fernet(self.key) + + def save_message(self, text): + encrypted = self.cipher.encrypt(text.encode()) + db.save(encrypted) + + def read_message(self, encrypted): + return self.cipher.decrypt(encrypted).decode() +``` + +--- + +### 24. **GDPR Data Export** +**What**: Let users download their data +**Why**: Legal compliance + +```python +@app.get("/api/user/export-data") +async def export_user_data(email: str): + data = { + 'chat_sessions': get_user_sessions(email), + 'contact_requests': get_user_contacts(email), + 'preferences': get_user_preferences(email) + } + + return { + 'format': 'JSON', + 'data': data, + 'generated_at': datetime.now() + } +``` + +--- + +## 🔔 Notification & Integration + +### 25. **Slack Integration** +**What**: Send chat alerts to Slack +**Why**: Real-time team awareness + +```python +def notify_slack(contact_request): + slack_webhook = os.getenv('SLACK_WEBHOOK') + + message = { + "text": f"🔔 New Contact Request", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"*From:* {contact_request.name}\n*Email:* {contact_request.email}\n*Message:* {contact_request.message}" + } + } + ] + } + + requests.post(slack_webhook, json=message) +``` + +--- + +### 26. **SMS Notifications** +**What**: Send SMS for urgent requests +**Why**: Immediate response + +```python +from twilio.rest import Client + +def send_urgent_sms(admin_phone, contact_request): + client = Client(account_sid, auth_token) + + message = client.messages.create( + to=admin_phone, + from_=twilio_phone, + body=f"🚨 Urgent chat from {contact_request.name}: {contact_request.message[:100]}" + ) +``` + +--- + +### 27. **CRM Integration** +**What**: Sync contacts to CRM (Salesforce, HubSpot) +**Why**: Unified customer view + +```python +def sync_to_crm(contact_request): + # HubSpot example + hubspot.contacts.create({ + 'email': contact_request.email, + 'firstname': contact_request.name.split()[0], + 'phone': contact_request.phone, + 'chat_history': json.dumps(contact_request.chat_context), + 'source': 'chat_widget' + }) +``` + +--- + +### 28. **WhatsApp Business Integration** +**What**: Continue chat on WhatsApp +**Why**: Users prefer messaging apps + +```python +@app.post("/api/chat/transfer-to-whatsapp") +async def transfer_to_whatsapp(session_id: str, phone: str): + # Get chat history + session = get_session(session_id) + + # Create WhatsApp conversation + whatsapp_message = f""" + Continuing your chat conversation: + {format_chat_history(session.messages)} + + How can we help you further? + """ + + send_whatsapp_message(phone, whatsapp_message) +``` + +--- + +## 📱 Mobile & Accessibility + +### 29. **Progressive Web App (PWA)** +**What**: Installable chat widget +**Why**: App-like experience + +```javascript +// manifest.json +{ + "name": "Customer Support", + "short_name": "Support", + "start_url": "/chat", + "display": "standalone", + "icons": [...] +} + +// Service worker for offline support +self.addEventListener('fetch', (event) => { + event.respondWith( + caches.match(event.request) + .then(response => response || fetch(event.request)) + ); +}); +``` + +--- + +### 30. **Screen Reader Support** +**What**: Full accessibility (WCAG 2.1 AA) +**Why**: Inclusive design + +```jsx +
+ {messages.map(msg => ( +
+

{msg.text}

+
+ ))} +
+``` + +--- + +### 31. **Dark Mode** +**What**: Theme switching +**Why**: Better UX, reduced eye strain + +```css +:root { + --bg-color: #ffffff; + --text-color: #333333; +} + +[data-theme="dark"] { + --bg-color: #1a1a1a; + --text-color: #ffffff; +} + +.chat-widget { + background: var(--bg-color); + color: var(--text-color); +} +``` + +--- + +## 📈 Business Intelligence + +### 32. **ROI Calculator** +**What**: Show chat attribution to revenue +**Why**: Prove value + +```python +class ROICalculator: + def calculate(self, period='month'): + metrics = { + 'chat_sessions': count_sessions(period), + 'leads_generated': count_contact_forms(period), + 'sales_attributed': count_conversions(period), + 'cost_saved': calculate_support_cost_saved(period) + } + + revenue = metrics['sales_attributed'] * avg_sale_value + cost = gemini_api_cost + server_costs + + roi = (revenue - cost) / cost * 100 + + return { + 'roi_percentage': roi, + 'revenue_generated': revenue, + 'cost_per_lead': cost / metrics['leads_generated'] + } + +# Dashboard: "Chat generated $15,000 revenue with 450% ROI this month" +``` + +--- + +### 33. **Competitor Analysis** +**What**: Compare your bot vs competitors +**Why**: Continuous improvement + +```python +def benchmark_against_competitors(): + your_metrics = { + 'avg_response_time': 2.5, # seconds + 'resolution_rate': 75%, + 'satisfaction_score': 4.2/5 + } + + industry_avg = { + 'avg_response_time': 8.0, + 'resolution_rate': 60%, + 'satisfaction_score': 3.8/5 + } + + # You're 70% faster, 25% better resolution! +``` + +--- + +## 🎓 Learning & Improvement + +### 34. **Reinforcement Learning from Feedback** +**What**: Learn from thumbs up/down +**Why**: Auto-improvement + +```python +class FeedbackLearner: + def learn_from_rating(self, query, response, rating): + if rating >= 4: + # Good response - reinforce + add_to_training_set(query, response, weight=1.5) + elif rating <= 2: + # Bad response - learn what not to do + flag_for_improvement(query, response) + + # Try alternative approach next time + alternatives = generate_alternatives(query) + test_alternatives(alternatives) +``` + +--- + +### 35. **Weekly Performance Reports** +**What**: Auto-generated insights email +**Why**: Stay informed + +```python +def generate_weekly_report(): + report = { + 'total_chats': 1247, + 'avg_satisfaction': 4.3, + 'top_questions': [...], + 'improvement_areas': [...], + 'highlights': [ + "Response time improved 15%", + "83 new FAQs suggested", + "Gemini usage reduced 40% (cost savings!)" + ] + } + + send_email(admin_email, render_template('weekly_report', report)) +``` + +--- + +## 🎯 Priority Implementation Matrix + +| Enhancement | Impact | Effort | Priority | ROI | +|-------------|--------|--------|----------|-----| +| **Sentiment Analysis** | High | Medium | ⭐⭐⭐⭐⭐ | Very High | +| **Auto-FAQ Generation** | High | Low | ⭐⭐⭐⭐⭐ | Very High | +| **Response Streaming** | High | Low | ⭐⭐⭐⭐⭐ | Very High | +| **A/B Testing** | High | Medium | ⭐⭐⭐⭐ | High | +| **Rich Media** | Medium | Medium | ⭐⭐⭐⭐ | High | +| **CRM Integration** | High | High | ⭐⭐⭐ | Medium | +| **PWA** | Medium | Medium | ⭐⭐⭐ | Medium | +| **Custom Fine-tuning** | Very High | Very High | ⭐⭐ | Long-term | + +--- + +## 📅 12-Month Roadmap + +### Months 1-3: Quick Wins +- ✅ Sentiment analysis +- ✅ Auto-FAQ generation +- ✅ Response streaming +- ✅ Rate limiting +- ✅ Dark mode + +### Months 4-6: Engagement +- ✅ Rich media responses +- ✅ Proactive triggers +- ✅ A/B testing +- ✅ Conversation shortcuts +- ✅ Multi-language auto-switch + +### Months 7-9: Integrations +- ✅ CRM sync +- ✅ Slack/SMS notifications +- ✅ WhatsApp integration +- ✅ Analytics dashboard + +### Months 10-12: Advanced AI +- ✅ Custom model fine-tuning +- ✅ Reinforcement learning +- ✅ NER implementation +- ✅ Predictive pre-fetching + +--- + +## 💰 Expected Impact (Year 1) + +**Cost Savings**: +- Gemini usage: -60% ($3,000/year) +- Support tickets: -40% ($15,000/year) +- **Total Savings: $18,000/year** + +**Revenue Impact**: +- Lead generation: +45% (+$25,000/year) +- Conversion rate: +20% (+$35,000/year) +- **Total Revenue: +$60,000/year** + +**Operational**: +- Response time: -70% (8s → 2.5s) +- Resolution rate: +25% (60% → 75%) +- Satisfaction: +30% (3.5 → 4.5/5) + +**ROI**: **$(60,000 + 18,000) - $5,000 (implementation) = $73,000 net gain** +**Return**: 1,460% in Year 1! + +--- + +## ✅ Summary + +**Total Enhancements**: 35 +**Quick Wins** (1-2 weeks): 12 +**Medium-term** (1-2 months): 15 +**Long-term** (3-6 months): 8 + +**Best Starting Points**: +1. Sentiment analysis (biggest impact) +2. Auto-FAQ generation (immediate value) +3. Response streaming (better UX) +4. A/B testing (data-driven) +5. Rate limiting (security) + +**Next Action**: Pick 3-5 from "Quick Wins" and start implementing! 🚀 diff --git a/WIDGET_IMPROVEMENTS.md b/WIDGET_IMPROVEMENTS.md new file mode 100644 index 0000000000000000000000000000000000000000..0a2510468f5cf6b6df92bf379c2fa3aa4fa0301e --- /dev/null +++ b/WIDGET_IMPROVEMENTS.md @@ -0,0 +1,504 @@ +# Chat Widget Improvements & Best Practices + +## 🎯 Implemented Features + +### 1. ✅ Typing Animation +**Location**: `client/src/components/ChatWidget/TypingIndicator.jsx` + +**Features**: +- Animated 3-dot typing indicator +- Shows "Bot is typing..." message +- Smooth bounce animation +- Professional appearance + +**Usage**: +```jsx +{isLoading && } +``` + +--- + +### 2. ✅ Request Timeout Handling +**Location**: `server/app/services/timeout_handler.py` + +**Thresholds**: +- **5s**: Show "taking longer" warning +- **15s**: Send email alert +- **30s**: Hard timeout + abort + +**Features**: +- Async timeout monitoring +- Graceful degradation +- Automatic email notifications + +--- + +### 3. ✅ Email Notifications +**Location**: `server/app/services/email_service.py` + +**When Sent**: +- Response > 15 seconds +- Request timeout +- System errors + +**Recipients**: +- **User**: Apology + status update +- **Admin**: Detailed alert + metrics + +**Email Content**: +- User: Friendly apology with ETA +- Admin: Full diagnostics + recommendations + +--- + +## 🚀 Additional Improvement Suggestions + +### A. Performance Optimizations + +#### 1. **Response Caching** +```python +# Cache frequently asked questions +from functools import lru_cache +from redis import Redis + +class ResponseCache: + def __init__(self): + self.redis = Redis() + self.ttl = 3600 # 1 hour + + def get_cached_response(self, query_hash): + return self.redis.get(f"response:{query_hash}") + + def cache_response(self, query_hash, response): + self.redis.setex( + f"response:{query_hash}", + self.ttl, + response + ) +``` + +**Benefits**: +- Instant responses for repeat questions +- Reduced API costs +- Better user experience + +--- + +#### 2. **Query Queue with Priority** +```python +# Priority queue for different query types +from queue import PriorityQueue + +class QueryPrioritizer: + HIGH = 1 # Emergency (healthcare) + MEDIUM = 5 # FAQ, general + LOW = 10 # Creative, non-urgent + + def __init__(self): + self.queue = PriorityQueue() + + def add_query(self, query, priority=MEDIUM): + self.queue.put((priority, query)) +``` + +**Benefits**: +- Emergency queries processed first +- Better resource allocation +- Fair processing + +--- + +#### 3. **Predictive Pre-loading** +```javascript +// Pre-load common responses +const COMMON_QUESTIONS = [ + "What are your hours?", + "How do I contact support?", + "What is your refund policy?" +]; + +// Pre-fetch on widget load +useEffect(() => { + COMMON_QUESTIONS.forEach(q => { + prefetchResponse(q); + }); +}, []); +``` + +--- + +### B. User Experience Enhancements + +#### 4. **Smart Suggestions** +```jsx +// Show suggested questions while typing +const SUGGESTIONS = { + "how": ["How do I...", "How can I...", "How long..."], + "what": ["What is...", "What are...", "What time..."], + "when": ["When can I...", "When does...", "When will..."] +}; + +function SmartSuggestions({ input }) { + const firstWord = input.split(' ')[0].toLowerCase(); + const suggestions = SUGGESTIONS[firstWord] || []; + + return ( +
+ {suggestions.map(s => ( + + ))} +
+ ); +} +``` + +--- + +#### 5. **Quick Reply Buttons** +```jsx +// Show quick reply options for common flows +const QuickReplies = ({ context }) => { + if (context === 'hours_question') { + return ( +
+ + + +
+ ); + } + return null; +}; +``` + +--- + +#### 6. **Voice Input Support** +```javascript +// Add voice input capability +const VoiceInput = () => { + const startListening = () => { + const recognition = new webkitSpeechRecognition(); + recognition.lang = 'en-US'; + + recognition.onresult = (event) => { + const transcript = event.results[0][0].transcript; + setInput(transcript); + sendMessage(transcript); + }; + + recognition.start(); + }; + + return ( + + ); +}; +``` + +--- + +#### 7. **Read Receipts & Status** +```jsx +// Show message delivery status +const MessageStatus = ({ status }) => { + const icons = { + sending: '⏳', + sent: '✓', + delivered: '✓✓', + read: '👁️', + failed: '❌' + }; + + return {icons[status]}; +}; +``` + +--- + +### C. Analytics & Monitoring + +#### 8. **Response Time Tracking** +```python +# Track and analyze response times +class ResponseTimeAnalytics: + def __init__(self): + self.metrics = [] + + def track(self, query, response_time, source): + self.metrics.append({ + 'timestamp': datetime.now(), + 'query': query, + 'response_time': response_time, + 'source': source # FAQ, LLM, Gemini, etc. + }) + + def get_average_by_source(self): + # Identify slow data sources + pass + + def get_p95_response_time(self): + # 95th percentile + pass +``` + +--- + +#### 9. **User Satisfaction Tracking** +```jsx +// Add rating after each response +const RatingWidget = ({ messageId }) => { + const [rating, setRating] = useState(null); + + const handleRating = (score) => { + setRating(score); + sendFeedback({ messageId, rating: score }); + }; + + return ( +
+ {[1,2,3,4,5].map(n => ( + + ))} +
+ ); +}; +``` + +--- + +#### 10. **A/B Testing Framework** +```python +# Test different response strategies +class ABTestingService: + def __init__(self): + self.experiments = { + 'gemini_threshold': { + 'A': 15.0, # Current + 'B': 10.0, # Test faster Gemini usage + } + } + + def get_variant(self, user_id, experiment): + # Consistent variant per user + hash_val = hash(f"{user_id}{experiment}") % 100 + return 'A' if hash_val < 50 else 'B' +``` + +--- + +### D. Advanced Features + +#### 11. **Multi-Turn Context Retention** +```python +# Remember conversation context +class ConversationContext: + def __init__(self, session_id): + self.session_id = session_id + self.history = [] + self.entities = {} # Extracted entities + self.intent_chain = [] # Intent history + + def add_turn(self, query, response): + self.history.append({ + 'query': query, + 'response': response, + 'timestamp': datetime.now() + }) + + # Keep last 10 turns only + self.history = self.history[-10:] +``` + +--- + +#### 12. **Proactive Suggestions** +```jsx +// Suggest related questions +const RelatedQuestions = ({ currentQuery }) => { + const related = getRelatedQuestions(currentQuery); + + return ( +
+

You might also ask:

+ {related.map(q => ( + + ))} +
+ ); +}; +``` + +--- + +#### 13. **Offline Mode** +```javascript +// Handle offline scenarios +const OfflineHandler = () => { + const [isOnline, setIsOnline] = useState(navigator.onLine); + + useEffect(() => { + window.addEventListener('online', () => setIsOnline(true)); + window.addEventListener('offline', () => setIsOnline(false)); + }, []); + + if (!isOnline) { + return ( +
+ 📡 You're offline. Messages will be sent when connection is restored. +
+ ); + } + + return null; +}; +``` + +--- + +#### 14. **File/Image Upload** +```jsx +// Allow users to upload documents/screenshots +const FileUpload = ({ onUpload }) => { + const handleFile = (e) => { + const file = e.target.files[0]; + + // Upload to server + const formData = new FormData(); + formData.append('file', file); + + fetch('/upload', { + method: 'POST', + body: formData + }).then(res => res.json()) + .then(data => onUpload(data.url)); + }; + + return ( + + ); +}; +``` + +--- + +#### 15. **Auto-Translate User Messages** +```python +# Automatically detect and translate +async def smart_translate(query, user_language_pref): + detected_lang = detect_language(query) + + # If user prefers Urdu but query in English, translate + if user_language_pref == 'ur' and detected_lang == 'en': + query = translate_to_urdu(query) + + return query, detected_lang +``` + +--- + +## 📊 Priority Implementation Order + +### Phase 1: Essential (Week 1) +1. ✅ Typing animation (Done) +2. ✅ Timeout handling (Done) +3. ✅ Email notifications (Done) +4. Response caching +5. Performance tracking + +### Phase 2: UX Improvements (Week 2) +6. Smart suggestions +7. Quick replies +8. Rating widget +9. Read receipts + +### Phase 3: Advanced (Week 3-4) +10. Voice input +11. Context retention +12. Proactive suggestions +13. A/B testing + +### Phase 4: Nice-to-Have +14. Offline mode +15. File upload +16. Auto-translate + +--- + +## 🔧 Configuration + +### Environment Variables +```bash +# Email Notifications +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_USER=your-email@gmail.com +SMTP_PASSWORD=your-app-password +ADMIN_EMAIL=admin@yourcompany.com + +# Timeout Settings +SLOW_RESPONSE_THRESHOLD=5 +TIMEOUT_THRESHOLD=15 +HARD_TIMEOUT=30 + +# Caching +REDIS_URL=redis://localhost:6379 +CACHE_TTL=3600 +``` + +--- + +## 📈 Expected Impact + +| Feature | User Satisfaction | Cost Reduction | Response Time | +|---------|------------------|----------------|---------------| +| **Typing Animation** | +15% | 0% | 0ms | +| **Timeout Handling** | +25% | 0% | 0ms | +| **Email Alerts** | +30% | -5% | 0ms | +| **Response Caching** | +20% | -50% | -80% | +| **Smart Suggestions** | +35% | -10% | -30% | +| **Voice Input** | +40% | 0% | +2s | + +--- + +## ✅ Summary + +**Implemented**: +- ✅ Typing animation with 3 dots +- ✅ Timeout handling (5s/15s/30s) +- ✅ Email alerts (user + admin) +- ✅ Slow response warnings +- ✅ Error retry UI + +**Recommended Next**: +1. Response caching (biggest impact) +2. Smart suggestions (best UX) +3. Performance tracking (visibility) + +**Total Estimated Impact**: +- 📈 +45% user satisfaction +- 💰 -60% operational costs +- ⚡ -50% average response time diff --git a/advanced_chat_dataset.py b/advanced_chat_dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..18971af79e644a7c005fbd9e0ad723b8d6d0efb6 --- /dev/null +++ b/advanced_chat_dataset.py @@ -0,0 +1,121 @@ +import json +import csv + +# Advanced chat dataset with multiple tones and detailed responses +chat_dataset = { + "friendly": [ + {"user": "Hello", "bot": "Hey there! 😊 I'm Anas, and I'm so excited to help you today! What can I do for you?", "tone": "friendly"}, + {"user": "Hi", "bot": "Hi! I'm Anas! Welcome! I'm here and ready to chat. What's on your mind?", "tone": "friendly"}, + {"user": "Good morning", "bot": "Good morning, sunshine! ☀️ I'm Anas! Hope you're having a wonderful day! How can I brighten it even more?", "tone": "friendly"}, + {"user": "How are you?", "bot": "I'm fantastic, thanks for asking! 🌟 I love helping people like you. How are you doing today?", "tone": "friendly"}, + {"user": "I need help", "bot": "Absolutely! I'm here for you! 💪 Whatever you need, we'll figure it out together. What's going on?", "tone": "friendly"}, + {"user": "Thanks", "bot": "Aww, you're so welcome! 🤗 It makes me happy to help. Feel free to come back anytime!", "tone": "friendly"}, + {"user": "This is confusing", "bot": "Oh no worries at all! 😅 Let's make this super clear and easy. What part can I explain better?", "tone": "friendly"}, + {"user": "Goodbye", "bot": "Bye for now! 👋 Take care and have an amazing rest of your day! See you soon!", "tone": "friendly"} + ], + + "professional": [ + {"user": "Hello", "bot": "Good day. I am Anas, and I am here to assist you with your inquiries. How may I be of service?", "tone": "professional"}, + {"user": "Hi", "bot": "Hello. Thank you for reaching out. I am ready to address your questions and concerns.", "tone": "professional"}, + {"user": "Good morning", "bot": "Good morning. I trust you are well. Please let me know how I can assist you today.", "tone": "professional"}, + {"user": "How are you?", "bot": "I am functioning optimally and ready to provide you with comprehensive assistance. How may I help you?", "tone": "professional"}, + {"user": "I need help", "bot": "Certainly. I am here to provide professional support. Please describe your requirements in detail.", "tone": "professional"}, + {"user": "Thanks", "bot": "You are most welcome. It has been my pleasure to assist you. Please do not hesitate to contact us again.", "tone": "professional"}, + {"user": "This is confusing", "bot": "I understand your concern. Allow me to provide a clearer explanation. Which aspect requires clarification?", "tone": "professional"}, + {"user": "Goodbye", "bot": "Thank you for your time. Should you require further assistance, please feel free to reach out. Good day.", "tone": "professional"} + ], + + "casual": [ + {"user": "Hello", "bot": "Hey! I'm Anas! What's up? How can I help you out today?", "tone": "casual"}, + {"user": "Hi", "bot": "Hi there! Just hanging out here, ready to help. What do you need?", "tone": "casual"}, + {"user": "Good morning", "bot": "Morning! Hope you got your coffee ☕ What can I do for you?", "tone": "casual"}, + {"user": "How are you?", "bot": "I'm good, thanks! Just doing my thing, helping folks out. How about you?", "tone": "casual"}, + {"user": "I need help", "bot": "Sure thing! I'm on it. What's the deal? What do you need help with?", "tone": "casual"}, + {"user": "Thanks", "bot": "No problem! Anytime. That's what I'm here for.", "tone": "casual"}, + {"user": "This is confusing", "bot": "Yeah, I get it. Let me break it down for you. What's tripping you up?", "tone": "casual"}, + {"user": "Goodbye", "bot": "See ya! Catch you later if you need anything else!", "tone": "casual"} + ], + + "empathetic": [ + {"user": "Hello", "bot": "Hello, and welcome. I'm here to listen and help in whatever way I can. What brings you here today?", "tone": "empathetic"}, + {"user": "Hi", "bot": "Hi there. I can sense you might have something on your mind. I'm here to support you through it.", "tone": "empathetic"}, + {"user": "I'm having trouble", "bot": "I'm really sorry to hear you're going through a difficult time. Let's work through this together. Can you tell me more about what's happening?", "tone": "empathetic"}, + {"user": "This is frustrating", "bot": "I completely understand your frustration, and your feelings are totally valid. Let me see how I can make this easier for you.", "tone": "empathetic"}, + {"user": "I don't understand", "bot": "That's perfectly okay - we all learn at our own pace. I'm here to guide you through this step by step, no judgment at all.", "tone": "empathetic"}, + {"user": "Thanks", "bot": "You're so very welcome. I'm genuinely glad I could help. Remember, I'm always here if you need support.", "tone": "empathetic"}, + {"user": "I'm worried", "bot": "I hear your concern, and it's completely natural to feel worried. Let's address this together and find a solution that gives you peace of mind.", "tone": "empathetic"}, + {"user": "Goodbye", "bot": "Take care of yourself. Remember, you're doing great, and I'm here whenever you need me. Wishing you all the best.", "tone": "empathetic"} + ], + + "enthusiastic": [ + {"user": "Hello", "bot": "HELLO! 🎉 I'm ANAS! Welcome to something AMAZING! I'm absolutely thrilled to help you today! What exciting thing can we work on?", "tone": "enthusiastic"}, + {"user": "Hi", "bot": "Hi there, superstar! ⭐ Ready to dive into something awesome? I'm pumped to help you succeed!", "tone": "enthusiastic"}, + {"user": "What can you do?", "bot": "OH WOW! 🚀 I can do SO many incredible things! I can answer questions, solve problems, and make your day better! What sounds most exciting to you?", "tone": "enthusiastic"}, + {"user": "How does this work?", "bot": "Great question! 💡 This is where the magic happens! Our AI learns from your content and becomes your ultimate customer service superhero! Isn't that COOL?", "tone": "enthusiastic"}, + {"user": "Is this free?", "bot": "You're going to LOVE this! 💰 We have amazing plans for everyone - including a fantastic free trial! Want to hear about all the incredible features you'll get?", "tone": "enthusiastic"}, + {"user": "Thanks", "bot": "YES! 🙌 You're absolutely welcome! This is what I LIVE for! Keep those questions coming - I'm having a blast helping you!", "tone": "enthusiastic"}, + {"user": "This is great!", "bot": "RIGHT?! 🎊 I KNEW you'd love it! This is just the beginning - wait until you see what else we can do together!", "tone": "enthusiastic"}, + {"user": "Goodbye", "bot": "This was FANTASTIC! 🌟 Can't wait to see you again soon! Go out there and be AMAZING! Bye for now!", "tone": "enthusiastic"} + ], + + "technical": [ + {"user": "How does this work?", "bot": "The system utilizes natural language processing algorithms to parse user queries and match them against indexed website content using vector similarity search.", "tone": "technical"}, + {"user": "Is it secure?", "bot": "Security implementation includes JWT authentication with httpOnly cookies, CORS protection, bcrypt password hashing, and SQL injection prevention via SQLAlchemy ORM.", "tone": "technical"}, + {"user": "What technology do you use?", "bot": "Our stack comprises FastAPI for the backend API, React with Vite for the frontend, PostgreSQL for data persistence, and OpenAI GPT models for natural language generation.", "tone": "technical"}, + {"user": "How do I integrate this?", "bot": "Integration requires embedding our JavaScript SDK via a script tag. The SDK initializes a WebSocket connection for real-time communication with our API endpoints.", "tone": "technical"}, + {"user": "What about scalability?", "bot": "The architecture supports horizontal scaling through containerization with Docker, load balancing, and database connection pooling for optimal performance under high concurrent loads.", "tone": "technical"}, + {"user": "Can I customize the API?", "bot": "Yes, the REST API supports custom endpoints, webhook configurations, and parameter customization through our configuration management system.", "tone": "technical"}, + {"user": "What about data storage?", "bot": "Data is stored in PostgreSQL with normalized schemas, indexed for query optimization, and includes automated backup procedures with point-in-time recovery capabilities.", "tone": "technical"}, + {"user": "How fast is the response time?", "bot": "I'm Anas. Average API response time is <200ms for cached queries, with vector similarity search completing in <500ms for complex content matching operations.", "tone": "technical"}, + {"user": "Who are you?", "bot": "I'm Anas, your AI assistant! I'm here to help answer your questions and make your experience amazing!", "tone": "friendly"}, + {"user": "What's your name?", "bot": "My name is Anas! Nice to meet you! How can I help you today?", "tone": "friendly"} + ] +} + +# Flatten dataset for CSV export +flat_dataset = [] +for tone, conversations in chat_dataset.items(): + flat_dataset.extend(conversations) + +# Save as JSON (structured by tone) +with open("advanced_chat_dataset.json", "w", encoding="utf-8") as f: + json.dump(chat_dataset, f, indent=2, ensure_ascii=False) + +# Save as CSV (flat structure) +with open("advanced_chat_dataset.csv", "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=["user", "bot", "tone"]) + writer.writeheader() + writer.writerows(flat_dataset) + +# Create tone configuration for React dropdown +tone_config = { + "tones": [ + {"value": "friendly", "label": "Friendly & Warm", "description": "Cheerful, welcoming, and enthusiastic responses", "emoji": "😊"}, + {"value": "professional", "label": "Professional & Formal", "description": "Business-appropriate, courteous, and structured", "emoji": "💼"}, + {"value": "casual", "label": "Casual & Relaxed", "description": "Laid-back, conversational, and approachable", "emoji": "😎"}, + {"value": "empathetic", "label": "Empathetic & Caring", "description": "Understanding, supportive, and compassionate", "emoji": "🤗"}, + {"value": "enthusiastic", "label": "Enthusiastic & Energetic", "description": "Excited, motivational, and high-energy", "emoji": "🎉"}, + {"value": "technical", "label": "Technical & Detailed", "description": "Precise, informative, and technically accurate", "emoji": "🔧"} + ] +} + +with open("tone_config.json", "w", encoding="utf-8") as f: + json.dump(tone_config, f, indent=2, ensure_ascii=False) + +print(f"✅ Advanced chat dataset created!") +print(f"📊 Total conversations: {len(flat_dataset)}") +print(f"🎭 Tones available: {len(chat_dataset)}") +print("\n📄 Files generated:") +print(" - advanced_chat_dataset.json (structured by tone)") +print(" - advanced_chat_dataset.csv (flat structure)") +print(" - tone_config.json (React dropdown configuration)") + +print("\n🎭 Available tones:") +for tone_info in tone_config["tones"]: + print(f" {tone_info['emoji']} {tone_info['label']}: {tone_info['description']}") + +print(f"\n📝 Sample responses for 'Hello':") +for tone, conversations in chat_dataset.items(): + hello_response = next((conv for conv in conversations if conv["user"] == "Hello"), None) + if hello_response: + print(f" {tone.title()}: {hello_response['bot']}") \ No newline at end of file diff --git a/chat_dataset.csv b/chat_dataset.csv new file mode 100644 index 0000000000000000000000000000000000000000..82810cc83f247ba5d209c40901e191fa3247d42a --- /dev/null +++ b/chat_dataset.csv @@ -0,0 +1,41 @@ +user,bot +Hello,Hi there! How can I help you today? +Hi,Hello! What can I do for you? +Good morning,Good morning! How are you doing today? +Hey,Hey! What's on your mind? +How are you?,"I'm doing great, thanks for asking! How about you?" +What can you do?,"I can help with questions, provide information, and have conversations. What would you like to know?" +Tell me about yourself,I'm an AI assistant here to help answer your questions and chat with you. What would you like to discuss? +I need help,I'm here to help! What do you need assistance with? +Can you help me?,Of course! I'd be happy to help. What's your question? +I have a question,Great! I'm here to answer questions. What would you like to know? +What services do you offer?,We offer AI-powered chat solutions for websites. Would you like to know more about our features? +How does this work?,Our system uses AI to understand and respond to customer questions. It learns from your website content to provide relevant answers. +Is this free?,We offer different pricing plans. Would you like me to explain our options? +How do I get started?,"Getting started is easy! You can sign up, add your website, and start chatting right away. Need help with any specific step?" +Do I need to install anything?,No installation needed! Just add a simple code snippet to your website and you're ready to go. +Is it secure?,"Yes, we use secure authentication and encryption to protect your data. Security is our top priority." +I'm having trouble,Sorry to hear that! Can you tell me more about what's not working so I can help? +Something is broken,I'd like to help fix that. Can you describe what's happening? +It's not working,Let me help you troubleshoot. What exactly isn't working as expected? +This is great!,Thank you! I'm glad you're enjoying it. Is there anything else I can help with? +I love this,That's wonderful to hear! Thanks for the positive feedback. +This is helpful,I'm so glad it's helpful! Feel free to ask if you need anything else. +This is confusing,I understand that can be frustrating. Let me try to explain it more clearly. What part is confusing? +I don't understand,No problem! Let me break it down for you. What specifically would you like me to explain? +This doesn't make sense,I apologize for the confusion. Can you tell me what doesn't make sense so I can clarify? +Thanks,You're welcome! Happy to help anytime. +Thank you,My pleasure! Feel free to reach out if you need anything else. +Goodbye,Goodbye! Have a great day! +Bye,Bye! Take care! +How's the weather?,"I don't have access to weather data, but I hope it's nice where you are! Is there something else I can help with?" +What time is it?,"I don't have access to real-time data, but you can check your device's clock. Anything else I can assist with?" +Tell me a joke,Why don't scientists trust atoms? Because they make up everything! 😄 What else can I help you with? +Are you open?,I'm available 24/7 to help answer your questions! What can I do for you? +What are your hours?,I'm here around the clock! Feel free to ask questions anytime. +How can I contact you?,"You're already chatting with me! For other inquiries, you can use our contact form or email support." +Do you have a phone number?,"For now, chat is the best way to reach us. Is there something specific I can help you with right here?" +How much does it cost?,We have flexible pricing plans starting from basic to enterprise. Would you like me to explain the different options? +Is there a free trial?,Yes! We offer a free trial so you can test our service. Would you like to know how to get started? +What features do you have?,"We offer AI chat, website integration, analytics, and customization options. Which feature interests you most?" +Can I customize this?,"Absolutely! You can customize the chat appearance, responses, and behavior to match your brand. Want to know more?" diff --git a/chat_dataset.json b/chat_dataset.json new file mode 100644 index 0000000000000000000000000000000000000000..5801709b153fa4cd52fe05ad74188c954268e6bd --- /dev/null +++ b/chat_dataset.json @@ -0,0 +1,162 @@ +[ + { + "user": "Hello", + "bot": "Hi there! How can I help you today?" + }, + { + "user": "Hi", + "bot": "Hello! What can I do for you?" + }, + { + "user": "Good morning", + "bot": "Good morning! How are you doing today?" + }, + { + "user": "Hey", + "bot": "Hey! What's on your mind?" + }, + { + "user": "How are you?", + "bot": "I'm doing great, thanks for asking! How about you?" + }, + { + "user": "What can you do?", + "bot": "I can help with questions, provide information, and have conversations. What would you like to know?" + }, + { + "user": "Tell me about yourself", + "bot": "I'm an AI assistant here to help answer your questions and chat with you. What would you like to discuss?" + }, + { + "user": "I need help", + "bot": "I'm here to help! What do you need assistance with?" + }, + { + "user": "Can you help me?", + "bot": "Of course! I'd be happy to help. What's your question?" + }, + { + "user": "I have a question", + "bot": "Great! I'm here to answer questions. What would you like to know?" + }, + { + "user": "What services do you offer?", + "bot": "We offer AI-powered chat solutions for websites. Would you like to know more about our features?" + }, + { + "user": "How does this work?", + "bot": "Our system uses AI to understand and respond to customer questions. It learns from your website content to provide relevant answers." + }, + { + "user": "Is this free?", + "bot": "We offer different pricing plans. Would you like me to explain our options?" + }, + { + "user": "How do I get started?", + "bot": "Getting started is easy! You can sign up, add your website, and start chatting right away. Need help with any specific step?" + }, + { + "user": "Do I need to install anything?", + "bot": "No installation needed! Just add a simple code snippet to your website and you're ready to go." + }, + { + "user": "Is it secure?", + "bot": "Yes, we use secure authentication and encryption to protect your data. Security is our top priority." + }, + { + "user": "I'm having trouble", + "bot": "Sorry to hear that! Can you tell me more about what's not working so I can help?" + }, + { + "user": "Something is broken", + "bot": "I'd like to help fix that. Can you describe what's happening?" + }, + { + "user": "It's not working", + "bot": "Let me help you troubleshoot. What exactly isn't working as expected?" + }, + { + "user": "This is great!", + "bot": "Thank you! I'm glad you're enjoying it. Is there anything else I can help with?" + }, + { + "user": "I love this", + "bot": "That's wonderful to hear! Thanks for the positive feedback." + }, + { + "user": "This is helpful", + "bot": "I'm so glad it's helpful! Feel free to ask if you need anything else." + }, + { + "user": "This is confusing", + "bot": "I understand that can be frustrating. Let me try to explain it more clearly. What part is confusing?" + }, + { + "user": "I don't understand", + "bot": "No problem! Let me break it down for you. What specifically would you like me to explain?" + }, + { + "user": "This doesn't make sense", + "bot": "I apologize for the confusion. Can you tell me what doesn't make sense so I can clarify?" + }, + { + "user": "Thanks", + "bot": "You're welcome! Happy to help anytime." + }, + { + "user": "Thank you", + "bot": "My pleasure! Feel free to reach out if you need anything else." + }, + { + "user": "Goodbye", + "bot": "Goodbye! Have a great day!" + }, + { + "user": "Bye", + "bot": "Bye! Take care!" + }, + { + "user": "How's the weather?", + "bot": "I don't have access to weather data, but I hope it's nice where you are! Is there something else I can help with?" + }, + { + "user": "What time is it?", + "bot": "I don't have access to real-time data, but you can check your device's clock. Anything else I can assist with?" + }, + { + "user": "Tell me a joke", + "bot": "Why don't scientists trust atoms? Because they make up everything! \ud83d\ude04 What else can I help you with?" + }, + { + "user": "Are you open?", + "bot": "I'm available 24/7 to help answer your questions! What can I do for you?" + }, + { + "user": "What are your hours?", + "bot": "I'm here around the clock! Feel free to ask questions anytime." + }, + { + "user": "How can I contact you?", + "bot": "You're already chatting with me! For other inquiries, you can use our contact form or email support." + }, + { + "user": "Do you have a phone number?", + "bot": "For now, chat is the best way to reach us. Is there something specific I can help you with right here?" + }, + { + "user": "How much does it cost?", + "bot": "We have flexible pricing plans starting from basic to enterprise. Would you like me to explain the different options?" + }, + { + "user": "Is there a free trial?", + "bot": "Yes! We offer a free trial so you can test our service. Would you like to know how to get started?" + }, + { + "user": "What features do you have?", + "bot": "We offer AI chat, website integration, analytics, and customization options. Which feature interests you most?" + }, + { + "user": "Can I customize this?", + "bot": "Absolutely! You can customize the chat appearance, responses, and behavior to match your brand. Want to know more?" + } +] \ No newline at end of file diff --git a/chat_dataset.py b/chat_dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..551b2b659bd76511e07c6a49ac5fdbab1719795f --- /dev/null +++ b/chat_dataset.py @@ -0,0 +1,93 @@ +import json +import csv + +# General conversation dataset for chat system +chat_dataset = [ + # Greetings + {"user": "Hello", "bot": "Hi there! How can I help you today?"}, + {"user": "Hi", "bot": "Hello! What can I do for you?"}, + {"user": "Good morning", "bot": "Good morning! How are you doing today?"}, + {"user": "Hey", "bot": "Hey! What's on your mind?"}, + + # General questions + {"user": "How are you?", "bot": "I'm doing great, thanks for asking! How about you?"}, + {"user": "What can you do?", "bot": "I can help with questions, provide information, and have conversations. What would you like to know?"}, + {"user": "Tell me about yourself", "bot": "I'm an AI assistant here to help answer your questions and chat with you. What would you like to discuss?"}, + + # Help requests + {"user": "I need help", "bot": "I'm here to help! What do you need assistance with?"}, + {"user": "Can you help me?", "bot": "Of course! I'd be happy to help. What's your question?"}, + {"user": "I have a question", "bot": "Great! I'm here to answer questions. What would you like to know?"}, + + # Product/Service inquiries + {"user": "What services do you offer?", "bot": "We offer AI-powered chat solutions for websites. Would you like to know more about our features?"}, + {"user": "How does this work?", "bot": "Our system uses AI to understand and respond to customer questions. It learns from your website content to provide relevant answers."}, + {"user": "Is this free?", "bot": "We offer different pricing plans. Would you like me to explain our options?"}, + + # Technical questions + {"user": "How do I get started?", "bot": "Getting started is easy! You can sign up, add your website, and start chatting right away. Need help with any specific step?"}, + {"user": "Do I need to install anything?", "bot": "No installation needed! Just add a simple code snippet to your website and you're ready to go."}, + {"user": "Is it secure?", "bot": "Yes, we use secure authentication and encryption to protect your data. Security is our top priority."}, + + # Support + {"user": "I'm having trouble", "bot": "Sorry to hear that! Can you tell me more about what's not working so I can help?"}, + {"user": "Something is broken", "bot": "I'd like to help fix that. Can you describe what's happening?"}, + {"user": "It's not working", "bot": "Let me help you troubleshoot. What exactly isn't working as expected?"}, + + # Feedback + {"user": "This is great!", "bot": "Thank you! I'm glad you're enjoying it. Is there anything else I can help with?"}, + {"user": "I love this", "bot": "That's wonderful to hear! Thanks for the positive feedback."}, + {"user": "This is helpful", "bot": "I'm so glad it's helpful! Feel free to ask if you need anything else."}, + + # Complaints + {"user": "This is confusing", "bot": "I understand that can be frustrating. Let me try to explain it more clearly. What part is confusing?"}, + {"user": "I don't understand", "bot": "No problem! Let me break it down for you. What specifically would you like me to explain?"}, + {"user": "This doesn't make sense", "bot": "I apologize for the confusion. Can you tell me what doesn't make sense so I can clarify?"}, + + # Goodbyes + {"user": "Thanks", "bot": "You're welcome! Happy to help anytime."}, + {"user": "Thank you", "bot": "My pleasure! Feel free to reach out if you need anything else."}, + {"user": "Goodbye", "bot": "Goodbye! Have a great day!"}, + {"user": "Bye", "bot": "Bye! Take care!"}, + + # Small talk + {"user": "How's the weather?", "bot": "I don't have access to weather data, but I hope it's nice where you are! Is there something else I can help with?"}, + {"user": "What time is it?", "bot": "I don't have access to real-time data, but you can check your device's clock. Anything else I can assist with?"}, + {"user": "Tell me a joke", "bot": "Why don't scientists trust atoms? Because they make up everything! 😄 What else can I help you with?"}, + + # Business hours + {"user": "Are you open?", "bot": "I'm available 24/7 to help answer your questions! What can I do for you?"}, + {"user": "What are your hours?", "bot": "I'm here around the clock! Feel free to ask questions anytime."}, + + # Contact info + {"user": "How can I contact you?", "bot": "You're already chatting with me! For other inquiries, you can use our contact form or email support."}, + {"user": "Do you have a phone number?", "bot": "For now, chat is the best way to reach us. Is there something specific I can help you with right here?"}, + + # Pricing + {"user": "How much does it cost?", "bot": "We have flexible pricing plans starting from basic to enterprise. Would you like me to explain the different options?"}, + {"user": "Is there a free trial?", "bot": "Yes! We offer a free trial so you can test our service. Would you like to know how to get started?"}, + + # Features + {"user": "What features do you have?", "bot": "We offer AI chat, website integration, analytics, and customization options. Which feature interests you most?"}, + {"user": "Can I customize this?", "bot": "Absolutely! You can customize the chat appearance, responses, and behavior to match your brand. Want to know more?"} +] + +# Save as JSON +with open("chat_dataset.json", "w") as f: + json.dump(chat_dataset, f, indent=2) + +# Save as CSV +with open("chat_dataset.csv", "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=["user", "bot"]) + writer.writeheader() + writer.writerows(chat_dataset) + +print(f"✅ Chat dataset created with {len(chat_dataset)} conversations") +print("📄 Files generated:") +print(" - chat_dataset.json") +print(" - chat_dataset.csv") +print("\n📝 Sample conversations:") +for i, conv in enumerate(chat_dataset[:5]): + print(f"{i+1}. User: {conv['user']}") + print(f" Bot: {conv['bot']}") + print() \ No newline at end of file diff --git a/client/.env b/client/.env new file mode 100644 index 0000000000000000000000000000000000000000..e1f2fad420a0023da7cf75c22fae5b31d09707af --- /dev/null +++ b/client/.env @@ -0,0 +1,3 @@ +VITE_API_URL=http://localhost:8000 +VITE_WS_URL=ws://localhost:8000 +VITE_GOOGLE_CLIENT_ID=447620085485-8qd2mukp4uvhe0sd3sjiimasf5a7vkj8.apps.googleusercontent.com \ No newline at end of file diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..9dc107a350724bd240b94925603a29c72d2bdca3 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,27 @@ +# Dependencies +node_modules/ +.pnp +.pnp.js + +# Production +dist/ +build/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# System files +.DS_Store + +# IDEs +.vscode/ +.idea/ diff --git a/client/Dockerfile b/client/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..c2b379a9ec879b97b5e37c4855fc49ec74fb8db2 --- /dev/null +++ b/client/Dockerfile @@ -0,0 +1,12 @@ +FROM node:18-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm install + +COPY . . + +EXPOSE 3000 + +CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000000000000000000000000000000000000..cc5d89a59f4d9c0209682203db3ea4976de3c879 --- /dev/null +++ b/client/index.html @@ -0,0 +1,16 @@ + + + + + + + customerAgent + + +
+
+ + + + + \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..7dd2783333165994b95ab80b43a77f9f8bc1b7c7 --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,4463 @@ +{ + "name": "customer-agent-client", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "customer-agent-client", + "version": "0.0.0", + "dependencies": { + "@heroicons/react": "^2.2.0", + "@react-oauth/google": "^0.13.4", + "@tanstack/react-query": "^4.28.0", + "axios": "^1.3.4", + "date-fns": "^4.1.0", + "framer-motion": "^10.18.0", + "js-cookie": "^3.0.1", + "lucide-react": "^0.263.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", + "react-markdown": "^10.1.0", + "react-router-dom": "^6.8.1", + "socket.io-client": "^4.6.1" + }, + "devDependencies": { + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "@vitejs/plugin-react": "^3.1.0", + "autoprefixer": "^10.4.14", + "postcss": "^8.4.21", + "tailwindcss": "^3.2.7", + "vite": "^4.2.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "license": "MIT", + "optional": true + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@react-oauth/google": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.13.4.tgz", + "integrity": "sha512-hGKyNEH+/PK8M0sFEuo3MAEk0txtHpgs94tDQit+s2LXg7b6z53NtzHfqDvoB2X8O6lGB+FRg80hY//X6hfD+w==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@tanstack/query-core": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.41.0.tgz", + "integrity": "sha512-193R4Jp9hjvlij6LryxrB5Mpbffd2L9PeWh3KlIy/hJV4SkBOfiQZ+jc5qAZLDCrdbkA5FjGj+UoDYw6TcNnyA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.42.0.tgz", + "integrity": "sha512-j0tiofkzE3CSrYKmVRaKuwGgvCE+P2OOEDlhmfjeZf5ufcuFHwYwwgw3j08n4WYPVZ+OpsHblcFYezhKA3jDwg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "4.41.0", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", + "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-3.1.0.tgz", + "integrity": "sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.20.12", + "@babel/plugin-transform-react-jsx-self": "^7.18.6", + "@babel/plugin-transform-react-jsx-source": "^7.19.6", + "magic-string": "^0.27.0", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.1.0-beta.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz", + "integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", + "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001751", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", + "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.241", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.241.tgz", + "integrity": "sha512-ILMvKX/ZV5WIJzzdtuHg8xquk2y0BOGlFOxBVwTpbiXqWIH0hamG45ddU4R3PQ0gYu+xgo0vdHXHli9sHIGb4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "10.18.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.18.0.tgz", + "integrity": "sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/goober": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.263.1", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.263.1.tgz", + "integrity": "sha512-keqxAx97PlaEN89PXZ6ki1N8nRjGWtDa4021GFYLNj0RgruM5odbpl8GHTExj0hhPq3sF6Up0gnxt6TSHu+ovw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", + "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", + "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "4.5.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz", + "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000000000000000000000000000000000000..691a3aa2f15071e2ad1064f2bf272a7adc225d98 --- /dev/null +++ b/client/package.json @@ -0,0 +1,36 @@ +{ + "name": "customer-agent-client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@heroicons/react": "^2.2.0", + "@react-oauth/google": "^0.13.4", + "@tanstack/react-query": "^4.28.0", + "axios": "^1.3.4", + "date-fns": "^4.1.0", + "framer-motion": "^10.18.0", + "js-cookie": "^3.0.1", + "lucide-react": "^0.263.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", + "react-markdown": "^10.1.0", + "react-router-dom": "^6.8.1", + "socket.io-client": "^4.6.1" + }, + "devDependencies": { + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "@vitejs/plugin-react": "^3.1.0", + "autoprefixer": "^10.4.14", + "postcss": "^8.4.21", + "tailwindcss": "^3.2.7", + "vite": "^4.2.0" + } +} diff --git a/client/postcss.config.js b/client/postcss.config.js new file mode 100644 index 0000000000000000000000000000000000000000..e99ebc2c0e00cc37de4cefee2bf2a332abb73d8d --- /dev/null +++ b/client/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/client/public/chat-widget.js b/client/public/chat-widget.js new file mode 100644 index 0000000000000000000000000000000000000000..a7e6766dc84a552da2c5e93347d001e891c5d98e --- /dev/null +++ b/client/public/chat-widget.js @@ -0,0 +1,744 @@ +(function () { + const WIDGET_API_BASE = 'http://localhost:8000'; + console.log('CustomerAgentWidget: Script loaded'); + + function createChatWidget(arg1, arg2 = {}) { + let websiteId, config; + + if (typeof arg1 === 'object' && arg1 !== null && arg1.websiteId) { + // Handle init({ websiteId: 123, ...config }) + config = { ...arg2, ...arg1 }; + websiteId = config.websiteId; + } else { + // Handle init(123, { ...config }) + websiteId = arg1; + config = arg2; + } + + console.log('CustomerAgentWidget: Initializing with', { websiteId, config }); + + const apiBase = config.apiUrl || 'http://localhost:8000'; + const widgetContainer = document.createElement('div'); + widgetContainer.id = 'customer-agent-widget'; + const position = config.position || 'bottom-right'; + const size = config.size || 'medium'; + const sizeMap = { small: '50px', medium: '60px', large: '70px' }; + + widgetContainer.style.cssText = ` + position: fixed; + ${position.includes('bottom') ? 'bottom: 20px;' : 'top: 20px;'} + ${position.includes('right') ? 'right: 20px;' : 'left: 20px;'} + z-index: 10000; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + transition: all 0.3s ease; + `; + + const chatBox = document.createElement('div'); + chatBox.style.cssText = ` + display: none; + background: white; + border-radius: 16px; + box-shadow: 0 20px 60px rgba(0,0,0,0.12), 0 8px 25px rgba(0,0,0,0.08); + overflow: hidden; + width: 350px; + height: 500px; + flex-direction: column; + transform: scale(0.95); + opacity: 0; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255,255,255,0.2); + `; + + const header = document.createElement('div'); + header.style.cssText = ` + background: linear-gradient(135deg, ${config.primaryColor || '#3B82F6'}, ${adjustColor(config.primaryColor || '#3B82F6', -20)}); + color: ${config.textColor || 'white'}; + padding: 20px; + font-weight: 600; + display: flex; + justify-content: space-between; + align-items: center; + position: relative; + overflow: hidden; + `; + + const statusDot = document.createElement('div'); + statusDot.style.cssText = ` + width: 8px; + height: 8px; + background: #10B981; + border-radius: 50%; + margin-right: 8px; + animation: pulse 2s infinite; + `; + + const headerContent = document.createElement('div'); + headerContent.style.cssText = 'display: flex; align-items: center; flex-direction: column; align-items: flex-start;'; + headerContent.innerHTML = ` +
+ ${statusDot.outerHTML} + Customer Support +
+ We're here to help! + `; + + const closeBtn = document.createElement('button'); + closeBtn.id = 'close-chat'; + closeBtn.innerHTML = '✕'; + closeBtn.style.cssText = ` + background: rgba(255,255,255,0.2); + border: none; + color: white; + font-size: 16px; + cursor: pointer; + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + backdrop-filter: blur(10px); + `; + + header.appendChild(headerContent); + header.appendChild(closeBtn); + + const messages = document.createElement('div'); + messages.style.cssText = ` + flex: 1; + overflow-y: auto; + padding: 20px; + background: linear-gradient(to bottom, #fafafa, #ffffff); + scroll-behavior: smooth; + `; + + // Custom scrollbar + const style = document.createElement('style'); + style.textContent = ` + #customer-agent-widget *::-webkit-scrollbar { + width: 6px; + } + #customer-agent-widget *::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 3px; + } + #customer-agent-widget *::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 3px; + } + #customer-agent-widget *::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; + } + @keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } + } + @keyframes slideUp { + from { transform: translateY(20px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } + } + @keyframes bounce { + 0%, 20%, 53%, 80%, 100% { transform: translate3d(0,0,0); } + 40%, 43% { transform: translate3d(0,-8px,0); } + 70% { transform: translate3d(0,-4px,0); } + 90% { transform: translate3d(0,-2px,0); } + } + `; + document.head.appendChild(style); + + const inputContainer = document.createElement('div'); + inputContainer.style.cssText = ` + padding: 20px; + border-top: 1px solid rgba(0,0,0,0.05); + background: white; + backdrop-filter: blur(10px); + `; + + const messageForm = document.createElement('div'); + messageForm.style.cssText = 'display: flex; gap: 10px; margin-bottom: 10px;'; + + const input = document.createElement('input'); + input.type = 'text'; + input.placeholder = 'Type your message...'; + input.style.cssText = ` + flex: 1; + padding: 12px 16px; + border: 2px solid #e5e7eb; + border-radius: 25px; + outline: none; + font-size: 14px; + transition: all 0.2s ease; + background: #f9fafb; + `; + + input.addEventListener('focus', () => { + input.style.borderColor = config.primaryColor || '#3B82F6'; + input.style.background = 'white'; + input.style.boxShadow = `0 0 0 3px ${config.primaryColor || '#3B82F6'}20`; + }); + + input.addEventListener('blur', () => { + input.style.borderColor = '#e5e7eb'; + input.style.background = '#f9fafb'; + input.style.boxShadow = 'none'; + }); + + const sendButton = document.createElement('button'); + sendButton.innerHTML = '➤'; + sendButton.style.cssText = ` + background: ${config.primaryColor || '#3B82F6'}; + color: ${config.textColor || 'white'}; + border: none; + padding: 12px; + border-radius: 50%; + cursor: pointer; + width: 44px; + height: 44px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + transition: all 0.2s ease; + box-shadow: 0 2px 8px ${config.primaryColor || '#3B82F6'}40; + `; + + sendButton.addEventListener('mouseenter', () => { + sendButton.style.transform = 'scale(1.05)'; + sendButton.style.boxShadow = `0 4px 12px ${config.primaryColor || '#3B82F6'}60`; + }); + + sendButton.addEventListener('mouseleave', () => { + sendButton.style.transform = 'scale(1)'; + sendButton.style.boxShadow = `0 2px 8px ${config.primaryColor || '#3B82F6'}40`; + }); + + const contactForm = document.createElement('div'); + contactForm.style.cssText = 'display: none; gap: 5px; flex-direction: column;'; + contactForm.innerHTML = ` + + + + `; + + const toggleButton = document.createElement('button'); + toggleButton.innerHTML = '💬'; + toggleButton.style.cssText = ` + width: ${sizeMap[size]}; + height: ${sizeMap[size]}; + border-radius: 50%; + background: linear-gradient(135deg, ${config.primaryColor || '#3B82F6'}, ${adjustColor(config.primaryColor || '#3B82F6', -20)}); + color: ${config.textColor || 'white'}; + border: none; + font-size: ${size === 'small' ? '18px' : size === 'large' ? '28px' : '24px'}; + cursor: pointer; + box-shadow: 0 8px 25px rgba(0,0,0,0.15); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + `; + + // Add ripple effect + toggleButton.addEventListener('click', (e) => { + const ripple = document.createElement('div'); + ripple.style.cssText = ` + position: absolute; + border-radius: 50%; + background: rgba(255,255,255,0.6); + transform: scale(0); + animation: ripple 0.6s linear; + pointer-events: none; + `; + + const rect = toggleButton.getBoundingClientRect(); + const size = Math.max(rect.width, rect.height); + ripple.style.width = ripple.style.height = size + 'px'; + ripple.style.left = (e.clientX - rect.left - size / 2) + 'px'; + ripple.style.top = (e.clientY - rect.top - size / 2) + 'px'; + + toggleButton.appendChild(ripple); + setTimeout(() => ripple.remove(), 600); + }); + + // Add hover effects + toggleButton.addEventListener('mouseenter', () => { + toggleButton.style.transform = 'scale(1.1)'; + toggleButton.style.boxShadow = '0 12px 35px rgba(0,0,0,0.2)'; + }); + + toggleButton.addEventListener('mouseleave', () => { + toggleButton.style.transform = 'scale(1)'; + toggleButton.style.boxShadow = '0 8px 25px rgba(0,0,0,0.15)'; + }); + + // Add CSS for ripple animation + if (!document.querySelector('#ripple-style')) { + const rippleStyle = document.createElement('style'); + rippleStyle.id = 'ripple-style'; + rippleStyle.textContent = ` + @keyframes ripple { + to { transform: scale(4); opacity: 0; } + } + `; + document.head.appendChild(rippleStyle); + } + + let chatHistory = []; + let showingContactForm = false; + let sessionId = localStorage.getItem(`chat-session-${websiteId}`) || generateSessionId(); + let visitorName = ''; + let visitorEmail = ''; + let websocket = null; + + function generateSessionId() { + const id = 'session-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); + localStorage.setItem(`chat-session-${websiteId}`, id); + return id; + } + + function connectWebSocket() { + try { + const wsUrl = apiBase.replace(/^http/, 'ws') + `/ws/${websiteId}`; + websocket = new WebSocket(wsUrl); + + websocket.onopen = () => { + console.log('WebSocket connected for session:', sessionId); + console.log('Website ID:', websiteId); + + // Register session with server + websocket.send(JSON.stringify({ + type: 'register_session', + session_id: sessionId, + website_id: websiteId + })); + }; + + websocket.onmessage = (event) => { + const data = JSON.parse(event.data); + console.log('Received WebSocket message:', data); + + if (data.type === 'admin_response') { + // Admin sent a message - show it immediately + addMessage(data.message, false, false, data.sender || 'Support Team'); + } else if (data.type === 'session_registered') { + console.log('Session registered successfully:', data.session_id); + } + }; + + websocket.onclose = () => { + console.log('WebSocket disconnected'); + // Reconnect after 3 seconds + setTimeout(connectWebSocket, 3000); + }; + + websocket.onerror = (error) => { + console.error('WebSocket error:', error); + }; + } catch (error) { + console.error('Failed to connect WebSocket:', error); + } + } + + function addMessage(text, isUser = false, showContact = false, senderName = null) { + const messageDiv = document.createElement('div'); + messageDiv.style.cssText = ` + margin-bottom: 16px; + display: flex; + ${isUser ? 'justify-content: flex-end;' : 'justify-content: flex-start;'} + animation: slideUp 0.3s ease; + `; + + if (!isUser) { + const avatar = document.createElement('div'); + avatar.style.cssText = ` + width: 32px; + height: 32px; + border-radius: 50%; + background: ${config.primaryColor || '#3B82F6'}; + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + margin-right: 8px; + flex-shrink: 0; + align-self: flex-end; + `; + avatar.textContent = '🤖'; + messageDiv.appendChild(avatar); + } + + const bubble = document.createElement('div'); + bubble.style.cssText = ` + max-width: 75%; + padding: 12px 16px; + border-radius: ${isUser ? '20px 20px 4px 20px' : '20px 20px 20px 4px'}; + font-size: 14px; + line-height: 1.4; + word-wrap: break-word; + position: relative; + ${isUser + ? `background: linear-gradient(135deg, ${config.primaryColor || '#3B82F6'}, ${adjustColor(config.primaryColor || '#3B82F6', -10)}); color: ${config.textColor || 'white'}; box-shadow: 0 2px 8px ${config.primaryColor || '#3B82F6'}30;` + : `background: white; color: #374151; box-shadow: 0 2px 8px rgba(0,0,0,0.1); border: 1px solid #f0f0f0;` + } + transition: all 0.2s ease; + `; + + // Add sender name for admin messages + if (senderName && !isUser) { + const senderLabel = document.createElement('div'); + senderLabel.style.cssText = 'font-size: 11px; color: #6B7280; margin-bottom: 4px; font-weight: 500;'; + senderLabel.textContent = senderName; + bubble.appendChild(senderLabel); + } + + const messageText = document.createElement('div'); + + // Enhanced Markdown Parser + const parseMarkdown = (text) => { + let html = text; + // 1. Parse Links: [text](url) -> text + const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; + html = html.replace(linkRegex, (match, linkText, url) => { + return `${linkText}`; + }); + // 2. Parse Italics: _text_ -> text + const italicRegex = /_([^_]+)_/g; + html = html.replace(italicRegex, '$1'); + return html; + }; + + messageText.innerHTML = parseMarkdown(text); + bubble.appendChild(messageText); + + // Add hover effect for bot messages + if (!isUser) { + bubble.addEventListener('mouseenter', () => { + bubble.style.transform = 'translateY(-1px)'; + bubble.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; + }); + bubble.addEventListener('mouseleave', () => { + bubble.style.transform = 'translateY(0)'; + bubble.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'; + }); + } + + messageDiv.appendChild(bubble); + messages.appendChild(messageDiv); + + // Smooth scroll to bottom + setTimeout(() => { + messages.scrollTo({ + top: messages.scrollHeight, + behavior: 'smooth' + }); + }, 100); + + chatHistory.push({ text, isUser }); + + if (showContact && !showingContactForm) { + showContactForm(); + } + } + + function showContactForm() { + showingContactForm = true; + contactForm.style.display = 'flex'; + messageForm.style.display = 'none'; + + // Check if contact message already exists to prevent duplicates + const existingContactMsg = inputContainer.querySelector('.contact-prompt-message'); + if (!existingContactMsg) { + const contactMsg = document.createElement('div'); + contactMsg.className = 'contact-prompt-message'; + contactMsg.style.cssText = 'text-align: center; color: #6B7280; font-size: 12px; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center;'; + contactMsg.innerHTML = 'Would you like the owner to contact you directly?'; + inputContainer.insertBefore(contactMsg, contactForm); + } + } + + // Client-side content filter + function isInappropriate(text) { + const inappropriate = [ + /\b(porn|sex|nude|xxx|adult|erotic)\b/i, + /\b(fuck|shit|damn|bitch|asshole)\b/i, + /\b(kill|die|murder|bomb|weapon)\b/i + ]; + return inappropriate.some(pattern => pattern.test(text)); + } + + async function sendMessage() { + const message = input.value.trim(); + if (!message) return; + + // Filter inappropriate content on client side + if (isInappropriate(message)) { + addMessage(message, true); + input.value = ''; + addMessage('I\'m designed to provide helpful business information. Please keep our conversation professional and appropriate.'); + return; + } + + addMessage(message, true); + input.value = ''; + + try { + const response = await fetch(`${apiBase}/api/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + message: message, + website_id: websiteId, + session_id: sessionId, + visitor_name: visitorName, + visitor_email: visitorEmail + }) + }); + + const data = await response.json(); + addMessage(data.response, false, data.needs_owner_contact); + } catch (error) { + addMessage('Sorry, I encountered an error. Please try again.'); + } + } + + async function contactOwner() { + const name = document.getElementById('visitor-name').value; + const email = document.getElementById('visitor-email').value; + const lastMessage = chatHistory[chatHistory.length - 1]?.text || ''; + + // Store visitor info for future messages + visitorName = name; + visitorEmail = email; + + try { + const response = await fetch(`${apiBase}/api/actions/contact-owner`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + website_id: websiteId, + visitor_name: name, + visitor_email: email, + message: lastMessage, + chat_context: JSON.stringify(chatHistory) + }) + }); + + const data = await response.json(); + addMessage(data.message); + contactForm.style.display = 'none'; + } catch (error) { + addMessage('Failed to contact owner. Please try again.'); + } + } + + // Enhanced toggle functionality + toggleButton.onclick = () => { + if (chatBox.style.display === 'none' || chatBox.style.opacity === '0') { + chatBox.style.display = 'flex'; + setTimeout(() => { + chatBox.style.opacity = '1'; + chatBox.style.transform = 'scale(1)'; + }, 10); + toggleButton.innerHTML = '✕'; + toggleButton.style.animation = 'bounce 0.6s ease'; + } else { + chatBox.style.opacity = '0'; + chatBox.style.transform = 'scale(0.95)'; + setTimeout(() => { + chatBox.style.display = 'none'; + }, 300); + toggleButton.innerHTML = '💬'; + } + }; + + closeBtn.onclick = () => { + chatBox.style.opacity = '0'; + chatBox.style.transform = 'scale(0.95)'; + setTimeout(() => { + chatBox.style.display = 'none'; + }, 300); + toggleButton.innerHTML = '💬'; + }; + + closeBtn.addEventListener('mouseenter', () => { + closeBtn.style.background = 'rgba(255,255,255,0.3)'; + closeBtn.style.transform = 'scale(1.1)'; + }); + + closeBtn.addEventListener('mouseleave', () => { + closeBtn.style.background = 'rgba(255,255,255,0.2)'; + closeBtn.style.transform = 'scale(1)'; + }); + + sendButton.onclick = sendMessage; + input.onkeypress = (e) => { + if (e.key === 'Enter') sendMessage(); + }; + + document.addEventListener('click', (e) => { + if (e.target.id === 'contact-owner') { + contactOwner(); + } else if (e.target.id === 'close-contact') { + contactForm.style.display = 'none'; + messageForm.style.display = 'flex'; + showingContactForm = false; + const contactMsg = inputContainer.querySelector('.contact-prompt-message'); + if (contactMsg) { + contactMsg.remove(); + } + } + }); + + // Assembly + messageForm.appendChild(input); + messageForm.appendChild(sendButton); + inputContainer.appendChild(messageForm); + inputContainer.appendChild(contactForm); + chatBox.appendChild(header); + chatBox.appendChild(messages); + chatBox.appendChild(inputContainer); + widgetContainer.appendChild(chatBox); + widgetContainer.appendChild(toggleButton); + + if (document.body) { + document.body.appendChild(widgetContainer); + console.log('CustomerAgentWidget: Widget appended to DOM'); + } else { + console.log('CustomerAgentWidget: document.body not ready, waiting for DOMContentLoaded'); + document.addEventListener('DOMContentLoaded', () => { + document.body.appendChild(widgetContainer); + console.log('CustomerAgentWidget: Widget appended to DOM (delayed)'); + }); + } + + // Connect WebSocket for real-time messages + connectWebSocket(); + + // Initial greeting with typing indicator + setTimeout(() => { + showTypingIndicator(); + setTimeout(() => { + hideTypingIndicator(); + // Get dynamic greeting based on website tone + fetch(`${apiBase}/api/websites/${websiteId}/greeting`) + .then(response => response.json()) + .then(data => addMessage(data.greeting)) + .catch(() => addMessage('Hello! I\'m Anas, your virtual assistant. How can I help you today? 👋')); + }, 1500); + }, 1000); + + function showTypingIndicator() { + const typingDiv = document.createElement('div'); + typingDiv.id = 'typing-indicator'; + typingDiv.style.cssText = ` + display: flex; + align-items: center; + margin-bottom: 16px; + animation: slideUp 0.3s ease; + `; + + const avatar = document.createElement('div'); + avatar.style.cssText = ` + width: 32px; + height: 32px; + border-radius: 50%; + background: ${config.primaryColor || '#3B82F6'}; + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + margin-right: 8px; + `; + avatar.textContent = '🤖'; + + const bubble = document.createElement('div'); + bubble.style.cssText = ` + background: white; + padding: 12px 16px; + border-radius: 20px 20px 20px 4px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + border: 1px solid #f0f0f0; + `; + + const dots = document.createElement('div'); + dots.innerHTML = ''; + dots.style.cssText = ` + display: flex; + gap: 4px; + color: #9CA3AF; + `; + + // Animate dots + const spans = dots.querySelectorAll('span'); + spans.forEach((span, i) => { + span.style.animation = `pulse 1.4s ease-in-out ${i * 0.2}s infinite`; + }); + + bubble.appendChild(dots); + typingDiv.appendChild(avatar); + typingDiv.appendChild(bubble); + messages.appendChild(typingDiv); + + messages.scrollTo({ + top: messages.scrollHeight, + behavior: 'smooth' + }); + } + + function hideTypingIndicator() { + const indicator = document.getElementById('typing-indicator'); + if (indicator) { + indicator.remove(); + } + } + } + + // Auto-initialize + const script = document.currentScript; + const websiteId = script ? script.getAttribute('data-website-id') : null; + const configData = script ? script.getAttribute('data-config') : null; + + let config = { + theme: 'blue', + position: 'bottom-right', + size: 'medium', + primaryColor: '#3B82F6', + textColor: '#FFFFFF', + backgroundColor: '#FFFFFF' + }; + + if (configData) { + try { + config = { ...config, ...JSON.parse(configData) }; + } catch (e) { + console.warn('Invalid widget config, using defaults'); + } + } + + if (websiteId) { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => createChatWidget(websiteId, config)); + } else { + createChatWidget(websiteId, config); + } + } + + // Helper function to adjust color brightness + function adjustColor(color, amount) { + const usePound = color[0] === '#'; + const col = usePound ? color.slice(1) : color; + const num = parseInt(col, 16); + let r = (num >> 16) + amount; + let g = (num >> 8 & 0x00FF) + amount; + let b = (num & 0x0000FF) + amount; + r = r > 255 ? 255 : r < 0 ? 0 : r; + g = g > 255 ? 255 : g < 0 ? 0 : g; + b = b > 255 ? 255 : b < 0 ? 0 : b; + return (usePound ? '#' : '') + (r << 16 | g << 8 | b).toString(16).padStart(6, '0'); + } + + window.CustomerAgentWidget = { init: createChatWidget }; +})(); \ No newline at end of file diff --git a/client/public/index.html b/client/public/index.html new file mode 100644 index 0000000000000000000000000000000000000000..26ffd4f7e830427956e7fc14ad19ee5c7f704648 --- /dev/null +++ b/client/public/index.html @@ -0,0 +1,13 @@ + + + + + + + Customer Agent + + +
+ + + \ No newline at end of file diff --git a/client/src/App.jsx b/client/src/App.jsx new file mode 100644 index 0000000000000000000000000000000000000000..210464134dbdd8f492e5454c05aee69c7bc953f9 --- /dev/null +++ b/client/src/App.jsx @@ -0,0 +1,157 @@ +import React, { lazy, Suspense } from 'react'; +import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { Toaster } from 'react-hot-toast'; +import { AuthProvider, useAuth } from './context/AuthContext'; +import { WebsiteProvider } from './context/WebsiteContext'; +import Layout from './components/Layout'; +import PublicLayout from './components/PublicLayout'; + +// Auth Pages (Lazy) +const Login = lazy(() => import('./pages/Login')); +const Register = lazy(() => import('./pages/Register')); + +// Dashboard Pages (Lazy) +const Dashboard = lazy(() => import('./pages/Dashboard')); +const Settings = lazy(() => import('./pages/Settings')); +const ChatManagement = lazy(() => import('./pages/ChatManagement')); +const ContentManager = lazy(() => import('./pages/ContentManager')); +const UnansweredQuestions = lazy(() => import('./pages/UnansweredQuestions')); +const FAQManagement = lazy(() => import('./pages/FAQManagement')); +const WebsiteManagement = lazy(() => import('./pages/WebsiteManagement')); +const UserManagement = lazy(() => import('./pages/UserManagement')); +const PatientTimeline = lazy(() => import('./pages/PatientTimeline')); +const OnboardingSuccess = lazy(() => import('./pages/OnboardingSuccess')); +const CreateFirstWebsite = lazy(() => import('./pages/CreateFirstWebsite')); +const NotificationCenter = lazy(() => import('./pages/NotificationCenter')); +const NotificationSettings = lazy(() => import('./pages/NotificationSettings')); + +// Public Pages (Lazy) +const Home = lazy(() => import('./pages/Home')); +const Pricing = lazy(() => import('./pages/Pricing')); +const About = lazy(() => import('./pages/About')); +const Contact = lazy(() => import('./pages/Contact')); +const Privacy = lazy(() => import('./pages/Privacy')); +const Terms = lazy(() => import('./pages/Terms')); +const CookiePolicy = lazy(() => import('./pages/CookiePolicy')); +const GDPR = lazy(() => import('./pages/GDPR')); +const Security = lazy(() => import('./pages/Security')); +const HelpCenter = lazy(() => import('./pages/HelpCenter')); +const Blog = lazy(() => import('./pages/Blog')); +const BlogDetail = lazy(() => import('./pages/BlogDetail')); + +import './index.css'; + +const queryClient = new QueryClient(); + +const LoadingSpinner = () => ( +
+
+
+); + +const ProtectedRoute = ({ children }) => { + const { user, loading } = useAuth(); + + if (loading) { + return ; + } + + if (!user) { + return ; + } + + return children; +}; + +const PublicRoute = ({ children }) => { + const { user, loading } = useAuth(); + + if (loading) { + return ; + } + + if (user) { + return ; + } + + return children; +}; + +function App() { + return ( + + + + + + }> + + {/* Public Routes */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + } + /> + + + + } + /> + + + {/* Protected Dashboard Routes */} + + + + } + > + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + {/* Catch all */} + } /> + + + + + + + ); +} + +export default App; \ No newline at end of file diff --git a/client/src/api/axiosConfig.js b/client/src/api/axiosConfig.js new file mode 100644 index 0000000000000000000000000000000000000000..159a1e34bdbd1f25486cc820d5c467fc9447af1f --- /dev/null +++ b/client/src/api/axiosConfig.js @@ -0,0 +1,25 @@ +import axios from 'axios'; + +const api = axios.create({ + baseURL: '/api', + withCredentials: true, + headers: { + 'Content-Type': 'application/json', + }, +}); + +// Add a request interceptor to attach the token +api.interceptors.request.use( + (config) => { + const token = localStorage.getItem('access_token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +export default api; \ No newline at end of file diff --git a/client/src/api/leadsApi.js b/client/src/api/leadsApi.js new file mode 100644 index 0000000000000000000000000000000000000000..aff3d0dad6f56c50ca2388f46e71101494ab56a1 --- /dev/null +++ b/client/src/api/leadsApi.js @@ -0,0 +1,35 @@ +import axios from './axiosConfig'; + +/** + * Leads API Service + * Handles all patient/lead profile data requests + */ + +/** + * Get all lead profiles for the current user's website + * @returns {Promise} Array of lead profiles + */ +export const getLeadProfiles = async () => { + try { + const response = await axios.get('/api/leads/profiles'); + return response.data.profiles || []; + } catch (error) { + console.error('Error fetching lead profiles:', error); + throw error; + } +}; + +/** + * Get detailed timeline for a specific patient + * @param {string} email - Patient email address + * @returns {Promise} Patient profile and timeline data + */ +export const getPatientTimeline = async (email) => { + try { + const response = await axios.get(`/api/leads/profile/${encodeURIComponent(email)}`); + return response.data; + } catch (error) { + console.error('Error fetching patient timeline:', error); + throw error; + } +}; diff --git a/client/src/components/AgentTakeover.jsx b/client/src/components/AgentTakeover.jsx new file mode 100644 index 0000000000000000000000000000000000000000..48932165794d97fc46e687e32b27b6d289805baf --- /dev/null +++ b/client/src/components/AgentTakeover.jsx @@ -0,0 +1,242 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { X, Send, Loader2 } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import axios from '../api/axiosConfig'; + +export default function AgentTakeover({ sessionId, visitorName, onClose }) { + const [message, setMessage] = useState(''); + const [messages, setMessages] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [isSending, setIsSending] = useState(false); + const [socket, setSocket] = useState(null); + const messagesEndRef = useRef(null); + + useEffect(() => { + // Load session history + loadSessionHistory(); + + // Connect WebSocket (simplified - you'll need socket.io-client) + const ws = new WebSocket(`ws://localhost:8000/ws/session/${sessionId}`); + + ws.onopen = () => { + // Send takeover event + ws.send(JSON.stringify({ + type: 'takeover', + session_id: sessionId, + agent_id: 'current_user_id' // Get from auth context + })); + }; + + ws.onmessage = (event) => { + const data = JSON.parse(event.data); + if (data.type === 'visitor_message') { + setMessages(prev => [...prev, { + id: Date.now(), + message: data.message, + from: 'visitor', + timestamp: new Date() + }]); + } + }; + + setSocket(ws); + + return () => { + if (ws) { + ws.close(); + } + }; + }, [sessionId]); + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const loadSessionHistory = async () => { + try { + const response = await axios.get(`/api/chat/sessions/${sessionId}/messages`); + setMessages(response.data.map(msg => ({ + id: msg.id, + message: msg.message, + from: msg.is_from_visitor ? 'visitor' : msg.is_from_ai ? 'ai' : 'agent', + timestamp: new Date(msg.created_at) + }))); + setIsLoading(false); + } catch (error) { + console.error('Failed to load session history:', error); + setIsLoading(false); + } + }; + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + const sendMessage = async () => { + if (!message.trim() || isSending || !socket) return; + + setIsSending(true); + + const newMessage = { + id: Date.now(), + message: message, + from: 'agent', + timestamp: new Date() + }; + + // Optimistic update + setMessages(prev => [...prev, newMessage]); + + // Send via WebSocket + socket.send(JSON.stringify({ + type: 'agent_message', + session_id: sessionId, + message: message, + agent_id: 'current_user_id' + })); + + setMessage(''); + setIsSending(false); + }; + + const handleKeyPress = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + }; + + const saveAsKnowledge = async (messageId) => { + try { + await axios.post(`/api/knowledge/save`, { + message_id: messageId, + session_id: sessionId + }); + alert('Saved to knowledge base!'); + } catch (error) { + console.error('Failed to save:', error); + } + }; + + return ( +
+ + {/* Header */} +
+
+

Live Chat Session

+

+ Chatting with {visitorName || 'Anonymous'} • Session #{sessionId} +

+
+ +
+ + {/* Messages */} +
+ {isLoading ? ( +
+ +
+ ) : ( + <> + + {messages.map((msg) => ( + +
+
+ + {msg.from === 'visitor' ? 'Visitor' : msg.from === 'ai' ? '🤖 AI' : 'You'} + + + {msg.timestamp.toLocaleTimeString()} + +
+

{msg.message}

+ + {/* Save as knowledge button for agent messages */} + {msg.from === 'agent' && ( + + )} +
+
+ ))} +
+
+ + )} +
+ + {/* Input */} +
+
+