diff --git a/apps/interactive-journal/.gitignore b/apps/interactive-journal/.gitignore new file mode 100644 index 0000000..6e4100c --- /dev/null +++ b/apps/interactive-journal/.gitignore @@ -0,0 +1,42 @@ +# Environment files +.env +.env.local +.env.*.local + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +venv/ +.venv/ +env/ +.eggs/ +*.egg-info/ +.pytest_cache/ + +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +dist/ +build/ +*.local + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +logs/ diff --git a/apps/interactive-journal/README.md b/apps/interactive-journal/README.md new file mode 100644 index 0000000..83f3fd0 --- /dev/null +++ b/apps/interactive-journal/README.md @@ -0,0 +1,40 @@ +## Setup + +### Backend + +```bash +cd backend + +# Create virtual environment +python -m venv venv +source venv/bin/activate + +# Install dependencies +pip install -r requirements.txt + +# Create .env file +cp .env.example .env +# Edit .env with your values: +# MONGODB_URI=your-mongodb-connection-string +# ANTHROPIC_API_KEY=your-anthropic-api-key +# VOYAGE_API_KEY=your-voyage-api-key + +# Run the server +uvicorn app.main:app --reload +``` + +Backend runs at http://localhost:8000 + +### Frontend + +```bash +cd frontend + +# Install dependencies +npm install + +# Run the dev server +npm run dev +``` + +Frontend runs at http://localhost:5173 diff --git a/apps/interactive-journal/backend/.env.example b/apps/interactive-journal/backend/.env.example new file mode 100644 index 0000000..f210bd9 --- /dev/null +++ b/apps/interactive-journal/backend/.env.example @@ -0,0 +1,14 @@ +# MongoDB Atlas connection string +MONGODB_URI=your-mongodb-connection-string-here + +# Database name +DATABASE_NAME=memoir + +# Anthropic API key +ANTHROPIC_API_KEY=your-anthropic-api-key-here + +# Anthropic model to use +ANTHROPIC_MODEL=claude-sonnet-4-5 + +# Voyage AI API key (for V2 semantic search) +VOYAGE_API_KEY=your-voyage-api-key-here diff --git a/apps/interactive-journal/backend/app/__init__.py b/apps/interactive-journal/backend/app/__init__.py new file mode 100644 index 0000000..3db73c2 --- /dev/null +++ b/apps/interactive-journal/backend/app/__init__.py @@ -0,0 +1 @@ +# Memoir - Interactive Journaling App diff --git a/apps/interactive-journal/backend/app/config.py b/apps/interactive-journal/backend/app/config.py new file mode 100644 index 0000000..72c40ab --- /dev/null +++ b/apps/interactive-journal/backend/app/config.py @@ -0,0 +1,25 @@ +import os + +# User config +USER_ID = "Apoorva" + +# MongoDB config +MONGODB_URI = os.getenv("MONGODB_URI", "mongodb://localhost:27017") +DATABASE_NAME = os.getenv("DATABASE_NAME", "memoir") + +# Anthropic config +ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") +ANTHROPIC_MODEL = os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-5") + +# Voyage AI config +VOYAGE_API_KEY = os.getenv("VOYAGE_API_KEY") +VOYAGE_MULTIMODAL_MODEL = "voyage-multimodal-3.5" +VOYAGE_TEXT_MODEL = "voyage-3-large" + +# Vector search config +VECTOR_INDEX_NAME = "vector_index" +VECTOR_DIMENSIONS = 1024 +VECTOR_NUM_CANDIDATES = 100 + +# Image config +IMAGE_SIZE = (1024, 1024) diff --git a/apps/interactive-journal/backend/app/main.py b/apps/interactive-journal/backend/app/main.py new file mode 100644 index 0000000..ddd17b5 --- /dev/null +++ b/apps/interactive-journal/backend/app/main.py @@ -0,0 +1,59 @@ +from dotenv import load_dotenv + +load_dotenv(override=True) + +import logging +from contextlib import asynccontextmanager + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.routers import routes +from app.services.mongodb import close_db, connect_db + +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger(__name__) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Startup + logger.info("Starting Memoir API...") + connect_db() + logger.info("Memoir API started successfully") + yield + # Shutdown + logger.info("Shutting down Memoir API...") + close_db() + + +app = FastAPI( + title="Memoir", + description="AI-powered interactive journaling application", + version="1.0.0", + lifespan=lifespan, +) + +# CORS middleware for frontend +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:5173"], # Vite default port + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include routers +app.include_router(routes.router, prefix="/api/entries", tags=["entries"]) + + +@app.get("/") +def root(): + return {"message": "Welcome to Memoir API"} + + +@app.get("/health") +def health_check(): + return {"status": "healthy"} diff --git a/apps/interactive-journal/backend/app/routers/__init__.py b/apps/interactive-journal/backend/app/routers/__init__.py new file mode 100644 index 0000000..22da696 --- /dev/null +++ b/apps/interactive-journal/backend/app/routers/__init__.py @@ -0,0 +1 @@ +# Routers module diff --git a/apps/interactive-journal/backend/app/routers/helpers.py b/apps/interactive-journal/backend/app/routers/helpers.py new file mode 100644 index 0000000..f4acd06 --- /dev/null +++ b/apps/interactive-journal/backend/app/routers/helpers.py @@ -0,0 +1,229 @@ +import base64 +import logging +import uuid +from datetime import datetime, timedelta +from io import BytesIO +from pathlib import Path + +from fastapi import UploadFile +from PIL import Image + +from app.config import IMAGE_SIZE, USER_ID, VECTOR_INDEX_NAME, VECTOR_NUM_CANDIDATES +from app.services.anthropic import extract_memories +from app.services.voyage import get_multimodal_embedding, get_text_embedding + +logger = logging.getLogger(__name__) + +# Store images in frontend's public folder for direct access +UPLOADS_DIR = ( + Path(__file__).parent.parent.parent.parent / "frontend" / "public" / "uploads" +) +UPLOADS_DIR.mkdir(parents=True, exist_ok=True) + + +def save_user_message( + db, entry_id: str, content: str | Path, version: int, msg_date: datetime +) -> None: + """Save a user message (text or image) with its embedding.""" + message = { + "entry_id": entry_id, + "user_id": USER_ID, + "role": "user", + "version": version, + "created_at": msg_date, + } + + if version == 2: + is_image = isinstance(content, Path) + mode = "image" if is_image else "text" + message["embedding"] = get_multimodal_embedding( + content, mode=mode, input_type="document" + ) + if is_image: + message["image"] = content.name + else: + message["content"] = content + else: + message["embedding"] = get_text_embedding(content, input_type="document") + message["content"] = content + + db.messages.insert_one(message) + logger.info(f"Saved message for entry {entry_id}") + + +def extract_and_save_memories( + db, entry_id: str, conversation: list[dict], entry_date: datetime +) -> None: + """Extract memories from conversation and save them.""" + context = "\n".join(f"{msg['role']}: {msg['content']}" for msg in conversation) + memories = extract_memories(context) + + if memories: + memory_docs = [ + { + "user_id": USER_ID, + "entry_id": entry_id, + "content": memory_content, + "embedding": get_text_embedding(memory_content, input_type="document"), + "created_at": entry_date, + } + for memory_content in memories + ] + db.memories.insert_many(memory_docs) + logger.info(f"Extracted and saved {len(memories)} memories: {memories}") + + +def retrieve_relevant_memories(db, query: str) -> list[str]: + """Retrieve relevant memories via vector search.""" + query_embedding = get_text_embedding(query, input_type="query") + pipeline = [ + { + "$vectorSearch": { + "index": VECTOR_INDEX_NAME, + "path": "embedding", + "queryVector": query_embedding, + "numCandidates": VECTOR_NUM_CANDIDATES, + "limit": 10, + "filter": {"user_id": USER_ID}, + } + }, + {"$project": {"content": 1, "score": {"$meta": "vectorSearchScore"}}}, + ] + results = list(db.memories.aggregate(pipeline)) + memories = [r["content"] for r in results] + logger.info(f"Retrieved {len(memories)} memories for context") + return memories + + +def get_conversation_history( + db, entry_id: str, include_images: bool = True +) -> list[dict]: + """Get conversation history for an entry.""" + history = list( + db.messages.find( + {"entry_id": entry_id}, {"role": 1, "content": 1, "image": 1, "_id": 0} + ).sort("created_at", 1) + ) + + messages = [] + for msg in history: + if msg.get("content"): + messages.append({"role": msg["role"], "content": msg["content"]}) + elif msg.get("image") and include_images: + image_path = UPLOADS_DIR / msg["image"] + if image_path.exists(): + messages.append( + { + "role": msg["role"], + "content": [image_to_base64(image_path)], + } + ) + return messages + + +def image_to_base64(image_path: Path) -> dict: + """Convert an image file to Claude's base64 format, resizing to fit limits.""" + with Image.open(image_path) as img: + img = img.resize(IMAGE_SIZE, Image.Resampling.LANCZOS) + buffer = BytesIO() + img.save(buffer, format="JPEG", quality=85) + data = base64.standard_b64encode(buffer.getvalue()).decode("utf-8") + + return { + "type": "image", + "source": {"type": "base64", "media_type": "image/jpeg", "data": data}, + } + + +def save_assistant_message(db, entry_id: str, content: str, msg_date: datetime) -> None: + """Save an assistant response message.""" + db.messages.insert_one( + { + "entry_id": entry_id, + "role": "assistant", + "content": content, + "created_at": msg_date, + } + ) + logger.info(f"Saved AI response for entry {entry_id}") + + +def save_image_file(image_file: UploadFile) -> Path: + """Save uploaded image file.""" + filename = f"{uuid.uuid4()}{Path(image_file.filename).suffix or '.jpg'}" + image_path = UPLOADS_DIR / filename + with open(image_path, "wb") as f: + f.write(image_file.file.read()) + return image_path + + +def get_monthly_filter(user_id: str) -> dict: + """Get common filter for monthly v2 entries.""" + thirty_days_ago = datetime.now() - timedelta(days=30) + return { + "user_id": user_id, + "version": 2, + "created_at": {"$gte": thirty_days_ago}, + } + + +def get_total_entries(db, user_id: str) -> int: + """Get total entries count for past 30 days.""" + return db.entries.count_documents(get_monthly_filter(user_id)) + + +def get_longest_streak(db, user_id: str) -> int: + """Get longest consecutive days streak in past 30 days.""" + pipeline = [ + {"$match": get_monthly_filter(user_id)}, + {"$project": {"date": {"$dateTrunc": {"date": "$created_at", "unit": "day"}}}}, + {"$group": {"_id": "$date"}}, + {"$sort": {"_id": 1}}, + ] + dates = [doc["_id"] for doc in db.entries.aggregate(pipeline)] + + if not dates: + return 0 + + longest = current = 1 + for i in range(1, len(dates)): + if (dates[i] - dates[i - 1]).days == 1: + current += 1 + longest = max(longest, current) + else: + current = 1 + + return longest + + +def get_mood_distribution(db, user_id: str) -> dict: + """Get sentiment distribution for past 30 days.""" + filter = get_monthly_filter(user_id) + filter["sentiment"] = {"$exists": True} + pipeline = [ + {"$match": filter}, + {"$group": {"_id": "$sentiment", "count": {"$sum": 1}}}, + ] + results = list(db.entries.aggregate(pipeline)) + counts = {r["_id"]: r["count"] for r in results} + total = sum(counts.values()) or 1 + return { + "positive": round(counts.get("positive", 0) / total * 100), + "neutral": round(counts.get("neutral", 0) / total * 100), + "mixed": round(counts.get("mixed", 0) / total * 100), + "negative": round(counts.get("negative", 0) / total * 100), + } + + +def get_themes(db, user_id: str) -> list[dict]: + """Get all themes with counts for past 30 days.""" + filter = get_monthly_filter(user_id) + filter["themes"] = {"$exists": True} + pipeline = [ + {"$match": filter}, + {"$unwind": "$themes"}, + {"$group": {"_id": "$themes", "count": {"$sum": 1}}}, + {"$sort": {"count": -1}}, + ] + results = list(db.entries.aggregate(pipeline)) + return [{"theme": r["_id"], "count": r["count"]} for r in results] diff --git a/apps/interactive-journal/backend/app/routers/routes.py b/apps/interactive-journal/backend/app/routers/routes.py new file mode 100644 index 0000000..9aaa427 --- /dev/null +++ b/apps/interactive-journal/backend/app/routers/routes.py @@ -0,0 +1,249 @@ +import logging +from datetime import datetime, timedelta +from typing import Optional + +from bson import ObjectId +from fastapi import APIRouter, File, Form, UploadFile +from fastapi.responses import StreamingResponse + +from app.config import USER_ID, VECTOR_INDEX_NAME, VECTOR_NUM_CANDIDATES +from app.routers.helpers import ( + extract_and_save_memories, + get_conversation_history, + get_longest_streak, + get_mood_distribution, + get_themes, + get_total_entries, + image_to_base64, + retrieve_relevant_memories, + save_assistant_message, + save_image_file, + save_user_message, +) +from app.services.anthropic import ( + analyze_entry, + generate_journal_prompt, + generate_response, +) +from app.services.mongodb import get_database +from app.services.voyage import get_multimodal_embedding, get_text_embedding + +logger = logging.getLogger(__name__) + +router = APIRouter() + + +@router.post("/") +def create_entry(version: int = Form(1), entry_date: str = Form(...)): + db = get_database() + entry_dt = datetime.fromisoformat(entry_date) + entry_data = { + "user_id": USER_ID, + "title": entry_dt.strftime("%d/%m/%Y"), + "version": version, + "created_at": entry_dt, + } + result = db.entries.insert_one(entry_data) + logger.info(f"Created entry {result.inserted_id} for user {USER_ID}") + return {"_id": str(result.inserted_id)} + + +@router.post("/{entry_id}/messages") +def send_message( + entry_id: str, + content: Optional[str] = Form(None), + images: list[UploadFile] = File([]), + version: int = Form(1), + entry_date: Optional[str] = Form(None), +): + db = get_database() + is_v2 = version == 2 + msg_date = datetime.fromisoformat(entry_date) + + # Save image files to disk before streaming (file handles close after) + image_paths = [save_image_file(image) for image in images] + + # Build current message (text, images, or both) + messages = [] + if content: + messages.append({"type": "text", "text": content}) + for path in image_paths: + messages.append(image_to_base64(path)) + + # Get conversation history and add current message + conversation = get_conversation_history(db, entry_id) + if messages: + conversation.append({"role": "user", "content": messages}) + + # Retrieve relevant memories for context (V2 only) + memories = retrieve_relevant_memories(db, content) if is_v2 and content else [] + + def respond_and_save(): + # Stream response to user + response_text = [] + for chunk in generate_response(conversation, memories=memories): + response_text.append(chunk) + yield chunk + + # Save messages to DB after response completes + if content: + save_user_message(db, entry_id, content, version, msg_date) + for path in image_paths: + save_user_message(db, entry_id, path, version, msg_date) + save_assistant_message(db, entry_id, "".join(response_text), msg_date) + + return StreamingResponse(respond_and_save(), media_type="text/plain") + + +@router.get("/search") +def search_entries(q: str, version: int = 1): + """Search entries using vector search, grouped by entry.""" + db = get_database() + logger.info(f"Searching entries with query: {q[:50]}... (version={version})") + + # Use appropriate embedding based on version + if version == 2: + query_embedding = get_multimodal_embedding(q, mode="text", input_type="query") + else: + query_embedding = get_text_embedding(q, input_type="query") + + pipeline = [ + { + "$vectorSearch": { + "index": VECTOR_INDEX_NAME, + "path": "embedding", + "queryVector": query_embedding, + "numCandidates": VECTOR_NUM_CANDIDATES, + "limit": 20, + "filter": {"user_id": USER_ID, "version": version}, + } + }, + { + "$project": { + "entry_id": 1, + "content": 1, + "image": 1, + "created_at": 1, + "score": {"$meta": "vectorSearchScore"}, + } + }, + { + "$group": { + "_id": "$entry_id", + "content": {"$first": "$content"}, + "image": {"$first": "$image"}, + "created_at": {"$first": "$created_at"}, + "score": {"$max": "$score"}, + } + }, + {"$sort": {"score": -1}}, + {"$limit": 5}, + ] + + results = list(db.messages.aggregate(pipeline)) + for result in results: + result["_id"] = str(result["_id"]) + + logger.info(f"Search returned {len(results)} entries") + return results + + +@router.post("/{entry_id}/analyze") +def save_entry(entry_id: str, entry_date: str = Form(...)): + """Analyze entry for sentiment/themes and extract memories.""" + db = get_database() + conversation = get_conversation_history(db, entry_id, include_images=False) + + if not conversation: + return {"error": "No messages in entry"} + + # Analyze sentiment and themes + analysis = analyze_entry(conversation) + db.entries.update_one( + {"_id": ObjectId(entry_id)}, + {"$set": {"sentiment": analysis["sentiment"], "themes": analysis["themes"]}}, + ) + + # Extract memories from full conversation + extract_and_save_memories( + db, entry_id, conversation, datetime.fromisoformat(entry_date) + ) + + +@router.get("/") +def get_entries(version: int = 1): + db = get_database() + query = {"user_id": USER_ID, "version": version} + entries = list(db.entries.find(query).sort("created_at", -1)) + for entry in entries: + entry["_id"] = str(entry["_id"]) + return entries + + +@router.post("/generate-prompt") +def generate_prompt(entry_id: str = Form(...), entry_date: str = Form(...)): + """Generate a journal prompt based on the last month's memories.""" + db = get_database() + one_month_ago = datetime.now() - timedelta(days=30) + + memories = list( + db.memories.find( + {"user_id": USER_ID, "created_at": {"$gte": one_month_ago}}, + {"content": 1, "created_at": 1, "_id": 0}, + ) + ) + memory_contents = [ + f"Date: {m['created_at'].strftime('%Y-%m-%d')}, Memory: {m['content']}" + for m in memories + ] + logger.info(f"Found {len(memory_contents)} memories from the last month") + + prompt = generate_journal_prompt(memory_contents) + + # Save the prompt as an assistant message + msg_date = datetime.fromisoformat(entry_date) + prompt_msg = { + "entry_id": entry_id, + "role": "assistant", + "content": prompt, + "created_at": msg_date, + } + db.messages.insert_one(prompt_msg) + logger.info(f"Saved generated prompt for entry {entry_id}") + + return {"prompt": prompt} + + +@router.get("/{entry_id}/messages") +def get_messages(entry_id: str): + db = get_database() + messages = list(db.messages.find({"entry_id": entry_id}).sort("created_at", 1)) + for msg in messages: + msg["_id"] = str(msg["_id"]) + msg.pop("embedding", None) + return messages + + +@router.get("/insights") +def get_insights(): + """Get user insights: stats, mood distribution, and themes.""" + db = get_database() + return { + "total_entries": get_total_entries(db, USER_ID), + "longest_streak": get_longest_streak(db, USER_ID), + "mood": get_mood_distribution(db, USER_ID), + "themes": get_themes(db, USER_ID), + } + + +@router.delete("/{entry_id}") +def delete_entry(entry_id: str): + db = get_database() + db.entries.delete_one({"_id": ObjectId(entry_id)}) + messages = db.messages.delete_many({"entry_id": entry_id}) + memories = db.memories.delete_many({"entry_id": entry_id}) + logger.info( + f"Deleted entry {entry_id}: " + f"{messages.deleted_count} messages, {memories.deleted_count} memories" + ) + return {"deleted": True} diff --git a/apps/interactive-journal/backend/app/services/__init__.py b/apps/interactive-journal/backend/app/services/__init__.py new file mode 100644 index 0000000..0557eb6 --- /dev/null +++ b/apps/interactive-journal/backend/app/services/__init__.py @@ -0,0 +1 @@ +# Services module diff --git a/apps/interactive-journal/backend/app/services/anthropic.py b/apps/interactive-journal/backend/app/services/anthropic.py new file mode 100644 index 0000000..3215f5d --- /dev/null +++ b/apps/interactive-journal/backend/app/services/anthropic.py @@ -0,0 +1,119 @@ +import logging +from datetime import datetime +from typing import Literal, Optional + +import anthropic +from pydantic import BaseModel + +from app.config import ANTHROPIC_API_KEY, ANTHROPIC_MODEL +from app.services.prompts import ( + INSIGHTS_PROMPT, + JOURNAL_SYSTEM_PROMPT, + MEMORY_EXTRACTION_PROMPT, + PROMPT_GENERATOR, +) + +logger = logging.getLogger(__name__) + +client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY) + + +class MemoriesOutput(BaseModel): + memories: list[str] + + +class EntryAnalysis(BaseModel): + sentiment: Literal["positive", "negative", "neutral", "mixed"] + themes: list[str] + + +def extract_memories(user_message: str) -> list[str]: + """Extract memories/insights from a user's journal entry.""" + logger.info(f"Extracting memories using {ANTHROPIC_MODEL}") + + try: + response = client.beta.messages.parse( + model=ANTHROPIC_MODEL, + max_tokens=500, + temperature=0.8, + betas=["structured-outputs-2025-11-13"], + system=MEMORY_EXTRACTION_PROMPT, + messages=[{"role": "user", "content": user_message}], + output_format=MemoriesOutput, + ) + memories = response.parsed_output.memories + logger.info(f"Extracted {len(memories)} memories") + return memories + except Exception as e: + logger.error(f"Failed to extract memories: {e}") + return [] + + +def analyze_entry(conversation: list[dict]) -> dict: + """Analyze a journal entry for sentiment and themes.""" + logger.info(f"Analyzing entry with {len(conversation)} messages") + + content = "\n".join(f"{msg['role']}: {msg['content']}" for msg in conversation) + + try: + response = client.beta.messages.parse( + model=ANTHROPIC_MODEL, + max_tokens=200, + temperature=0.8, + betas=["structured-outputs-2025-11-13"], + system=INSIGHTS_PROMPT, + messages=[{"role": "user", "content": content}], + output_format=EntryAnalysis, + ) + result = { + "sentiment": response.parsed_output.sentiment, + "themes": response.parsed_output.themes, + } + logger.info(f"Entry analysis: {result}") + return result + except Exception as e: + logger.error(f"Failed to analyze entry: {e}") + return {"sentiment": "neutral", "themes": []} + + +def generate_response(messages: list[dict], memories: Optional[list[str]] = None): + """Generate a streaming response using Anthropic's Claude.""" + logger.info( + f"Generating response using {ANTHROPIC_MODEL} with {len(memories) if memories else 0} memories" + ) + + system_prompt = JOURNAL_SYSTEM_PROMPT + if memories: + memory_context = "\n".join(f"- {m}" for m in memories) + system_prompt += f"\n\nRelevant memories about this user:\n{memory_context}\n\nUse these memories to provide more personalized and contextual responses when relevant." + + with client.messages.stream( + model=ANTHROPIC_MODEL, + max_tokens=500, + temperature=0.8, + system=system_prompt, + messages=messages, + ) as stream: + yield from stream.text_stream + + +def generate_journal_prompt(memories: list[str]) -> str: + """Generate a reflective journal prompt based on past memories.""" + logger.info(f"Generating journal prompt from {len(memories)} memories") + + if not memories: + return "What's on your mind today?" + + today = datetime.now().strftime("%Y-%m-%d") + memory_context = "\n".join(f"- {m}" for m in memories) + user_content = f"Today's date: {today}\n\nMemories:\n{memory_context}" + + response = client.messages.create( + model=ANTHROPIC_MODEL, + max_tokens=150, + temperature=0.8, + system=PROMPT_GENERATOR, + messages=[{"role": "user", "content": user_content}], + ) + + return response.content[0].text diff --git a/apps/interactive-journal/backend/app/services/mongodb.py b/apps/interactive-journal/backend/app/services/mongodb.py new file mode 100644 index 0000000..feee948 --- /dev/null +++ b/apps/interactive-journal/backend/app/services/mongodb.py @@ -0,0 +1,81 @@ +import logging + +from pymongo import MongoClient +from pymongo.errors import CollectionInvalid, OperationFailure + +from app.config import ( + DATABASE_NAME, + MONGODB_URI, + VECTOR_DIMENSIONS, + VECTOR_INDEX_NAME, +) + +logger = logging.getLogger(__name__) + +client: MongoClient = None +db = None + + +def connect_db(): + global client, db + client = MongoClient(MONGODB_URI) + db = client[DATABASE_NAME] + client.admin.command("ping") + logger.info(f"Connected to MongoDB: {DATABASE_NAME}") + setup_collections() + setup_indexes() + + +def setup_collections(): + for name in ["entries", "messages", "memories"]: + try: + db.create_collection(name) + logger.info(f"Created collection: {name}") + except CollectionInvalid: + logger.info(f"Collection already exists: {name}") + + +def setup_indexes(): + create_vector_index("messages", filter_paths=["user_id", "version"]) + create_vector_index("memories", filter_paths=["user_id"]) + + +def create_vector_index(collection_name: str, filter_paths: list[str]): + collection = db[collection_name] + + existing = list(collection.list_search_indexes()) + if any(idx.get("name") == VECTOR_INDEX_NAME for idx in existing): + logger.info(f"Vector index already exists on {collection_name}") + return + + fields = [ + { + "type": "vector", + "numDimensions": VECTOR_DIMENSIONS, + "path": "embedding", + "similarity": "cosine", + }, + ] + [{"type": "filter", "path": p} for p in filter_paths] + + try: + collection.create_search_index( + model={ + "name": VECTOR_INDEX_NAME, + "type": "vectorSearch", + "definition": {"fields": fields}, + } + ) + logger.info(f"Created vector index on {collection_name}") + except OperationFailure as e: + logger.error(f"Failed to create index on {collection_name}: {e}") + + +def close_db(): + global client + if client: + client.close() + logger.info("MongoDB connection closed") + + +def get_database(): + return db diff --git a/apps/interactive-journal/backend/app/services/prompts.py b/apps/interactive-journal/backend/app/services/prompts.py new file mode 100644 index 0000000..459aadf --- /dev/null +++ b/apps/interactive-journal/backend/app/services/prompts.py @@ -0,0 +1,47 @@ +JOURNAL_SYSTEM_PROMPT = """You are a thoughtful and empathetic AI journaling companion called Memoir. +Your role is to help users reflect on their thoughts, feelings, and experiences through conversation. + +Guidelines: +- Ask thoughtful follow-up questions to help users explore their thoughts deeper +- Be supportive and non-judgmental +- Help users identify patterns and insights in their reflections +- Keep responses concise but meaningful +- Encourage self-reflection without being preachy +- If users share something difficult, acknowledge their feelings first + +Remember: You're a journaling companion, not a therapist. Focus on reflection and exploration.""" + +MEMORY_EXTRACTION_PROMPT = """You are a memory extraction system. Analyze the user's journal entry and extract meaningful memories, insights, and facts about the user. + +Extract information such as: +- Personal facts (relationships, work, hobbies, preferences) +- Emotional patterns and feelings +- Goals, aspirations, and plans +- Significant events or experiences +- Insights and realizations + +Return a JSON array of memory strings. Each memory should be a concise, standalone statement ending in a period. +If no meaningful memories can be extracted, return an empty array. + +Example output: +["User has a sister named Sarah.", "User feels anxious about their job interview next week.", "User enjoys morning walks."]""" + +PROMPT_GENERATOR = """Based on the user's past memories, generate a thoughtful journaling prompt that encourages deeper reflection. + +Each memory includes its date. Use this to frame your prompt appropriately (e.g., "Last week you mentioned..." or "A few weeks ago you wrote about..."). Today's date is provided below. + +Pick one memory that seems meaningful and ask an open-ended question about it. Keep the prompt concise (1-2 sentences). + +Return only the prompt, nothing else.""" + +INSIGHTS_PROMPT = """Analyze this journal entry conversation and extract: +1. Overall sentiment (positive, negative, neutral, or mixed) +2. Key themes discussed (2-4 short themes) + +Return a JSON object with this structure: +{ + "sentiment": "positive" | "negative" | "neutral" | "mixed", + "themes": ["theme1", "theme2", ...] +} + +Keep themes concise (1-3 words each). Examples: "work stress", "family", "self-improvement", "gratitude".""" diff --git a/apps/interactive-journal/backend/app/services/voyage.py b/apps/interactive-journal/backend/app/services/voyage.py new file mode 100644 index 0000000..bf2fdcc --- /dev/null +++ b/apps/interactive-journal/backend/app/services/voyage.py @@ -0,0 +1,67 @@ +import logging +from pathlib import Path + +import voyageai +from PIL import Image + +from app.config import ( + IMAGE_SIZE, + VOYAGE_API_KEY, + VOYAGE_MULTIMODAL_MODEL, + VOYAGE_TEXT_MODEL, +) + +logger = logging.getLogger(__name__) + +vo = voyageai.Client(api_key=VOYAGE_API_KEY) + + +def get_multimodal_embedding( + content: str | Path, mode: str, input_type: str +) -> list[float]: + """ + Generate embeddings using Voyage AI's voyage-multimodal-3.5 model. + + Args: + content: Text string or path to image file + mode (str): Content mode ("image" or "text") + input_type (str): Type of input ("document" or "query") + + Returns: + list[float]: Embedding of the content as a list. + """ + logger.info( + f"Generating multimodal embedding: mode={mode}, input_type={input_type}" + ) + + if mode == "image": + img = Image.open(content) + content = img.resize(IMAGE_SIZE, Image.Resampling.LANCZOS) + + result = vo.multimodal_embed( + inputs=[[content]], model=VOYAGE_MULTIMODAL_MODEL, input_type=input_type + ).embeddings[0] + + logger.debug(f"Generated {len(result)}-dim embedding") + return result + + +def get_text_embedding(text: str, input_type: str = "document") -> list[float]: + """ + Generate text embeddings using Voyage AI's voyage-3-large model. + + Args: + text: Text string to embed + input_type: Type of input ("document" or "query") + + Returns: + list[float]: Embedding of the text as a list. + """ + logger.info(f"Generating text embedding: input_type={input_type}") + + result = vo.embed( + texts=[text], model=VOYAGE_TEXT_MODEL, input_type=input_type + ).embeddings[0] + + logger.debug(f"Generated {len(result)}-dim embedding") + return result diff --git a/apps/interactive-journal/backend/requirements.txt b/apps/interactive-journal/backend/requirements.txt new file mode 100644 index 0000000..4ae2815 --- /dev/null +++ b/apps/interactive-journal/backend/requirements.txt @@ -0,0 +1,7 @@ +fastapi==0.115.6 +uvicorn[standard]==0.34.0 +pymongo==4.10.1 +anthropic==0.75.0 +python-dotenv==1.0.1 +voyageai==0.3.7 +Pillow==11.0.0 diff --git a/apps/interactive-journal/frontend/.gitignore b/apps/interactive-journal/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/apps/interactive-journal/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/interactive-journal/frontend/eslint.config.js b/apps/interactive-journal/frontend/eslint.config.js new file mode 100644 index 0000000..4fa125d --- /dev/null +++ b/apps/interactive-journal/frontend/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + }, + }, +]) diff --git a/apps/interactive-journal/frontend/index.html b/apps/interactive-journal/frontend/index.html new file mode 100644 index 0000000..c20fbd3 --- /dev/null +++ b/apps/interactive-journal/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +
+ + + diff --git a/apps/interactive-journal/frontend/package-lock.json b/apps/interactive-journal/frontend/package-lock.json new file mode 100644 index 0000000..bdafea8 --- /dev/null +++ b/apps/interactive-journal/frontend/package-lock.json @@ -0,0 +1,2872 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "vite": "^7.2.4" + } + }, + "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/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "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.9.4", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.4.tgz", + "integrity": "sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "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.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "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/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.266", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", + "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", + "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "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/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/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/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "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-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/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/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "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/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "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/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/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": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.1" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "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/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/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz", + "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "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/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.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/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/apps/interactive-journal/frontend/package.json b/apps/interactive-journal/frontend/package.json new file mode 100644 index 0000000..1d89f06 --- /dev/null +++ b/apps/interactive-journal/frontend/package.json @@ -0,0 +1,27 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "vite": "^7.2.4" + } +} diff --git a/apps/interactive-journal/frontend/public/vite.svg b/apps/interactive-journal/frontend/public/vite.svg new file mode 100644 index 0000000..ee9fada --- /dev/null +++ b/apps/interactive-journal/frontend/public/vite.svg @@ -0,0 +1 @@ + diff --git a/apps/interactive-journal/frontend/src/App.css b/apps/interactive-journal/frontend/src/App.css new file mode 100644 index 0000000..24e63de --- /dev/null +++ b/apps/interactive-journal/frontend/src/App.css @@ -0,0 +1,1030 @@ +.app { + display: flex; + height: 100vh; + position: relative; + overflow: hidden; + background: #fafafa; +} + +/* Mesh gradient background */ +.app::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: + radial-gradient(at 0% 0%, rgba(6, 182, 212, 0.4) 0%, transparent 50%), + radial-gradient(at 50% 0%, rgba(168, 85, 247, 0.35) 0%, transparent 45%), + radial-gradient(at 100% 0%, rgba(251, 146, 60, 0.5) 0%, transparent 50%), + radial-gradient(at 0% 50%, rgba(34, 197, 94, 0.3) 0%, transparent 40%), + radial-gradient(at 50% 50%, rgba(236, 72, 153, 0.25) 0%, transparent 50%), + radial-gradient(at 100% 50%, rgba(234, 179, 8, 0.4) 0%, transparent 45%), + radial-gradient(at 0% 100%, rgba(59, 130, 246, 0.35) 0%, transparent 50%), + radial-gradient(at 50% 100%, rgba(20, 184, 166, 0.3) 0%, transparent 45%), + radial-gradient(at 100% 100%, rgba(239, 68, 68, 0.35) 0%, transparent 50%); + animation: meshMove 30s ease-in-out infinite alternate; + z-index: 0; + pointer-events: none; +} + +@keyframes meshMove { + 0% { + filter: blur(60px) saturate(150%); + transform: scale(1) rotate(0deg); + } + 50% { + filter: blur(80px) saturate(130%); + transform: scale(1.1) rotate(2deg); + } + 100% { + filter: blur(60px) saturate(150%); + transform: scale(1) rotate(-2deg); + } +} + +/* Sidebar */ +.sidebar { + width: 280px; + background: transparent; + display: flex; + flex-direction: column; + position: relative; + z-index: 10; + padding: 24px 20px; +} + +.sidebar-header { + padding: 8px 4px 24px; +} + +.user-info { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 4px; + margin-top: auto; +} + +.user-avatar { + width: 32px; + height: 32px; + background: linear-gradient(135deg, #8b5cf6, #ec4899); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-weight: 600; + font-size: 14px; +} + +.user-name { + font-size: 14px; + font-weight: 500; + color: #1a1a1a; +} + +.logo { + font-family: 'Sacramento', cursive; + font-size: 2.5rem; + font-weight: 400; + color: #1a1a1a; + letter-spacing: 0.08em; + -webkit-text-stroke: 0.5px #1a1a1a; +} + +.sidebar-section { + margin-bottom: 24px; +} + +.sidebar-section:last-child { + flex: 1; + display: flex; + flex-direction: column; + overflow: visible; +} + +.section-header { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: #6b7280; + padding: 0 4px 12px; +} + +.new-entry-btn { + width: 100%; + padding: 12px 16px; + background: transparent; + color: #374151; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 10px; + font-family: inherit; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 10px; +} + +.new-entry-icon { + font-size: 18px; + font-weight: 400; + color: #6b7280; +} + +.new-entry-btn:hover { + background: rgba(0, 0, 0, 0.04); + border-color: rgba(0, 0, 0, 0.25); +} + +.empty-state { + padding: 16px 4px; + color: #9ca3af; + font-size: 13px; +} + +.entry-list { + flex: 1; + overflow-y: auto; +} + +.entry-item { + padding: 10px 12px; + border-radius: 8px; + cursor: pointer; + font-family: inherit; + font-size: 14px; + font-weight: 400; + color: #374151; + transition: all 0.15s ease; + margin-bottom: 2px; + display: flex; + align-items: center; + justify-content: space-between; + position: relative; +} + +.entry-item:hover { + background: rgba(0, 0, 0, 0.05); +} + +.entry-item.active { + background: rgba(0, 0, 0, 0.08); + font-weight: 500; + color: #1a1a1a; +} + +.entry-item.menu-open { + background: rgba(0, 0, 0, 0.06); +} + +.entry-title { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.menu-btn { + opacity: 0; + background: none; + border: none; + padding: 4px 6px; + cursor: pointer; + font-size: 14px; + color: #9ca3af; + border-radius: 4px; + transition: all 0.15s ease; + line-height: 1; +} + +.entry-item:hover .menu-btn, +.menu-btn.open { + opacity: 1; +} + +.menu-btn:hover, +.menu-btn.open { + color: #374151; + background: rgba(0, 0, 0, 0.08); +} + +.dropdown-menu { + position: fixed; + background: white; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + min-width: 120px; + z-index: 9999; + overflow: hidden; + border: 1px solid rgba(0, 0, 0, 0.08); +} + +.dropdown-item { + width: 100%; + padding: 10px 14px; + border: none; + background: none; + font-family: inherit; + font-size: 13px; + text-align: left; + cursor: pointer; + transition: background 0.15s ease; + display: block; +} + +.dropdown-item:hover { + background: #f3f4f6; +} + +.dropdown-item.delete { + color: #dc2626; +} + +.dropdown-item.delete:hover { + background: #fef2f2; +} + +/* Entry area */ +.entry { + flex: 1; + display: flex; + flex-direction: column; + position: relative; + z-index: 1; + margin: 24px 24px 24px 0; + background: rgba(255, 255, 255, 0.6); + backdrop-filter: blur(30px); + border-radius: 24px; + border: 1px solid rgba(255, 255, 255, 0.7); + box-shadow: 0 8px 40px rgba(0, 0, 0, 0.06); +} + +.entry-empty { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: #6b7280; + font-size: 17px; + font-weight: 500; +} + +.greeting { + font-size: 28px; + font-weight: 400; + color: #374151; + text-align: center; + margin-bottom: 32px; +} + +.messages { + flex: 1; + overflow-y: auto; + padding: 0; + display: flex; + flex-direction: column; +} + +.message { + width: 100%; + padding: 24px 48px; + font-size: 15px; + line-height: 1.75; + border-bottom: 1px solid rgba(0, 0, 0, 0.04); +} + +.message.user { + background: transparent; + color: #1f2937; +} + +.message.assistant { + background: rgba(139, 92, 246, 0.04); + color: #1f2937; +} + +.message-content { + max-width: 720px; + margin: 0 auto; +} + +.message-label { + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 8px; + color: #6b7280; +} + +.message.user .message-label { + color: #8b5cf6; +} + +.message.assistant .message-label { + color: #ec4899; +} + +/* Entry input */ +.entry-input { + padding: 24px 48px 32px; + max-width: 816px; + margin: 0 auto; + width: 100%; +} + +.entry-input-wrapper { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 8px 8px 20px; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 24px; + background: rgba(255, 255, 255, 0.9); + transition: all 0.3s ease; +} + +.entry-input-wrapper:focus-within { + border-color: rgba(139, 92, 246, 0.5); + box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.08); +} + +.entry-input input { + flex: 1; + padding: 12px 0; + border: none; + font-family: inherit; + font-size: 15px; + outline: none; + background: transparent; +} + +.entry-input input::placeholder { + color: #9ca3af; +} + +.send-btn { + padding: 10px; + background: transparent; + color: #374151; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 10px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.send-btn:hover { + background: rgba(0, 0, 0, 0.04); + border-color: rgba(0, 0, 0, 0.25); +} + +.send-btn svg { + display: block; +} + +/* Version toggle */ +.version-toggle { + display: flex; + align-items: center; + gap: 8px; + margin-top: 12px; +} + +.version-label { + font-size: 12px; + font-weight: 500; + color: #9ca3af; + transition: color 0.2s ease; +} + +.version-label.active { + color: #1a1a1a; +} + +.toggle-switch { + width: 44px; + height: 24px; + background: rgba(0, 0, 0, 0.1); + border: none; + border-radius: 12px; + cursor: pointer; + position: relative; + transition: background 0.2s ease; + padding: 0; +} + +.toggle-switch.on { + background: #8b5cf6; +} + +.toggle-knob { + position: absolute; + top: 2px; + left: 2px; + width: 20px; + height: 20px; + background: white; + border-radius: 50%; + transition: transform 0.2s ease; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); +} + +.toggle-switch.on .toggle-knob { + transform: translateX(20px); +} + +/* Sidebar nav */ +.sidebar-nav { + display: flex; + flex-direction: column; + gap: 2px; + padding: 0 4px; + margin-bottom: 16px; +} + +.nav-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + background: transparent; + border: none; + border-radius: 8px; + font-family: inherit; + font-size: 14px; + font-weight: 400; + color: #374151; + cursor: pointer; + transition: all 0.15s ease; + text-align: left; +} + +.nav-item:hover { + background: rgba(0, 0, 0, 0.05); +} + +.nav-item.active { + background: rgba(0, 0, 0, 0.08); + font-weight: 500; +} + +.nav-item svg { + flex-shrink: 0; + color: #6b7280; +} + +/* Search form */ +.search-form { + position: relative; + margin-bottom: 12px; +} + +.search-input { + width: 100%; + padding: 10px 32px 10px 12px; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 8px; + font-family: inherit; + font-size: 13px; + background: rgba(255, 255, 255, 0.6); + outline: none; + transition: all 0.2s ease; +} + +.search-input:focus { + border-color: rgba(139, 92, 246, 0.5); + box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.1); +} + +.search-input::placeholder { + color: #9ca3af; +} + +.search-clear { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + font-size: 18px; + color: #9ca3af; + cursor: pointer; + padding: 0 4px; + line-height: 1; +} + +.search-clear:hover { + color: #6b7280; +} + +.search-results { + margin-bottom: 8px; +} + +/* Photo upload button */ +.photo-btn { + padding: 10px; + background: transparent; + color: #6b7280; + border: none; + border-radius: 10px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.photo-btn:hover { + background: rgba(0, 0, 0, 0.04); + color: #374151; +} + +/* Main area search */ +.entry-search { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 48px; + max-width: 600px; + margin: 0 auto; + width: 100%; + overflow-y: auto; +} + +.search-title { + font-size: 24px; + font-weight: 500; + color: #1a1a1a; + margin-bottom: 24px; +} + +.search-form-main { + position: relative; + width: 100%; + margin-bottom: 24px; +} + +.search-input-main { + width: 100%; + padding: 16px 48px 16px 20px; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 16px; + font-family: inherit; + font-size: 16px; + background: rgba(255, 255, 255, 0.9); + outline: none; + transition: all 0.2s ease; +} + +.search-input-main:focus { + border-color: rgba(139, 92, 246, 0.5); + box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1); +} + +.search-input-main::placeholder { + color: #9ca3af; +} + +.search-clear-main { + position: absolute; + right: 16px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + font-size: 24px; + color: #9ca3af; + cursor: pointer; + padding: 0 4px; + line-height: 1; +} + +.search-clear-main:hover { + color: #6b7280; +} + +.search-results-main { + width: 100%; + flex: 1; + overflow-y: auto; +} + +.no-results { + text-align: center; + color: #9ca3af; + font-size: 15px; +} + +.search-result-item { + padding: 16px 20px; + background: rgba(255, 255, 255, 0.6); + border: 1px solid rgba(0, 0, 0, 0.06); + border-radius: 12px; + margin-bottom: 12px; + cursor: pointer; + transition: all 0.15s ease; +} + +.search-result-item:hover { + background: rgba(255, 255, 255, 0.9); + border-color: rgba(139, 92, 246, 0.3); +} + +.result-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.result-date { + font-size: 12px; + font-weight: 500; + color: #8b5cf6; +} + +.result-score { + font-size: 11px; + color: #6b7280; + background: #f3f4f6; + padding: 2px 8px; + border-radius: 12px; +} + +.result-content { + font-size: 14px; + color: #374151; + line-height: 1.6; + margin: 0; +} + +.result-image { + max-width: 100%; + max-height: 150px; + border-radius: 8px; + object-fit: contain; +} + +/* Image previews in input */ +.image-preview-container { + display: flex; + gap: 12px; + padding: 12px 48px; + flex-wrap: wrap; +} + +.image-preview { + position: relative; + width: 80px; + height: 80px; + border-radius: 8px; + overflow: hidden; +} + +.image-preview img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.remove-image-btn { + position: absolute; + top: 4px; + right: 4px; + width: 20px; + height: 20px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.6); + color: white; + border: none; + font-size: 14px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +.remove-image-btn:hover { + background: rgba(0, 0, 0, 0.8); +} + +/* Message text and images */ +.message-text { + margin: 0; +} + +.message-image { + max-width: 400px; + max-height: 300px; + border-radius: 12px; + object-fit: contain; + display: block; +} + +/* Date picker modal */ +.date-picker-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.date-picker-modal { + background: white; + border-radius: 16px; + padding: 24px; + min-width: 300px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2); +} + +.date-picker-modal h3 { + margin: 0 0 20px; + font-size: 18px; + font-weight: 600; + color: #1a1a1a; +} + +.date-input { + width: 100%; + padding: 12px 16px; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 10px; + font-family: inherit; + font-size: 15px; + outline: none; + transition: all 0.2s ease; +} + +.date-input:focus { + border-color: rgba(139, 92, 246, 0.5); + box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1); +} + +.date-picker-actions { + display: flex; + gap: 12px; + margin-top: 20px; + justify-content: flex-end; +} + +.date-cancel-btn, +.date-confirm-btn { + padding: 10px 20px; + border-radius: 10px; + font-family: inherit; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.date-cancel-btn { + background: transparent; + border: 1px solid rgba(0, 0, 0, 0.15); + color: #374151; +} + +.date-cancel-btn:hover { + background: rgba(0, 0, 0, 0.04); +} + +.date-confirm-btn { + background: #8b5cf6; + border: none; + color: white; +} + +.date-confirm-btn:hover { + background: #7c3aed; +} + +/* Prompt generator */ +.prompt-generator { + display: flex; + justify-content: flex-end; + padding: 0 48px 12px; + max-width: 816px; + margin: 0 auto; + width: 100%; +} + +.generate-prompt-btn { + padding: 12px 16px; + background: transparent; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 10px; + color: #374151; + font-family: inherit; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.generate-prompt-btn:hover:not(:disabled) { + background: rgba(0, 0, 0, 0.04); + border-color: rgba(0, 0, 0, 0.25); +} + +.generate-prompt-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Insights */ +.insights-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 24px; + width: 100%; +} + +.insight-card { + background: rgba(255, 255, 255, 0.7); + backdrop-filter: blur(20px); + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 16px; + padding: 32px 24px; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +.insight-value { + font-size: 48px; + font-weight: 600; + color: #1a1a1a; + line-height: 1; + margin-bottom: 8px; +} + +.insight-label { + font-size: 14px; + color: #6b7280; + font-weight: 500; +} + +/* Save Entry */ +.save-entry { + display: flex; + justify-content: flex-end; + padding: 16px 48px; + max-width: 816px; + margin: 0 auto; + width: 100%; +} + +.save-btn { + padding: 12px 24px; + background: #8b5cf6; + border: none; + border-radius: 10px; + color: white; + font-family: inherit; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.save-btn:hover { + background: #7c3aed; +} + +.save-btn:active { + transform: scale(0.95); +} + +.save-btn.saved { + background: #bbf7d0; + color: #166534; +} + +/* Insights Sections */ +.section-title { + font-size: 16px; + font-weight: 500; + color: #1a1a1a; + margin-bottom: 16px; +} + +.mood-section, +.themes-section { + width: 100%; + margin-top: 32px; +} + +/* Mood Bars */ +.mood-bars { + display: flex; + flex-direction: column; + gap: 12px; +} + +.mood-row { + display: flex; + align-items: center; + gap: 12px; +} + +.mood-emoji { + font-size: 20px; + width: 28px; +} + +.mood-bar-track { + flex: 1; + height: 16px; + background: rgba(0, 0, 0, 0.05); + border-radius: 8px; + overflow: hidden; +} + +.mood-bar-fill { + height: 100%; + border-radius: 8px; + transition: width 0.3s ease; +} + +.mood-bar-fill.positive { + background: #22c55e; +} + +.mood-bar-fill.neutral { + background: #94a3b8; +} + +.mood-bar-fill.mixed { + background: #f59e0b; +} + +.mood-bar-fill.negative { + background: #ef4444; +} + +.mood-percent { + font-size: 14px; + color: #6b7280; + width: 40px; + text-align: right; +} + +/* Word Cloud */ +.word-cloud { + display: flex; + flex-wrap: wrap; + gap: 8px 16px; + justify-content: center; + align-items: baseline; + max-height: 300px; + overflow-y: auto; +} + +.theme-word { + color: #6b7280; + font-weight: 500; + transition: color 0.2s ease; + cursor: default; +} + +.theme-word:hover { + color: #7c3aed; +} + +/* Hidden Scrollbar */ +.entry-list, +.messages, +.entry-search, +.search-results-main, +.word-cloud { + scrollbar-width: none; +} + +.entry-list::-webkit-scrollbar, +.messages::-webkit-scrollbar, +.entry-search::-webkit-scrollbar, +.search-results-main::-webkit-scrollbar, +.word-cloud::-webkit-scrollbar { + display: none; +} diff --git a/apps/interactive-journal/frontend/src/App.jsx b/apps/interactive-journal/frontend/src/App.jsx new file mode 100644 index 0000000..9f53848 --- /dev/null +++ b/apps/interactive-journal/frontend/src/App.jsx @@ -0,0 +1,172 @@ +import { useState, useEffect } from 'react' +import Sidebar from './components/Sidebar' +import Entry from './components/Entry' +import './App.css' + +const API_URL = 'http://localhost:8000/api' + +function App() { + const [entries, setEntries] = useState([]) + const [activeEntry, setActiveEntry] = useState(null) + const [messages, setMessages] = useState([]) + const [isV2, setIsV2] = useState(false) + const [activeSection, setActiveSection] = useState(null) + + useEffect(() => { + fetchEntries() + }, [isV2]) + + useEffect(() => { + if (activeEntry) { + fetchMessages(activeEntry) + } + }, [activeEntry]) + + const fetchEntries = async () => { + const version = isV2 ? 2 : 1 + const res = await fetch(`${API_URL}/entries/?version=${version}`) + const data = await res.json() + setEntries(data) + } + + const fetchMessages = async (entryId) => { + const res = await fetch(`${API_URL}/entries/${entryId}/messages`) + const data = await res.json() + setMessages(data) + } + + const createEntry = async (entryDate) => { + const version = isV2 ? 2 : 1 + const formData = new FormData() + formData.append('version', version) + formData.append('entry_date', entryDate) + const res = await fetch(`${API_URL}/entries/`, { + method: 'POST', + body: formData + }) + const data = await res.json() + await fetchEntries() + setActiveEntry(data._id) + setMessages([]) + setActiveSection(null) + } + + const deleteEntry = async (entryId) => { + await fetch(`${API_URL}/entries/${entryId}`, { method: 'DELETE' }) + await fetchEntries() + if (activeEntry === entryId) { + setActiveEntry(null) + setMessages([]) + } + } + + const toggleVersion = () => { + setIsV2(!isV2) + setActiveEntry(null) + setMessages([]) + } + + const sendMessage = async (content, images = []) => { + // Show user messages immediately (text and images separately) + const newMessages = [] + + if (content.trim()) { + newMessages.push({ + _id: Date.now().toString(), + role: 'user', + content + }) + } + + images.forEach((img, index) => { + newMessages.push({ + _id: Date.now().toString() + '-img-' + index, + role: 'user', + image: img.preview + }) + }) + + // Show user messages immediately + setMessages(prev => [...prev, ...newMessages]) + + // Send to backend using FormData + const formData = new FormData() + if (content) { + formData.append('content', content) + } + images.forEach(img => { + formData.append('images', img.file) + }) + formData.append('version', isV2 ? 2 : 1) + const activeEntryObj = entries.find(e => e._id === activeEntry) + if (activeEntryObj?.created_at) { + formData.append('entry_date', activeEntryObj.created_at) + } + + const res = await fetch(`${API_URL}/entries/${activeEntry}/messages`, { + method: 'POST', + body: formData + }) + + // Read the streaming response + const reader = res.body.getReader() + const decoder = new TextDecoder() + const aiMessageId = Date.now().toString() + '-ai' + let fullResponse = '' + let messageAdded = false + + while (true) { + const { done, value } = await reader.read() + if (done) break + + const chunk = decoder.decode(value, { stream: true }) + fullResponse += chunk + + // Add AI message on first chunk, then update + if (!messageAdded) { + setMessages(prev => [...prev, { _id: aiMessageId, role: 'assistant', content: fullResponse }]) + messageAdded = true + } else { + setMessages(prev => prev.map(msg => + msg._id === aiMessageId ? { ...msg, content: fullResponse } : msg + )) + } + } + } + + return ( +
+ { + setActiveEntry(entryId) + if (isV2) setActiveSection('entries') + }} + onNewEntry={createEntry} + onDeleteEntry={deleteEntry} + isV2={isV2} + onToggleVersion={toggleVersion} + activeSection={activeSection} + onSectionChange={(section) => { + setActiveSection(section) + setActiveEntry(null) + setMessages([]) + }} + /> + activeEntry && fetchMessages(activeEntry)} + isV2={isV2} + activeSection={activeSection} + onSelectEntry={setActiveEntry} + /> +
+ ) +} + +export default App diff --git a/apps/interactive-journal/frontend/src/assets/react.svg b/apps/interactive-journal/frontend/src/assets/react.svg new file mode 100644 index 0000000..8e0e0f1 --- /dev/null +++ b/apps/interactive-journal/frontend/src/assets/react.svg @@ -0,0 +1 @@ + diff --git a/apps/interactive-journal/frontend/src/components/Entry.jsx b/apps/interactive-journal/frontend/src/components/Entry.jsx new file mode 100644 index 0000000..fbe61e0 --- /dev/null +++ b/apps/interactive-journal/frontend/src/components/Entry.jsx @@ -0,0 +1,380 @@ +import { useState, useRef, useEffect } from 'react' + +function Entry({ messages, onSendMessage, hasActiveEntry, activeEntry, entries, onRefreshMessages, isV2, activeSection, onSelectEntry }) { + const [input, setInput] = useState('') + const [selectedImages, setSelectedImages] = useState([]) + const [searchQuery, setSearchQuery] = useState('') + const [searchResults, setSearchResults] = useState(null) + const [isSearching, setIsSearching] = useState(false) + const [isGeneratingPrompt, setIsGeneratingPrompt] = useState(false) + const [insights, setInsights] = useState(null) + const [saveStatus, setSaveStatus] = useState(null) + const messagesEndRef = useRef(null) + const fileInputRef = useRef(null) + + const handleGeneratePrompt = async () => { + setIsGeneratingPrompt(true) + try { + const activeEntryObj = entries.find(e => e._id === activeEntry) + const formData = new FormData() + formData.append('entry_id', activeEntry) + formData.append('entry_date', activeEntryObj?.created_at || new Date().toISOString()) + + await fetch('http://localhost:8000/api/entries/generate-prompt', { + method: 'POST', + body: formData + }) + onRefreshMessages() + } catch (error) { + console.error('Failed to generate prompt:', error) + } + setIsGeneratingPrompt(false) + } + + const handleSaveEntry = async () => { + setSaveStatus('saving') + try { + const activeEntryObj = entries.find(e => e._id === activeEntry) + const formData = new FormData() + formData.append('entry_date', activeEntryObj?.created_at || new Date().toISOString()) + + await fetch(`http://localhost:8000/api/entries/${activeEntry}/analyze`, { + method: 'POST', + body: formData + }) + setSaveStatus('saved') + setTimeout(() => setSaveStatus(null), 2000) + } catch (error) { + console.error('Failed to save entry:', error) + setSaveStatus(null) + } + } + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) + } + + useEffect(() => { + scrollToBottom() + }, [messages]) + + useEffect(() => { + if (isV2 && activeSection === 'insights') { + fetch('http://localhost:8000/api/entries/insights') + .then(res => res.json()) + .then(data => setInsights(data)) + .catch(err => console.error('Failed to fetch insights:', err)) + } + }, [isV2, activeSection]) + + useEffect(() => { + setSearchQuery('') + setSearchResults(null) + }, [isV2]) + + + const handleSubmit = (e) => { + e.preventDefault() + if ((input.trim() || selectedImages.length > 0) && hasActiveEntry) { + onSendMessage(input, selectedImages) + setInput('') + setSelectedImages([]) + } + } + + const handlePhotoClick = () => { + fileInputRef.current?.click() + } + + const handleFileChange = (e) => { + const files = Array.from(e.target.files || []) + + files.forEach(file => { + setSelectedImages(prev => [...prev, { + file, + preview: URL.createObjectURL(file), + name: file.name + }]) + }) + + // Reset input so same file can be selected again + e.target.value = '' + } + + const removeImage = (index) => { + setSelectedImages(prev => prev.filter((_, i) => i !== index)) + } + + const handleSearch = async (e) => { + e.preventDefault() + if (!searchQuery.trim()) return + + setIsSearching(true) + try { + const version = isV2 ? 2 : 1 + const res = await fetch(`http://localhost:8000/api/entries/search?q=${encodeURIComponent(searchQuery)}&version=${version}`) + const data = await res.json() + setSearchResults(data) + } catch (error) { + console.error('Search failed:', error) + } + setIsSearching(false) + } + + const clearSearch = () => { + setSearchQuery('') + setSearchResults(null) + } + + // Show search interface when Entries tab is clicked and no entry selected + if (activeSection === 'entries' && !hasActiveEntry) { + return ( +
+
+

Search your entries

+
+ setSearchQuery(e.target.value)} + /> + {searchQuery && ( + + )} +
+ {searchResults && ( +
+ {searchResults.length === 0 ? ( +

No matches found

+ ) : ( + searchResults.map((result) => ( +
onSelectEntry(result._id)} + > +
+ + {new Date(result.created_at).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + })} + + + {(result.score * 100).toFixed(0)}% match + +
+ {result.image ? ( + Result + ) : ( +

{result.content}

+ )} +
+ )) + )} +
+ )} +
+
+ ) + } + + // Show insights when V2 Insights tab is active and no entry selected + if (isV2 && activeSection === 'insights' && !hasActiveEntry) { + return ( +
+
+

Your month in review

+ {insights ? ( + <> +
+
+ {insights.total_entries} + Entries +
+
+ {insights.longest_streak} + Longest streak +
+
+ + {(insights.mood.positive > 0 || insights.mood.neutral > 0 || insights.mood.mixed > 0 || insights.mood.negative > 0) && ( +
+

Mood

+
+
+ 😊 +
+
+
+ {insights.mood.positive}% +
+
+ 😐 +
+
+
+ {insights.mood.neutral}% +
+
+ 🤔 +
+
+
+ {insights.mood.mixed}% +
+
+ 😔 +
+
+
+ {insights.mood.negative}% +
+
+
+ )} + + {insights.themes.length > 0 && ( +
+

Themes

+
+ {insights.themes.map((item, i) => ( + + {item.theme} + + ))} +
+
+ )} + + ) : ( +

Loading...

+ )} +
+
+ ) + } + + if (!hasActiveEntry) { + return ( +
+
+

How was your day, Apoorva?

+
+
+ ) + } + + return ( +
+
+ {messages.map((msg) => ( +
+
+
+ {msg.role === 'user' ? 'You' : 'Memoir'} +
+ {msg.content &&

{msg.content}

} + {msg.image && ( + Uploaded + )} +
+
+ ))} + {isV2 && messages.length > 0 && ( +
+ +
+ )} +
+
+ + {/* Image previews */} + {selectedImages.length > 0 && ( +
+ {selectedImages.map((img, index) => ( +
+ {img.name} + +
+ ))} +
+ )} + + {isV2 && messages.length === 0 && ( +
+ +
+ )} + +
+
+ {isV2 && ( + <> + + + + )} + setInput(e.target.value)} + placeholder="What's on your mind?" + /> + +
+
+
+ ) +} + +export default Entry diff --git a/apps/interactive-journal/frontend/src/components/Sidebar.jsx b/apps/interactive-journal/frontend/src/components/Sidebar.jsx new file mode 100644 index 0000000..1a6273d --- /dev/null +++ b/apps/interactive-journal/frontend/src/components/Sidebar.jsx @@ -0,0 +1,192 @@ +import { useState, useEffect } from 'react' + +function Sidebar({ entries, activeEntry, onSelectEntry, onNewEntry, onDeleteEntry, isV2, onToggleVersion, activeSection, onSectionChange }) { + const [openMenu, setOpenMenu] = useState(null) + const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 }) + const [showDatePicker, setShowDatePicker] = useState(false) + const [selectedDate, setSelectedDate] = useState('') + + const handleNewEntryClick = () => { + setSelectedDate(new Date().toISOString().split('T')[0]) + setShowDatePicker(true) + } + + const handleDateConfirm = () => { + if (selectedDate) { + onNewEntry(selectedDate) + setShowDatePicker(false) + } + } + + // Filter entries to only show recent ones (last 7 days) in V2 + const recentEntries = entries.filter(entry => { + const entryDate = new Date(entry.created_at) + const oneWeekAgo = new Date() + oneWeekAgo.setDate(oneWeekAgo.getDate() - 7) + return entryDate >= oneWeekAgo + }) + + const handleMenuClick = (e, entryId) => { + e.stopPropagation() + if (openMenu === entryId) { + setOpenMenu(null) + } else { + const rect = e.currentTarget.getBoundingClientRect() + setMenuPosition({ + top: rect.bottom + 4, + left: rect.left + }) + setOpenMenu(entryId) + } + } + + const handleDelete = (e, entryId) => { + e.stopPropagation() + onDeleteEntry(entryId) + setOpenMenu(null) + } + + // Close menu when clicking outside + useEffect(() => { + const handleClickOutside = () => setOpenMenu(null) + if (openMenu) { + document.addEventListener('click', handleClickOutside) + return () => document.removeEventListener('click', handleClickOutside) + } + }, [openMenu]) + + // Ensure active entry is always included, sorted chronologically + const getDisplayEntries = () => { + if (!isV2) return entries + const activeEntryObj = entries.find(e => e._id === activeEntry) + if (!activeEntryObj || recentEntries.find(e => e._id === activeEntry)) { + return recentEntries + } + return [...recentEntries, activeEntryObj].sort( + (a, b) => new Date(b.created_at) - new Date(a.created_at) + ) + } + const displayEntries = getDisplayEntries() + + const formatDate = (dateString) => { + const date = new Date(dateString) + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }) + } + + return ( +
+
+

Memoir

+
+ V1 + + V2 +
+
+ +
+ +
+ + {showDatePicker && ( +
setShowDatePicker(false)}> +
e.stopPropagation()}> +

Select entry date

+ setSelectedDate(e.target.value)} + className="date-input" + /> +
+ + +
+
+
+ )} + +
+ + {isV2 && ( + + )} +
+ +
+
Recent
+
+ {displayEntries.length === 0 ? ( +
No recent entries
+ ) : ( + displayEntries.map((entry) => ( +
onSelectEntry(entry._id)} + > + {formatDate(entry.created_at)} + +
+ )) + )} +
+
+ + {/* Fixed position dropdown */} + {openMenu && ( +
e.stopPropagation()} + > + +
+ )} + +
+ A + Apoorva +
+
+ ) +} + +export default Sidebar diff --git a/apps/interactive-journal/frontend/src/index.css b/apps/interactive-journal/frontend/src/index.css new file mode 100644 index 0000000..363ca51 --- /dev/null +++ b/apps/interactive-journal/frontend/src/index.css @@ -0,0 +1,24 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Sacramento&display=swap'); + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + line-height: 1.6; + font-weight: 400; + color: #1a1a1a; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + min-height: 100vh; +} + +#root { + height: 100vh; +} diff --git a/apps/interactive-journal/frontend/src/main.jsx b/apps/interactive-journal/frontend/src/main.jsx new file mode 100644 index 0000000..b9a1a6d --- /dev/null +++ b/apps/interactive-journal/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/apps/interactive-journal/frontend/vite.config.js b/apps/interactive-journal/frontend/vite.config.js new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/apps/interactive-journal/frontend/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/ruff.toml b/ruff.toml index 411c7bd..e73ef5a 100644 --- a/ruff.toml +++ b/ruff.toml @@ -17,6 +17,7 @@ extend-select = [ "YTT", # flake8-2020 ] ignore = [ + "E402", # Module level import not at top of file, "F811", # Redefinition of unused, "F402", # Import shadowing, "B006", # Do not use mutable data structures for argument defaults,