diff --git a/.claude/commands/archon/archon-coderabbit-helper.md b/.claude/commands/archon/archon-coderabbit-helper.md new file mode 100644 index 00000000..b94a967f --- /dev/null +++ b/.claude/commands/archon/archon-coderabbit-helper.md @@ -0,0 +1,73 @@ +--- +name: Archon CodeRabbit Helper +description: Analyze CodeRabbit suggestions, assess validity, and provide actionable options with tradeoffs +argument-hint: Paste the CodeRabbit suggestion here +--- + +# CodeRabbit Review Analysis + +**Review:** $ARGUMENTS + +## Instructions + +Analyze this CodeRabbit suggestion following these steps: + +### 1. Deep Analysis + +- Understand the technical issue being raised +- Check if it's a real problem or false positive +- Search the codebase for related patterns and context +- Consider project phase (early beta) and architecture + +### 2. Context Assessment + +- We're in early beta - prioritize simplicity over perfection +- Follow KISS principles and existing codebase patterns +- Avoid premature optimization or over-engineering +- Consider if this affects user experience or is internal only + +### 3. Generate Options + +Think harder about the problem and potential solutions. +Provide 2-5 practical options with clear tradeoffs + +## Response Format + +### 📋 Issue Summary + +_[One sentence describing what CodeRabbit found]_ + +### ✅ Is this valid? + +_[YES/NO with brief explanation]_ + +### 🎯 Priority for this PR + +_[HIGH/MEDIUM/LOW/SKIP with reasoning]_ + +### 🔧 Options & Tradeoffs + +**Option 1: [Name]** + +- What: _[Brief description]_ +- Pros: _[Benefits]_ +- Cons: _[Drawbacks]_ +- Effort: _[Low/Medium/High]_ + +**Option 2: [Name]** + +- What: _[Brief description]_ +- Pros: _[Benefits]_ +- Cons: _[Drawbacks]_ +- Effort: _[Low/Medium/High]_ + +### 💡 Recommendation + +_[Your recommended option with 1-2 sentence justification]_ + +## User feedback + +- When you have presented the review to the user you must ask for their feedback on the suggested changes. +- Ask the user if they wish to discuss any of the options further +- If the user wishes for you to explore further, provide additional options or tradeoffs. +- If the user is ready to implement the recommended option right away diff --git a/.env.example b/.env.example index dc00d2e6..4077e9cd 100644 --- a/.env.example +++ b/.env.example @@ -42,6 +42,12 @@ ARCHON_DOCS_PORT=3838 # If not set, defaults to localhost, 127.0.0.1, ::1, and the HOST value above VITE_ALLOWED_HOSTS= +# Development Tools +# VITE_SHOW_DEVTOOLS: Show TanStack Query DevTools (for developers only) +# Set to "true" to enable the DevTools panel in bottom right corner +# Defaults to "false" for end users +VITE_SHOW_DEVTOOLS=false + # When enabled, PROD mode will proxy ARCHON_SERVER_PORT through ARCHON_UI_PORT. This exposes both the # Archon UI and API through a single port. This is useful when deploying Archon behind a reverse # proxy where you want to expose the frontend on a single external domain. diff --git a/.gitignore b/.gitignore index bc0c0474..e9b1084a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,8 @@ __pycache__ .serena .claude/settings.local.json PRPs/local +PRPs/completed/ /logs/ +.zed +tmp/ +temp/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..3c0928c1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,388 @@ +# AGENTS.md + +## Alpha Development Guidelines + +**Local-only deployment** - each user runs their own instance. + +### Core Principles + +- **No backwards compatibility** - remove deprecated code immediately +- **Detailed errors over graceful failures** - we want to identify and fix issues fast +- **Break things to improve them** - alpha is for rapid iteration + +### Error Handling + +**Core Principle**: In alpha, we need to intelligently decide when to fail hard and fast to quickly address issues, and when to allow processes to complete in critical services despite failures. Read below carefully and make intelligent decisions on a case-by-case basis. + +#### When to Fail Fast and Loud (Let it Crash!) + +These errors should stop execution and bubble up immediately: (except for crawling flows) + +- **Service startup failures** - If credentials, database, or any service can't initialize, the system should crash with a clear error +- **Missing configuration** - Missing environment variables or invalid settings should stop the system +- **Database connection failures** - Don't hide connection issues, expose them +- **Authentication/authorization failures** - Security errors must be visible and halt the operation +- **Data corruption or validation errors** - Never silently accept bad data, Pydantic should raise +- **Critical dependencies unavailable** - If a required service is down, fail immediately +- **Invalid data that would corrupt state** - Never store zero embeddings, null foreign keys, or malformed JSON + +#### When to Complete but Log Detailed Errors + +These operations should continue but track and report failures clearly: + +- **Batch processing** - When crawling websites or processing documents, complete what you can and report detailed failures for each item +- **Background tasks** - Embedding generation, async jobs should finish the queue but log failures +- **WebSocket events** - Don't crash on a single event failure, log it and continue serving other clients +- **Optional features** - If projects/tasks are disabled, log and skip rather than crash +- **External API calls** - Retry with exponential backoff, then fail with a clear message about what service failed and why + +#### Critical Nuance: Never Accept Corrupted Data + +When a process should continue despite failures, it must **skip the failed item entirely** rather than storing corrupted data: + +**❌ WRONG - Silent Corruption:** + +```python +try: + embedding = create_embedding(text) +except Exception as e: + embedding = [0.0] * 1536 # NEVER DO THIS - corrupts database + store_document(doc, embedding) +``` + +**✅ CORRECT - Skip Failed Items:** + +```python +try: + embedding = create_embedding(text) + store_document(doc, embedding) # Only store on success +except Exception as e: + failed_items.append({'doc': doc, 'error': str(e)}) + logger.error(f"Skipping document {doc.id}: {e}") + # Continue with next document, don't store anything +``` + +**✅ CORRECT - Batch Processing with Failure Tracking:** + +```python +def process_batch(items): + results = {'succeeded': [], 'failed': []} + + for item in items: + try: + result = process_item(item) + results['succeeded'].append(result) + except Exception as e: + results['failed'].append({ + 'item': item, + 'error': str(e), + 'traceback': traceback.format_exc() + }) + logger.error(f"Failed to process {item.id}: {e}") + + # Always return both successes and failures + return results +``` + +#### Error Message Guidelines + +- Include context about what was being attempted when the error occurred +- Preserve full stack traces with `exc_info=True` in Python logging +- Use specific exception types, not generic Exception catching +- Include relevant IDs, URLs, or data that helps debug the issue +- Never return None/null to indicate failure - raise an exception with details +- For batch operations, always report both success count and detailed failure list + +### Code Quality + +- Remove dead code immediately rather than maintaining it - no backward compatibility or legacy functions +- Prioritize functionality over production-ready patterns +- Focus on user experience and feature completeness +- When updating code, don't reference what is changing (avoid keywords like LEGACY, CHANGED, REMOVED), instead focus on comments that document just the functionality of the code + +## Architecture Overview + +Archon V2 Alpha is a microservices-based knowledge management system with MCP (Model Context Protocol) integration: + +- **Frontend (port 3737)**: React + TypeScript + Vite + TailwindCSS + - **UI Strategy**: Radix UI primitives in `/features`, custom components in legacy `/components` + - **State Management**: TanStack Query for all data fetching in `/features` + - **Styling**: Tron-inspired glassmorphism with Tailwind CSS +- **Main Server (port 8181)**: FastAPI with HTTP polling for updates +- **MCP Server (port 8051)**: Lightweight HTTP-based MCP protocol server +- **Agents Service (port 8052)**: PydanticAI agents for AI/ML operations +- **Database**: Supabase (PostgreSQL + pgvector for embeddings) + +## Development Commands + +### Frontend (archon-ui-main/) + +```bash +npm run dev # Start development server on port 3737 +npm run build # Build for production +npm run lint # Run ESLint +npm run test # Run Vitest tests +npm run test:coverage # Run tests with coverage report +``` + +# Biome Linter Guide for AI Assistants + +## Overview + +This project uses Biome for linting and formatting the `/src/features` directory. Biome provides fast, machine-readable feedback that AI assistants can use to improve code quality. + +## Configuration + +Biome is configured in `biome.json`: + +- **Scope**: Only checks `/src/features/**` directory +- **Formatting**: 2 spaces, 80 char line width +- **Linting**: Recommended rules enabled +- **Import Organization**: Automatically sorts and groups imports + +## AI Assistant Workflow in the new /features directory + +1. **Check Issues**: Run `npm run biome:ai` to get JSON output +2. **Parse Output**: Extract error locations and types +3. **Apply Fixes**: + - Run `npm run biome:ai-fix` for auto-fixable issues + - Manually fix remaining issues based on patterns above +4. **Verify**: Run `npm run biome:ai` again to confirm fixes + +## JSON Output Format + +When using `biome:ai`, the output is structured JSON: + +```json +{ + "diagnostics": [ + { + "file": "path/to/file.tsx", + "line": 10, + "column": 5, + "severity": "error", + "message": "Description of the issue", + "rule": "lint/a11y/useButtonType" + } + ] +} +``` + +### Backend (python/) + +```bash +# Using uv package manager +uv sync # Install/update dependencies +uv run pytest # Run tests +uv run python -m src.server.main # Run server locally + +# With Docker +docker-compose up --build -d # Start all services +docker-compose logs -f # View logs +docker-compose restart # Restart services +``` + +### Testing + +```bash +# Frontend tests (from archon-ui-main/) +npm run test:coverage:stream # Run with streaming output +npm run test:ui # Run with Vitest UI + +# Backend tests (from python/) +uv run pytest tests/test_api_essentials.py -v +uv run pytest tests/test_service_integration.py -v +``` + +## Key API Endpoints + +### Knowledge Base + +- `POST /api/knowledge/crawl` - Crawl a website +- `POST /api/knowledge/upload` - Upload documents (PDF, DOCX, MD) +- `GET /api/knowledge/items` - List knowledge items +- `POST /api/knowledge/search` - RAG search + +### MCP Integration + +- `GET /api/mcp/health` - MCP server status +- `POST /api/mcp/tools/{tool_name}` - Execute MCP tool +- `GET /api/mcp/tools` - List available tools + +### Projects & Tasks (when enabled) + +- `GET /api/projects` - List all projects +- `POST /api/projects` - Create project +- `GET /api/projects/{id}` - Get single project +- `PUT /api/projects/{id}` - Update project +- `DELETE /api/projects/{id}` - Delete project +- `GET /api/projects/{id}/tasks` - Get tasks for project (use this, not getTasks) +- `POST /api/tasks` - Create task +- `PUT /api/tasks/{id}` - Update task +- `DELETE /api/tasks/{id}` - Delete task + +## Polling Architecture + +### HTTP Polling (replaced Socket.IO) + +- **Polling intervals**: 1-2s for active operations, 5-10s for background data +- **ETag caching**: Reduces bandwidth by ~70% via 304 Not Modified responses +- **Smart pausing**: Stops polling when browser tab is inactive +- **Progress endpoints**: `/api/progress/crawl`, `/api/progress/project-creation` + +### Key Polling Hooks + +- `usePolling` - Generic polling with ETag support +- `useDatabaseMutation` - Optimistic updates with rollback +- `useProjectMutation` - Project-specific operations + +## Environment Variables + +Required in `.env`: + +```bash +SUPABASE_URL=https://your-project.supabase.co +SUPABASE_SERVICE_KEY=your-service-key-here +``` + +Optional: + +```bash +OPENAI_API_KEY=your-openai-key # Can be set via UI +LOGFIRE_TOKEN=your-logfire-token # For observability +LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR +``` + +## File Organization + +### Frontend Structure + +- `src/components/` - Legacy UI components (custom-built) +- `src/features/` - Modern vertical slice architecture with Radix UI + - `ui/primitives/` - Radix UI primitives with Tron glassmorphism + - `projects/` - Project management feature + - `tasks/` - Task management sub-feature +- `src/pages/` - Main application pages +- `src/services/` - API communication and business logic +- `src/hooks/` - Custom React hooks +- `src/contexts/` - React context providers + +### UI Libraries + +- **Radix UI** (@radix-ui/react-\*) - Unstyled, accessible primitives for `/features` +- **TanStack Query** - Data fetching and caching for `/features` +- **React DnD** - Drag and drop for Kanban boards +- **Tailwind CSS** - Utility-first styling with Tron-inspired glassmorphism +- **Framer Motion** - Animations (minimal usage) + +### Theme Management + +- **ThemeContext** - Manages light/dark theme state +- **Tailwind dark mode** - Uses `dark:` prefix with selector strategy +- **Automatic switching** - All components respect theme via Tailwind classes +- **Persistent** - Theme choice saved in localStorage +- **Tron aesthetic** - Stronger neon glows in dark mode, subtle in light mode + +We're migrating to a vertical slice architecture where each feature is self-contained. Features are organized by domain hierarchy - main features contain their sub-features. For example, tasks are a sub-feature of projects, so they live at `features/projects/tasks/` rather than as separate siblings. Each feature level has its own components, hooks, types, and services folders. This keeps related code together and makes the codebase more maintainable as it scales. + +### Backend Structure + +- `src/server/` - Main FastAPI application +- `src/server/api_routes/` - API route handlers +- `src/server/services/` - Business logic services +- `src/mcp/` - MCP server implementation +- `src/agents/` - PydanticAI agent implementations + +## Database Schema + +Key tables in Supabase: + +- `sources` - Crawled websites and uploaded documents +- `documents` - Processed document chunks with embeddings +- `projects` - Project management (optional feature) +- `tasks` - Task tracking linked to projects +- `code_examples` - Extracted code snippets + +## API Naming Conventions + +### Task Status Values + +Use database values directly (no UI mapping): + +- `todo`, `doing`, `review`, `done` + +### Service Method Patterns + +- `get[Resource]sByProject(projectId)` - Scoped queries +- `get[Resource](id)` - Single resource +- `create[Resource](data)` - Create operations +- `update[Resource](id, updates)` - Updates +- `delete[Resource](id)` - Soft deletes + +### State Naming + +- `is[Action]ing` - Loading states (e.g., `isSwitchingProject`) +- `[resource]Error` - Error messages +- `selected[Resource]` - Current selection + +## Common Development Tasks + +### Add a new API endpoint + +1. Create route handler in `python/src/server/api_routes/` +2. Add service logic in `python/src/server/services/` +3. Include router in `python/src/server/main.py` +4. Update frontend service in `archon-ui-main/src/services/` + +### Add a new UI component + +For **features** directory (preferred for new components): + +1. Use Radix UI primitives from `src/features/ui/primitives/` +2. Create component in relevant feature folder under `src/features/` +3. Use TanStack Query for data fetching +4. Apply Tron-inspired glassmorphism styling with Tailwind + +For **legacy** components: + +1. Create component in `archon-ui-main/src/components/` +2. Add to page in `archon-ui-main/src/pages/` +3. Include any new API calls in services +4. Add tests in `archon-ui-main/test/` + +### Debug MCP connection issues + +1. Check MCP health: `curl http://localhost:8051/health` +2. View MCP logs: `docker-compose logs archon-mcp` +3. Test tool execution via UI MCP page +4. Verify Supabase connection and credentials + +## Code Quality Standards + +We enforce code quality through automated linting and type checking: + +- **Python 3.12** with 120 character line length +- **Ruff** for linting - checks for errors, warnings, unused imports, and code style +- **Mypy** for type checking - ensures type safety across the codebase +- **Auto-formatting** on save in IDEs to maintain consistent style +- Run `uv run ruff check` and `uv run mypy src/` locally before committing + +## MCP Tools Available + +When connected to Cursor/Windsurf: + +- `archon:perform_rag_query` - Search knowledge base +- `archon:search_code_examples` - Find code snippets +- `archon:manage_project` - Project operations +- `archon:manage_task` - Task management +- `archon:get_available_sources` - List knowledge sources + +## Important Notes + +- Projects feature is optional - toggle in Settings UI +- All services communicate via HTTP, not gRPC +- HTTP polling handles all updates (Socket.IO removed) +- Frontend uses Vite proxy for API calls in development +- Python backend uses `uv` for dependency management +- Docker Compose handles service orchestration +- we use tanstack query NO PROP DRILLING! refacring in progress! diff --git a/CLAUDE.md b/CLAUDE.md index 46688916..b618b161 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co #### When to Fail Fast and Loud (Let it Crash!) -These errors should stop execution and bubble up immediately: +These errors should stop execution and bubble up immediately: (except for crawling flows) - **Service startup failures** - If credentials, database, or any service can't initialize, the system should crash with a clear error - **Missing configuration** - Missing environment variables or invalid settings should stop the system @@ -102,16 +102,6 @@ def process_batch(items): - Focus on user experience and feature completeness - When updating code, don't reference what is changing (avoid keywords like LEGACY, CHANGED, REMOVED), instead focus on comments that document just the functionality of the code -## Architecture Overview - -Archon V2 Alpha is a microservices-based knowledge management system with MCP (Model Context Protocol) integration: - -- **Frontend (port 3737)**: React + TypeScript + Vite + TailwindCSS -- **Main Server (port 8181)**: FastAPI + Socket.IO for real-time updates -- **MCP Server (port 8051)**: Lightweight HTTP-based MCP protocol server -- **Agents Service (port 8052)**: PydanticAI agents for AI/ML operations -- **Database**: Supabase (PostgreSQL + pgvector for embeddings) - ## Development Commands ### Frontend (archon-ui-main/) @@ -119,112 +109,261 @@ Archon V2 Alpha is a microservices-based knowledge management system with MCP (M ```bash npm run dev # Start development server on port 3737 npm run build # Build for production -npm run lint # Run ESLint -npm run test # Run Vitest tests -npm run test:coverage # Run tests with coverage report +npm run lint # Run ESLint on legacy code (excludes /features) +npm run lint:files path/to/file.tsx # Lint specific files + +# Biome for /src/features directory only +npm run biome # Check features directory +npm run biome:fix # Auto-fix issues +npm run biome:format # Format code (120 char lines) +npm run biome:ai # Machine-readable JSON output for AI +npm run biome:ai-fix # Auto-fix with JSON output + +# Testing +npm run test # Run all tests in watch mode +npm run test:ui # Run with Vitest UI interface +npm run test:coverage:stream # Run once with streaming output +vitest run src/features/projects # Test specific directory + +# TypeScript +npx tsc --noEmit # Check all TypeScript errors +npx tsc --noEmit 2>&1 | grep "src/features" # Check features only ``` ### Backend (python/) ```bash -# Using uv package manager -uv sync # Install/update dependencies -uv run pytest # Run tests -uv run python -m src.server.main # Run server locally +# Using uv package manager (preferred) +uv sync --group all # Install all dependencies +uv run python -m src.server.main # Run server locally on 8181 +uv run pytest # Run all tests +uv run pytest tests/test_api_essentials.py -v # Run specific test +uv run ruff check # Run linter +uv run ruff check --fix # Auto-fix linting issues +uv run mypy src/ # Type check -# With Docker -docker-compose up --build -d # Start all services -docker-compose logs -f # View logs -docker-compose restart # Restart services +# Docker operations +docker compose up --build -d # Start all services +docker compose --profile backend up -d # Backend only (for hybrid dev) +docker compose logs -f archon-server # View server logs +docker compose logs -f archon-mcp # View MCP server logs +docker compose restart archon-server # Restart after code changes +docker compose down # Stop all services +docker compose down -v # Stop and remove volumes ``` -### Testing +### Quick Workflows ```bash -# Frontend tests (from archon-ui-main/) -npm run test:coverage:stream # Run with streaming output -npm run test:ui # Run with Vitest UI +# Hybrid development (recommended) - backend in Docker, frontend local +make dev # Or manually: docker compose --profile backend up -d && cd archon-ui-main && npm run dev -# Backend tests (from python/) -uv run pytest tests/test_api_essentials.py -v -uv run pytest tests/test_service_integration.py -v +# Full Docker mode +make dev-docker # Or: docker compose up --build -d + +# Run linters before committing +make lint # Runs both frontend and backend linters +make lint-fe # Frontend only (ESLint + Biome) +make lint-be # Backend only (Ruff + MyPy) + +# Testing +make test # Run all tests +make test-fe # Frontend tests only +make test-be # Backend tests only ``` -## Key API Endpoints +## Architecture Overview -### Knowledge Base +Archon V2 Alpha is a microservices-based knowledge management system with MCP (Model Context Protocol) integration: -- `POST /api/knowledge/crawl` - Crawl a website -- `POST /api/knowledge/upload` - Upload documents (PDF, DOCX, MD) -- `GET /api/knowledge/items` - List knowledge items -- `POST /api/knowledge/search` - RAG search +### Service Architecture -### MCP Integration +- **Frontend (port 3737)**: React + TypeScript + Vite + TailwindCSS + - **Dual UI Strategy**: + - `/features` - Modern vertical slice with Radix UI primitives + TanStack Query + - `/components` - Legacy custom components (being migrated) + - **State Management**: TanStack Query for all data fetching (no prop drilling) + - **Styling**: Tron-inspired glassmorphism with Tailwind CSS + - **Linting**: Biome for `/features`, ESLint for legacy code -- `GET /api/mcp/health` - MCP server status -- `POST /api/mcp/tools/{tool_name}` - Execute MCP tool -- `GET /api/mcp/tools` - List available tools +- **Main Server (port 8181)**: FastAPI with HTTP polling for updates + - Handles all business logic, database operations, and external API calls + - WebSocket support removed in favor of HTTP polling with ETag caching -### Projects & Tasks (when enabled) +- **MCP Server (port 8051)**: Lightweight HTTP-based MCP protocol server + - Provides tools for AI assistants (Claude, Cursor, Windsurf) + - Exposes knowledge search, task management, and project operations -- `GET /api/projects` - List projects -- `POST /api/projects` - Create project -- `GET /api/projects/{id}/tasks` - Get project tasks -- `POST /api/projects/{id}/tasks` - Create task +- **Agents Service (port 8052)**: PydanticAI agents for AI/ML operations + - Handles complex AI workflows and document processing -## Socket.IO Events +- **Database**: Supabase (PostgreSQL + pgvector for embeddings) + - Cloud or local Supabase both supported + - pgvector for semantic search capabilities -Real-time updates via Socket.IO on port 8181: +### Frontend Architecture Details -- `crawl_progress` - Website crawling progress -- `project_creation_progress` - Project setup progress -- `task_update` - Task status changes -- `knowledge_update` - Knowledge base changes +#### Vertical Slice Architecture (/features) -## Environment Variables +Features are organized by domain hierarchy with self-contained modules: -Required in `.env`: - -```bash -SUPABASE_URL=https://your-project.supabase.co -SUPABASE_SERVICE_KEY=your-service-key-here +``` +src/features/ +├── ui/ +│ ├── primitives/ # Radix UI base components +│ ├── hooks/ # Shared UI hooks (useSmartPolling, etc) +│ └── types/ # UI type definitions +├── projects/ +│ ├── components/ # Project UI components +│ ├── hooks/ # Project hooks (useProjectQueries, etc) +│ ├── services/ # Project API services +│ ├── types/ # Project type definitions +│ ├── tasks/ # Tasks sub-feature (nested under projects) +│ │ ├── components/ +│ │ ├── hooks/ # Task-specific hooks +│ │ ├── services/ # Task API services +│ │ └── types/ +│ └── documents/ # Documents sub-feature +│ ├── components/ +│ ├── services/ +│ └── types/ ``` -Optional: +#### TanStack Query Patterns -```bash -OPENAI_API_KEY=your-openai-key # Can be set via UI -LOGFIRE_TOKEN=your-logfire-token # For observability -LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR +All data fetching uses TanStack Query with consistent patterns: + +```typescript +// Query keys factory pattern +export const projectKeys = { + all: ["projects"] as const, + lists: () => [...projectKeys.all, "list"] as const, + detail: (id: string) => [...projectKeys.all, "detail", id] as const, +}; + +// Smart polling with visibility awareness +const { refetchInterval } = useSmartPolling(10000); // Pauses when tab inactive + +// Optimistic updates with rollback +useMutation({ + onMutate: async (data) => { + await queryClient.cancelQueries(key); + const previous = queryClient.getQueryData(key); + queryClient.setQueryData(key, optimisticData); + return { previous }; + }, + onError: (err, vars, context) => { + if (context?.previous) { + queryClient.setQueryData(key, context.previous); + } + }, +}); ``` -## File Organization +### Backend Architecture Details -### Frontend Structure +#### Service Layer Pattern -- `src/components/` - Reusable UI components -- `src/pages/` - Main application pages -- `src/services/` - API communication and business logic -- `src/hooks/` - Custom React hooks -- `src/contexts/` - React context providers +```python +# API Route -> Service -> Database +# src/server/api_routes/projects.py +@router.get("/{project_id}") +async def get_project(project_id: str): + return await project_service.get_project(project_id) -### Backend Structure +# src/server/services/project_service.py +async def get_project(project_id: str): + # Business logic here + return await db.fetch_project(project_id) +``` -- `src/server/` - Main FastAPI application -- `src/server/api_routes/` - API route handlers -- `src/server/services/` - Business logic services -- `src/mcp/` - MCP server implementation -- `src/agents/` - PydanticAI agent implementations +#### Error Handling Patterns + +```python +# Use specific exceptions +class ProjectNotFoundError(Exception): pass +class ValidationError(Exception): pass + +# Rich error responses +@app.exception_handler(ProjectNotFoundError) +async def handle_not_found(request, exc): + return JSONResponse( + status_code=404, + content={"detail": str(exc), "type": "not_found"} + ) +``` + +## Polling Architecture + +### HTTP Polling (replaced Socket.IO) + +- **Polling intervals**: 1-2s for active operations, 5-10s for background data +- **ETag caching**: Reduces bandwidth by ~70% via 304 Not Modified responses +- **Smart pausing**: Stops polling when browser tab is inactive +- **Progress endpoints**: `/api/progress/{id}` for operation tracking + +### Key Polling Hooks + +- `useSmartPolling` - Adjusts interval based on page visibility/focus +- `useCrawlProgressPolling` - Specialized for crawl progress with auto-cleanup +- `useProjectTasks` - Smart polling for task lists ## Database Schema Key tables in Supabase: - `sources` - Crawled websites and uploaded documents + - Stores metadata, crawl status, and configuration - `documents` - Processed document chunks with embeddings + - Text chunks with vector embeddings for semantic search - `projects` - Project management (optional feature) + - Contains features array, documents, and metadata - `tasks` - Task tracking linked to projects + - Status: todo, doing, review, done + - Assignee: User, Archon, AI IDE Agent - `code_examples` - Extracted code snippets + - Language, summary, and relevance metadata + +## API Naming Conventions + +### Task Status Values + +Use database values directly (no UI mapping): + +- `todo`, `doing`, `review`, `done` + +### Service Method Patterns + +- `get[Resource]sByProject(projectId)` - Scoped queries +- `get[Resource](id)` - Single resource +- `create[Resource](data)` - Create operations +- `update[Resource](id, updates)` - Updates +- `delete[Resource](id)` - Soft deletes + +### State Naming + +- `is[Action]ing` - Loading states (e.g., `isSwitchingProject`) +- `[resource]Error` - Error messages +- `selected[Resource]` - Current selection + +## Environment Variables + +Required in `.env`: + +```bash +SUPABASE_URL=https://your-project.supabase.co # Or http://host.docker.internal:8000 for local +SUPABASE_SERVICE_KEY=your-service-key-here # Use legacy key format for cloud Supabase +``` + +Optional: + +```bash +LOGFIRE_TOKEN=your-logfire-token # For observability +LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR +ARCHON_SERVER_PORT=8181 # Server port +ARCHON_MCP_PORT=8051 # MCP server port +ARCHON_UI_PORT=3737 # Frontend port +``` ## Common Development Tasks @@ -233,47 +372,72 @@ Key tables in Supabase: 1. Create route handler in `python/src/server/api_routes/` 2. Add service logic in `python/src/server/services/` 3. Include router in `python/src/server/main.py` -4. Update frontend service in `archon-ui-main/src/services/` +4. Update frontend service in `archon-ui-main/src/features/[feature]/services/` -### Add a new UI component +### Add a new UI component in features directory -1. Create component in `archon-ui-main/src/components/` -2. Add to page in `archon-ui-main/src/pages/` -3. Include any new API calls in services -4. Add tests in `archon-ui-main/test/` +1. Use Radix UI primitives from `src/features/ui/primitives/` +2. Create component in relevant feature folder under `src/features/[feature]/components/` +3. Define types in `src/features/[feature]/types/` +4. Use TanStack Query hook from `src/features/[feature]/hooks/` +5. Apply Tron-inspired glassmorphism styling with Tailwind ### Debug MCP connection issues 1. Check MCP health: `curl http://localhost:8051/health` -2. View MCP logs: `docker-compose logs archon-mcp` +2. View MCP logs: `docker compose logs archon-mcp` 3. Test tool execution via UI MCP page 4. Verify Supabase connection and credentials +### Fix TypeScript/Linting Issues + +```bash +# TypeScript errors in features +npx tsc --noEmit 2>&1 | grep "src/features" + +# Biome auto-fix for features +npm run biome:fix + +# ESLint for legacy code +npm run lint:files src/components/SomeComponent.tsx +``` + ## Code Quality Standards -We enforce code quality through automated linting and type checking: +### Frontend + +- **TypeScript**: Strict mode enabled, no implicit any +- **Biome** for `/src/features/`: 120 char lines, double quotes, trailing commas +- **ESLint** for legacy code: Standard React rules +- **Testing**: Vitest with React Testing Library + +### Backend - **Python 3.12** with 120 character line length -- **Ruff** for linting - checks for errors, warnings, unused imports, and code style -- **Mypy** for type checking - ensures type safety across the codebase -- **Auto-formatting** on save in IDEs to maintain consistent style -- Run `uv run ruff check` and `uv run mypy src/` locally before committing +- **Ruff** for linting - checks for errors, warnings, unused imports +- **Mypy** for type checking - ensures type safety +- **Pytest** for testing with async support ## MCP Tools Available -When connected to Cursor/Windsurf: +When connected to Client/Cursor/Windsurf: - `archon:perform_rag_query` - Search knowledge base - `archon:search_code_examples` - Find code snippets -- `archon:manage_project` - Project operations -- `archon:manage_task` - Task management +- `archon:create_project` - Create new project +- `archon:list_projects` - List all projects +- `archon:create_task` - Create task in project +- `archon:list_tasks` - List and filter tasks +- `archon:update_task` - Update task status/details - `archon:get_available_sources` - List knowledge sources ## Important Notes - Projects feature is optional - toggle in Settings UI - All services communicate via HTTP, not gRPC -- Socket.IO handles all real-time updates +- HTTP polling handles all updates - Frontend uses Vite proxy for API calls in development - Python backend uses `uv` for dependency management - Docker Compose handles service orchestration +- TanStack Query for all data fetching - NO PROP DRILLING +- Vertical slice architecture in `/features` - features own their sub-features diff --git a/PRPs/ai_docs/API_NAMING_CONVENTIONS.md b/PRPs/ai_docs/API_NAMING_CONVENTIONS.md new file mode 100644 index 00000000..82a97dfb --- /dev/null +++ b/PRPs/ai_docs/API_NAMING_CONVENTIONS.md @@ -0,0 +1,163 @@ +# API Naming Conventions + +## Overview +This document defines the naming conventions used throughout the Archon V2 codebase for consistency and clarity. + +## Task Status Values +**Database values only - no UI mapping:** +- `todo` - Task is in backlog/todo state +- `doing` - Task is actively being worked on +- `review` - Task is pending review +- `done` - Task is completed + +## Service Method Naming + +### Project Service (`projectService.ts`) + +#### Projects +- `listProjects()` - Get all projects +- `getProject(projectId)` - Get single project by ID +- `createProject(projectData)` - Create new project +- `updateProject(projectId, updates)` - Update project +- `deleteProject(projectId)` - Delete project + +#### Tasks +- `getTasksByProject(projectId)` - Get all tasks for a specific project +- `getTask(taskId)` - Get single task by ID +- `createTask(taskData)` - Create new task +- `updateTask(taskId, updates)` - Update task with partial data +- `updateTaskStatus(taskId, status)` - Update only task status +- `updateTaskOrder(taskId, newOrder, newStatus?)` - Update task position/order +- `deleteTask(taskId)` - Delete task (soft delete/archive) +- `getTasksByStatus(status)` - Get all tasks with specific status + +#### Documents +- `getDocuments(projectId)` - Get all documents for project +- `getDocument(projectId, docId)` - Get single document +- `createDocument(projectId, documentData)` - Create document +- `updateDocument(projectId, docId, updates)` - Update document +- `deleteDocument(projectId, docId)` - Delete document + +#### Versions +- `createVersion(projectId, field, content)` - Create version snapshot +- `listVersions(projectId, fieldName?)` - List version history +- `getVersion(projectId, fieldName, versionNumber)` - Get specific version +- `restoreVersion(projectId, fieldName, versionNumber)` - Restore version + +## API Endpoint Patterns + +### RESTful Endpoints +``` +GET /api/projects - List all projects +POST /api/projects - Create project +GET /api/projects/{project_id} - Get project +PUT /api/projects/{project_id} - Update project +DELETE /api/projects/{project_id} - Delete project + +GET /api/projects/{project_id}/tasks - Get project tasks +POST /api/tasks - Create task (project_id in body) +GET /api/tasks/{task_id} - Get task +PUT /api/tasks/{task_id} - Update task +DELETE /api/tasks/{task_id} - Delete task + +GET /api/projects/{project_id}/docs - Get project documents +POST /api/projects/{project_id}/docs - Create document +GET /api/projects/{project_id}/docs/{doc_id} - Get document +PUT /api/projects/{project_id}/docs/{doc_id} - Update document +DELETE /api/projects/{project_id}/docs/{doc_id} - Delete document +``` + +### Progress/Polling Endpoints +``` +GET /api/progress/{operation_id} - Generic operation progress +GET /api/knowledge/crawl-progress/{id} - Crawling progress +GET /api/agent-chat/sessions/{id}/messages - Chat messages +``` + +## Component Naming + +### Hooks +- `use[Feature]` - Custom hooks (e.g., `usePolling`, `useProjectMutation`) +- Returns object with: `{ data, isLoading, error, refetch }` + +### Services +- `[feature]Service` - Service modules (e.g., `projectService`, `crawlProgressService`) +- Methods return Promises with typed responses + +### Components +- `[Feature][Type]` - UI components (e.g., `TaskBoardView`, `EditTaskModal`) +- Props interfaces: `[Component]Props` + +## State Variable Naming + +### Loading States +- `isLoading[Feature]` - Boolean loading indicators +- `isSwitchingProject` - Specific operation states +- `movingTaskIds` - Set/Array of items being processed + +### Error States +- `[feature]Error` - Error message strings +- `taskOperationError` - Specific operation errors + +### Data States +- `[feature]s` - Plural for collections (e.g., `tasks`, `projects`) +- `selected[Feature]` - Currently selected item +- `[feature]Data` - Raw data from API + +## Type Definitions + +### Database Types (from backend) +```typescript +type DatabaseTaskStatus = 'todo' | 'doing' | 'review' | 'done'; +type Assignee = 'User' | 'Archon' | 'AI IDE Agent'; +``` + +### Request/Response Types +```typescript +Create[Feature]Request // e.g., CreateTaskRequest +Update[Feature]Request // e.g., UpdateTaskRequest +[Feature]Response // e.g., TaskResponse +``` + +## Function Naming Patterns + +### Event Handlers +- `handle[Event]` - Generic handlers (e.g., `handleProjectSelect`) +- `on[Event]` - Props callbacks (e.g., `onTaskMove`, `onRefresh`) + +### Operations +- `load[Feature]` - Fetch data (e.g., `loadTasksForProject`) +- `save[Feature]` - Persist changes (e.g., `saveTask`) +- `delete[Feature]` - Remove items (e.g., `deleteTask`) +- `refresh[Feature]` - Reload data (e.g., `refreshTasks`) + +### Formatting/Transformation +- `format[Feature]` - Format for display (e.g., `formatTask`) +- `validate[Feature]` - Validate data (e.g., `validateUpdateTask`) + +## Best Practices + +### ✅ Do Use +- `getTasksByProject(projectId)` - Clear scope with context +- `status` - Single source of truth from database +- Direct database values everywhere (no mapping) +- Polling with `usePolling` hook for data fetching +- Async/await with proper error handling +- ETag headers for efficient polling +- Loading indicators during operations + +## Current Architecture Patterns + +### Polling & Data Fetching +- HTTP polling with `usePolling` and `useCrawlProgressPolling` hooks +- ETag-based caching for bandwidth efficiency +- Loading state indicators (`isLoading`, `isSwitchingProject`) +- Error toast notifications for user feedback +- Manual refresh triggers via `refetch()` +- Immediate UI updates followed by API calls + +### Service Architecture +- Specialized services for different domains (`projectService`, `crawlProgressService`) +- Direct database value usage (no UI/DB mapping) +- Promise-based async operations +- Typed request/response interfaces \ No newline at end of file diff --git a/PRPs/ai_docs/ARCHITECTURE.md b/PRPs/ai_docs/ARCHITECTURE.md new file mode 100644 index 00000000..04494b39 --- /dev/null +++ b/PRPs/ai_docs/ARCHITECTURE.md @@ -0,0 +1,481 @@ +# Archon Architecture + +## Overview + +Archon follows a **Vertical Slice Architecture** pattern where features are organized by business capability rather than technical layers. Each module is self-contained with its own API, business logic, and data access, making the system modular, maintainable, and ready for future microservice extraction if needed. + +## Core Principles + +1. **Feature Cohesion**: All code for a feature lives together +2. **Module Independence**: Modules communicate through well-defined interfaces +3. **Vertical Slices**: Each feature contains its complete stack (API → Service → Repository) +4. **Shared Minimal**: Only truly cross-cutting concerns go in shared +5. **Migration Ready**: Structure supports easy extraction to microservices + +## Directory Structure + +``` +archon/ +├── python/ +│ ├── src/ +│ │ ├── knowledge/ # Knowledge Management Module +│ │ │ ├── __init__.py +│ │ │ ├── main.py # Knowledge module entry point +│ │ │ ├── shared/ # Shared within knowledge context +│ │ │ │ ├── models.py +│ │ │ │ ├── exceptions.py +│ │ │ │ └── utils.py +│ │ │ └── features/ # Knowledge feature slices +│ │ │ ├── crawling/ # Web crawling feature +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api.py # Crawl endpoints +│ │ │ │ ├── service.py # Crawling orchestration +│ │ │ │ ├── models.py # Crawl-specific models +│ │ │ │ ├── repository.py # Crawl data storage +│ │ │ │ └── tests/ +│ │ │ ├── document_processing/ # Document upload & processing +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api.py # Upload endpoints +│ │ │ │ ├── service.py # PDF/DOCX processing +│ │ │ │ ├── extractors.py # Text extraction +│ │ │ │ └── tests/ +│ │ │ ├── embeddings/ # Vector embeddings +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api.py # Embedding endpoints +│ │ │ │ ├── service.py # OpenAI/local embeddings +│ │ │ │ ├── models.py +│ │ │ │ └── repository.py # Vector storage +│ │ │ ├── search/ # RAG search +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api.py # Search endpoints +│ │ │ │ ├── service.py # Search algorithms +│ │ │ │ ├── reranker.py # Result reranking +│ │ │ │ └── tests/ +│ │ │ ├── code_extraction/ # Code snippet extraction +│ │ │ │ ├── __init__.py +│ │ │ │ ├── service.py # Code parsing +│ │ │ │ ├── analyzers.py # Language detection +│ │ │ │ └── repository.py +│ │ │ └── source_management/ # Knowledge source CRUD +│ │ │ ├── __init__.py +│ │ │ ├── api.py +│ │ │ ├── service.py +│ │ │ └── repository.py +│ │ │ +│ │ ├── projects/ # Project Management Module +│ │ │ ├── __init__.py +│ │ │ ├── main.py # Projects module entry point +│ │ │ ├── shared/ # Shared within projects context +│ │ │ │ ├── database.py # Project DB utilities +│ │ │ │ ├── models.py # Shared project models +│ │ │ │ └── exceptions.py # Project-specific exceptions +│ │ │ └── features/ # Project feature slices +│ │ │ ├── project_management/ # Project CRUD +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api.py # Project endpoints +│ │ │ │ ├── service.py # Project business logic +│ │ │ │ ├── models.py # Project models +│ │ │ │ ├── repository.py # Project DB operations +│ │ │ │ └── tests/ +│ │ │ ├── task_management/ # Task CRUD +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api.py # Task endpoints +│ │ │ │ ├── service.py # Task business logic +│ │ │ │ ├── models.py # Task models +│ │ │ │ ├── repository.py # Task DB operations +│ │ │ │ └── tests/ +│ │ │ ├── task_ordering/ # Drag-and-drop reordering +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api.py # Reorder endpoints +│ │ │ │ ├── service.py # Reordering algorithm +│ │ │ │ └── tests/ +│ │ │ ├── document_management/ # Project documents +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api.py # Document endpoints +│ │ │ │ ├── service.py # Document logic +│ │ │ │ ├── models.py +│ │ │ │ └── repository.py +│ │ │ ├── document_versioning/ # Version control +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api.py # Version endpoints +│ │ │ │ ├── service.py # Versioning logic +│ │ │ │ ├── models.py # Version models +│ │ │ │ └── repository.py # Version storage +│ │ │ ├── ai_generation/ # AI project creation +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api.py # Generate endpoints +│ │ │ │ ├── service.py # AI orchestration +│ │ │ │ ├── agents.py # Agent interactions +│ │ │ │ ├── progress.py # Progress tracking +│ │ │ │ └── prompts.py # Generation prompts +│ │ │ ├── source_linking/ # Link to knowledge base +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api.py # Link endpoints +│ │ │ │ ├── service.py # Linking logic +│ │ │ │ └── repository.py # Junction table ops +│ │ │ └── bulk_operations/ # Batch updates +│ │ │ ├── __init__.py +│ │ │ ├── api.py # Bulk endpoints +│ │ │ ├── service.py # Batch processing +│ │ │ └── tests/ +│ │ │ +│ │ ├── mcp_server/ # MCP Protocol Server (IDE Integration) +│ │ │ ├── __init__.py +│ │ │ ├── main.py # MCP server entry point +│ │ │ ├── server.py # FastMCP server setup +│ │ │ ├── features/ # MCP tool implementations +│ │ │ │ ├── projects/ # Project tools for IDEs +│ │ │ │ │ ├── __init__.py +│ │ │ │ │ ├── project_tools.py +│ │ │ │ │ └── tests/ +│ │ │ │ ├── tasks/ # Task tools for IDEs +│ │ │ │ │ ├── __init__.py +│ │ │ │ │ ├── task_tools.py +│ │ │ │ │ └── tests/ +│ │ │ │ ├── documents/ # Document tools for IDEs +│ │ │ │ │ ├── __init__.py +│ │ │ │ │ ├── document_tools.py +│ │ │ │ │ ├── version_tools.py +│ │ │ │ │ └── tests/ +│ │ │ │ └── feature_tools.py # Feature management +│ │ │ ├── modules/ # MCP modules +│ │ │ │ └── archon.py # Main Archon MCP module +│ │ │ └── utils/ # MCP utilities +│ │ │ └── tool_utils.py +│ │ │ +│ │ ├── agents/ # AI Agents Module +│ │ │ ├── __init__.py +│ │ │ ├── main.py # Agents module entry point +│ │ │ ├── config.py # Agent configurations +│ │ │ ├── features/ # Agent capabilities +│ │ │ │ ├── document_agent/ # Document processing agent +│ │ │ │ │ ├── __init__.py +│ │ │ │ │ ├── agent.py # PydanticAI agent +│ │ │ │ │ ├── prompts.py # Agent prompts +│ │ │ │ │ └── tools.py # Agent tools +│ │ │ │ ├── code_agent/ # Code analysis agent +│ │ │ │ │ ├── __init__.py +│ │ │ │ │ ├── agent.py +│ │ │ │ │ └── analyzers.py +│ │ │ │ └── project_agent/ # Project creation agent +│ │ │ │ ├── __init__.py +│ │ │ │ ├── agent.py +│ │ │ │ ├── prp_generator.py +│ │ │ │ └── task_generator.py +│ │ │ └── shared/ # Shared agent utilities +│ │ │ ├── base_agent.py +│ │ │ ├── llm_client.py +│ │ │ └── response_models.py +│ │ │ +│ │ ├── shared/ # Shared Across All Modules +│ │ │ ├── database/ # Database utilities +│ │ │ │ ├── __init__.py +│ │ │ │ ├── supabase.py # Supabase client +│ │ │ │ ├── migrations.py # DB migrations +│ │ │ │ └── connection_pool.py +│ │ │ ├── auth/ # Authentication +│ │ │ │ ├── __init__.py +│ │ │ │ ├── api_keys.py +│ │ │ │ └── permissions.py +│ │ │ ├── config/ # Configuration +│ │ │ │ ├── __init__.py +│ │ │ │ ├── settings.py # Environment settings +│ │ │ │ └── logfire_config.py # Logging config +│ │ │ ├── middleware/ # HTTP middleware +│ │ │ │ ├── __init__.py +│ │ │ │ ├── cors.py +│ │ │ │ └── error_handler.py +│ │ │ └── utils/ # General utilities +│ │ │ ├── __init__.py +│ │ │ ├── datetime_utils.py +│ │ │ └── json_utils.py +│ │ │ +│ │ └── main.py # Application orchestrator +│ │ +│ └── tests/ # Integration tests +│ ├── test_api_essentials.py +│ ├── test_service_integration.py +│ └── fixtures/ +│ +├── archon-ui-main/ # Frontend Application +│ ├── src/ +│ │ ├── pages/ # Page components +│ │ │ ├── KnowledgeBasePage.tsx +│ │ │ ├── ProjectPage.tsx +│ │ │ ├── SettingsPage.tsx +│ │ │ └── MCPPage.tsx +│ │ ├── components/ # Reusable components +│ │ │ ├── knowledge-base/ # Knowledge features +│ │ │ ├── project-tasks/ # Project features +│ │ │ └── ui/ # Shared UI components +│ │ ├── services/ # API services +│ │ │ ├── api.ts # Base API client +│ │ │ ├── knowledgeBaseService.ts +│ │ │ ├── projectService.ts +│ │ │ └── pollingService.ts # New polling utilities +│ │ ├── hooks/ # React hooks +│ │ │ ├── usePolling.ts # Polling hook +│ │ │ ├── useDatabaseMutation.ts # DB-first mutations +│ │ │ └── useAsyncAction.ts +│ │ └── contexts/ # React contexts +│ │ ├── ToastContext.tsx +│ │ └── ThemeContext.tsx +│ │ +│ └── tests/ # Frontend tests +│ +├── PRPs/ # Product Requirement Prompts +│ ├── templates/ # PRP templates +│ ├── ai_docs/ # AI context documentation +│ └── *.md # Feature PRPs +│ +├── docs/ # Documentation +│ └── architecture/ # Architecture decisions +│ +└── docker/ # Docker configurations + ├── Dockerfile + └── docker-compose.yml +``` + +## Module Descriptions + +### Knowledge Module (`src/knowledge/`) + +Core knowledge management functionality including web crawling, document processing, embeddings, and RAG search. This is the heart of Archon's knowledge engine. + +**Key Features:** + +- Web crawling with JavaScript rendering +- Document upload and text extraction +- Vector embeddings and similarity search +- Code snippet extraction and indexing +- Source management and organization + +### Projects Module (`src/projects/`) + +Project and task management system with AI-powered project generation. Currently optional via feature flag. + +**Key Features:** + +- Project CRUD operations +- Task management with drag-and-drop ordering +- Document management with versioning +- AI-powered project generation +- Integration with knowledge base sources + +### MCP Server Module (`src/mcp_server/`) + +Model Context Protocol server that exposes Archon functionality to IDEs like Cursor and Windsurf. + +**Key Features:** + +- Tool-based API for IDE integration +- Project and task management tools +- Document operations +- Async operation support + +### Agents Module (`src/agents/`) + +AI agents powered by PydanticAI for intelligent document processing and project generation. + +**Key Features:** + +- Document analysis and summarization +- Code understanding and extraction +- Project requirement generation +- Task breakdown and planning + +### Shared Module (`src/shared/`) + +Cross-cutting concerns shared across all modules. Kept minimal to maintain module independence. + +**Key Components:** + +- Database connections and utilities +- Authentication and authorization +- Configuration management +- Logging and observability +- Common middleware + +## Communication Patterns + +### Inter-Module Communication + +Modules communicate through: + +1. **Direct HTTP API Calls** (current) + - Projects module calls Knowledge module APIs + - Simple and straightforward + - Works well for current scale + +2. **Event Bus** (future consideration) + + ```python + # Example event-driven communication + await event_bus.publish("project.created", { + "project_id": "123", + "created_by": "user" + }) + ``` + +3. **Shared Database** (current reality) + - All modules use same Supabase instance + - Direct foreign keys between contexts + - Will need refactoring for true microservices + +## Feature Flags + +Features can be toggled via environment variables: + +```python +# settings.py +PROJECTS_ENABLED = env.bool("PROJECTS_ENABLED", default=False) +TASK_ORDERING_ENABLED = env.bool("TASK_ORDERING_ENABLED", default=True) +AI_GENERATION_ENABLED = env.bool("AI_GENERATION_ENABLED", default=True) +``` + +## Database Architecture + +Currently using a shared Supabase (PostgreSQL) database: + +```sql +-- Knowledge context tables +sources +documents +code_examples + +-- Projects context tables +archon_projects +archon_tasks +archon_document_versions + +-- Cross-context junction tables +archon_project_sources -- Links projects to knowledge +``` + +## API Structure + +Each feature exposes its own API routes: + +``` +/api/knowledge/ + /crawl # Web crawling + /upload # Document upload + /search # RAG search + /sources # Source management + +/api/projects/ + /projects # Project CRUD + /tasks # Task management + /tasks/reorder # Task ordering + /documents # Document management + /generate # AI generation +``` + +## Deployment Architecture + +### Current mixed + +### Future (service modules) + +Each module can become its own service: + +```yaml +# docker-compose.yml (future) +services: + knowledge: + image: archon-knowledge + ports: ["8001:8000"] + + projects: + image: archon-projects + ports: ["8002:8000"] + + mcp-server: + image: archon-mcp + ports: ["8051:8051"] + + agents: + image: archon-agents + ports: ["8052:8052"] +``` + +## Migration Path + +### Phase 1: Current State (Modules/service) + +- All code in one repository +- Shared database +- Single deployment + +### Phase 2: Vertical Slices + +- Reorganize by feature +- Clear module boundaries +- Feature flags for control + +## Development Guidelines + +### Adding a New Feature + +1. **Identify the Module**: Which bounded context does it belong to? +2. **Create Feature Slice**: New folder under `module/features/` +3. **Implement Vertical Slice**: + - `api.py` - HTTP endpoints + - `service.py` - Business logic + - `models.py` - Data models + - `repository.py` - Data access + - `tests/` - Feature tests + +### Testing Strategy + +- **Unit Tests**: Each feature has its own tests +- **Integration Tests**: Test module boundaries +- **E2E Tests**: Test complete user flows + +### Code Organization Rules + +1. **Features are Self-Contained**: All code for a feature lives together +2. **No Cross-Feature Imports**: Use module's shared or API calls +3. **Shared is Minimal**: Only truly cross-cutting concerns +4. **Dependencies Point Inward**: Features → Module Shared → Global Shared + +## Technology Stack + +### Backend + +- **FastAPI**: Web framework +- **Supabase**: Database and auth +- **PydanticAI**: AI agents +- **OpenAI**: Embeddings and LLM +- **Crawl4AI**: Web crawling + +### Frontend + +- **React**: UI framework +- **TypeScript**: Type safety +- **TailwindCSS**: Styling +- **React Query**: Data fetching +- **Vite**: Build tool + +### Infrastructure + +- **Docker**: Containerization +- **PostgreSQL**: Database (via Supabase, desire to support any PostgreSQL) +- **pgvector**: Vector storage, Desire to support ChromaDB, Pinecone, Weaviate, etc. + +## Future Considerations + +### Planned Improvements + +1. **Remove Socket.IO**: Replace with polling (in progress) +2. **API Gateway**: Central entry point for all services +3. **Separate Databases**: One per bounded context + +### Scalability Path + +1. **Vertical Scaling**: Current approach, works for single-user +2. **Horizontal Scaling**: Add load balancer and multiple instances + +--- + +This architecture provides a clear path from the current monolithic application to a more modular approach with vertical slicing, for easy potential to service separation if needed. diff --git a/PRPs/ai_docs/ETAG_IMPLEMENTATION.md b/PRPs/ai_docs/ETAG_IMPLEMENTATION.md new file mode 100644 index 00000000..b8ebcedc --- /dev/null +++ b/PRPs/ai_docs/ETAG_IMPLEMENTATION.md @@ -0,0 +1,39 @@ +# ETag Implementation + +## Current Implementation + +Our ETag implementation provides efficient HTTP caching for polling endpoints to reduce bandwidth usage. + +### What It Does +- **Generates ETags**: Creates MD5 hashes of JSON response data +- **Checks ETags**: Simple string equality comparison between client's `If-None-Match` header and current data's ETag +- **Returns 304**: When ETags match, returns `304 Not Modified` with no body (saves bandwidth) + +### How It Works +1. Server generates ETag from response data using MD5 hash +2. Client sends previous ETag in `If-None-Match` header +3. Server compares ETags: + - **Match**: Returns 304 (no body) + - **No match**: Returns 200 with new data and new ETag + +### Example +```python +# Server generates: ETag: "a3c2f1e4b5d6789" +# Client sends: If-None-Match: "a3c2f1e4b5d6789" +# Server returns: 304 Not Modified (no body) +``` + +## Limitations + +Our implementation is simplified and doesn't support full RFC 7232 features: +- ❌ Wildcard (`*`) matching +- ❌ Multiple ETags (`"etag1", "etag2"`) +- ❌ Weak validators (`W/"etag"`) +- ✅ Single ETag comparison only + +This works perfectly for our browser-to-API polling use case but may need enhancement for CDN/proxy support. + +## Files +- Implementation: `python/src/server/utils/etag_utils.py` +- Tests: `python/tests/server/utils/test_etag_utils.py` +- Used in: Progress API, Projects API polling endpoints \ No newline at end of file diff --git a/PRPs/ai_docs/POLLING_ARCHITECTURE.md b/PRPs/ai_docs/POLLING_ARCHITECTURE.md new file mode 100644 index 00000000..0c034b62 --- /dev/null +++ b/PRPs/ai_docs/POLLING_ARCHITECTURE.md @@ -0,0 +1,194 @@ +# Polling Architecture Documentation + +## Overview +Archon V2 uses HTTP polling instead of WebSockets for real-time updates. This simplifies the architecture, reduces complexity, and improves maintainability while providing adequate responsiveness for project management tasks. + +## Core Components + +### 1. usePolling Hook (`archon-ui-main/src/hooks/usePolling.ts`) +Generic polling hook that manages periodic data fetching with smart optimizations. + +**Key Features:** +- Configurable polling intervals (default: 3 seconds) +- Automatic pause during browser tab inactivity +- ETag-based caching to reduce bandwidth +- Manual refresh capability + +**Usage:** +```typescript +const { data, isLoading, error, refetch } = usePolling('/api/projects', { + interval: 5000, + enabled: true, + onSuccess: (data) => console.log('Projects updated:', data) +}); +``` + +### 2. Specialized Progress Services +Individual services handle specific progress tracking needs: + +**CrawlProgressService (`archon-ui-main/src/services/crawlProgressService.ts`)** +- Tracks website crawling operations +- Maps backend status to UI-friendly format +- Includes in-flight request guard to prevent overlapping fetches +- 1-second polling interval during active crawls + +**Polling Endpoints:** +- `/api/projects` - Project list updates +- `/api/projects/{project_id}/tasks` - Task list for active project +- `/api/crawl-progress/{progress_id}` - Website crawling progress +- `/api/agent-chat/sessions/{session_id}/messages` - Chat messages + +## Backend Support + +### ETag Implementation (`python/src/server/utils/etag_utils.py`) +Server-side optimization to reduce unnecessary data transfer. + +**How it works:** +1. Server generates ETag hash from response data +2. Client sends `If-None-Match` header with cached ETag +3. Server returns 304 Not Modified if data unchanged +4. Client uses cached data, reducing bandwidth by ~70% + +### Progress API (`python/src/server/api_routes/progress_api.py`) +Dedicated endpoints for progress tracking: +- `GET /api/crawl-progress/{progress_id}` - Returns crawling status with ETag support +- Includes completion percentage, current step, and error details + +## State Management + +### Loading States +Visual feedback during operations: +- `movingTaskIds: Set` - Tracks tasks being moved +- `isSwitchingProject: boolean` - Project transition state +- Loading overlays prevent concurrent operations + +## Error Handling + +### Retry Strategy +```typescript +retryCount: 3 +retryDelay: attempt => Math.min(1000 * 2 ** attempt, 30000) +``` +- Exponential backoff: 1s, 2s, 4s... +- Maximum retry delay: 30 seconds +- Automatic recovery after network issues + +### User Feedback +- Toast notifications for errors +- Loading spinners during operations +- Clear error messages with recovery actions + +## Performance Optimizations + +### 1. Request Deduplication +Prevents multiple components from making identical requests: +```typescript +const cacheKey = `${endpoint}-${JSON.stringify(params)}`; +if (pendingRequests.has(cacheKey)) { + return pendingRequests.get(cacheKey); +} +``` + +### 2. Smart Polling Intervals +- Active operations: 1-2 second intervals +- Background data: 5-10 second intervals +- Paused when tab inactive (visibility API) + +### 3. Selective Updates +Only polls active/relevant data: +- Tasks poll only for selected project +- Progress polls only during active operations +- Chat polls only for open sessions + +## Architecture Benefits + +### What We Have +- **Simple HTTP polling** - Standard request/response pattern +- **Automatic error recovery** - Built-in retry with exponential backoff +- **ETag caching** - 70% bandwidth reduction via 304 responses +- **Easy debugging** - Standard HTTP requests visible in DevTools +- **No connection limits** - Scales with standard HTTP infrastructure +- **Consolidated polling hooks** - Single pattern for all data fetching + +### Trade-offs +- **Latency:** 1-5 second delay vs instant updates +- **Bandwidth:** More requests, but mitigated by ETags +- **Battery:** Slightly higher mobile battery usage + +## Developer Guidelines + +### Adding New Polling Endpoint + +1. **Frontend - Use the usePolling hook:** +```typescript +// In your component or custom hook +const { data, isLoading, error, refetch } = usePolling('/api/new-endpoint', { + interval: 5000, + enabled: true, + staleTime: 2000 +}); +``` + +2. **Backend - Add ETag support:** +```python +from ..utils.etag_utils import generate_etag, check_etag + +@router.get("/api/new-endpoint") +async def get_data(request: Request): + data = fetch_data() + etag = generate_etag(data) + + if check_etag(request, etag): + return Response(status_code=304) + + return JSONResponse( + content=data, + headers={"ETag": etag} + ) +``` + +3. **For progress tracking, use useCrawlProgressPolling:** +```typescript +const { data, isLoading } = useCrawlProgressPolling(operationId, { + onSuccess: (data) => { + if (data.status === 'completed') { + // Handle completion + } + } +}); +``` + +### Best Practices + +1. **Always provide loading states** - Users should know when data is updating +2. **Handle errors gracefully** - Show toast notifications with clear messages +3. **Respect polling intervals** - Don't poll faster than necessary +4. **Clean up on unmount** - Cancel pending requests when components unmount +5. **Use ETag caching** - Reduce bandwidth with 304 responses + +## Testing Polling Behavior + +### Manual Testing +1. Open Network tab in DevTools +2. Look for requests with 304 status (cache hits) +3. Verify polling stops when switching tabs +4. Test error recovery by stopping backend + +### Debugging Tips +- Check `localStorage` for cached ETags +- Monitor `console.log` for polling lifecycle events +- Use React DevTools to inspect hook states +- Watch for memory leaks in long-running sessions + +## Future Improvements + +### Planned Enhancements +- WebSocket fallback for critical updates +- Configurable per-user polling rates +- Smart polling based on user activity patterns +- GraphQL subscriptions for selective field updates + +### Considered Alternatives +- Server-Sent Events (SSE) - One-way real-time updates +- Long polling - Reduced request frequency +- WebRTC data channels - P2P updates between clients \ No newline at end of file diff --git a/PRPs/ai_docs/optimistic_updates.md b/PRPs/ai_docs/optimistic_updates.md new file mode 100644 index 00000000..5884338b --- /dev/null +++ b/PRPs/ai_docs/optimistic_updates.md @@ -0,0 +1,148 @@ +# Optimistic Updates Pattern (Future State) + +**⚠️ STATUS:** This is not currently implemented. There is a proof‑of‑concept (POC) on the frontend Project page. This document describes the desired future state for handling optimistic updates in a simple, consistent way. + +## Mental Model + +Think of optimistic updates as "assuming success" - update the UI immediately for instant feedback, then verify with the server. If something goes wrong, revert to the last known good state. + +## The Pattern + +```typescript +// 1. Save current state (for rollback) — take an immutable snapshot +const previousState = structuredClone(currentState); + +// 2. Update UI immediately +setState(newState); + +// 3. Call API +try { + const serverState = await api.updateResource(newState); + // Success — use server as the source of truth + setState(serverState); +} catch (error) { + // 4. Rollback on failure + setState(previousState); + showToast("Failed to update. Reverted changes.", "error"); +} +``` + +## Implementation Approach + +### Simple Hook Pattern + +```typescript +function useOptimistic(initialValue: T, updateFn: (value: T) => Promise) { + const [value, setValue] = useState(initialValue); + const [isUpdating, setIsUpdating] = useState(false); + const previousValueRef = useRef(initialValue); + const opSeqRef = useRef(0); // monotonically increasing op id + const mountedRef = useRef(true); // avoid setState after unmount + useEffect(() => () => { mountedRef.current = false; }, []); + + const optimisticUpdate = async (newValue: T) => { + const opId = ++opSeqRef.current; + // Save for rollback + previousValueRef.current = value; + + // Update immediately + if (mountedRef.current) setValue(newValue); + if (mountedRef.current) setIsUpdating(true); + + try { + const result = await updateFn(newValue); + // Apply only if latest op and still mounted + if (mountedRef.current && opId === opSeqRef.current) { + setValue(result); // Server is source of truth + } + } catch (error) { + // Rollback + if (mountedRef.current && opId === opSeqRef.current) { + setValue(previousValueRef.current); + } + throw error; + } finally { + if (mountedRef.current && opId === opSeqRef.current) { + setIsUpdating(false); + } + } + }; + + return { value, optimisticUpdate, isUpdating }; +} +``` + +### Usage Example + +```typescript +// In a component +const { + value: task, + optimisticUpdate, + isUpdating, +} = useOptimistic(initialTask, (task) => + projectService.updateTask(task.id, task), +); + +// Handle user action +const handleStatusChange = (newStatus: string) => { + optimisticUpdate({ ...task, status: newStatus }).catch((error) => + showToast("Failed to update task", "error"), + ); +}; +``` + +## Key Principles + +1. **Keep it simple** — save, update, roll back. +2. **Server is the source of truth** — always use the server response as the final state. +3. **User feedback** — show loading states and clear error messages. +4. **Selective usage** — only where instant feedback matters: + - Drag‑and‑drop + - Status changes + - Toggle switches + - Quick edits + +## What NOT to Do + +- Don't track complex state histories +- Don't try to merge conflicts +- Use with caution for create/delete operations. If used, generate temporary client IDs, reconcile with server‑assigned IDs, ensure idempotency, and define clear rollback/error states. Prefer non‑optimistic flows when side effects are complex. +- Don't over-engineer with queues or reconciliation + +## When to Implement + +Implement optimistic updates when: + +- Users complain about UI feeling "slow" +- Drag-and-drop or reordering feels laggy +- Quick actions (like checkbox toggles) feel unresponsive +- Network latency is noticeable (> 200ms) + +## Success Metrics + +When implemented correctly: + +- UI feels instant (< 100ms response) +- Rollbacks are rare (< 1% of updates) +- Error messages are clear +- Users understand what happened when things fail + +## Production Considerations + +The examples above are simplified for clarity. Production implementations should consider: + +1. **Deep cloning**: Use `structuredClone()` or a deep clone utility for complex state + + ```typescript + const previousState = structuredClone(currentState); // Proper deep clone + ``` + +2. **Race conditions**: Handle out-of-order responses with operation IDs +3. **Unmount safety**: Avoid setState after component unmount +4. **Debouncing**: For rapid updates (e.g., sliders), debounce API calls +5. **Conflict resolution**: For collaborative editing, consider operational transforms +6. **Polling/ETag interplay**: When polling, ignore stale responses (e.g., compare opId or Last-Modified) and rely on ETag/304 to prevent flicker overriding optimistic state. +7. **Idempotency & retries**: Use idempotency keys on write APIs so client retries (or duplicate submits) don't create duplicate effects. + +These complexities are why we recommend starting simple and only adding optimistic updates where the UX benefit is clear. diff --git a/archon-ui-main/.dockerignore b/archon-ui-main/.dockerignore index bbae0365..9e1e2818 100644 --- a/archon-ui-main/.dockerignore +++ b/archon-ui-main/.dockerignore @@ -43,6 +43,16 @@ docker-compose.yml # Tests coverage test-results +tests/ +**/*.test.ts +**/*.test.tsx +**/*.spec.ts +**/*.spec.tsx +**/__tests__ +**/*.e2e.test.ts +**/*.integration.test.ts +vitest.config.ts +tsconfig.prod.json # Documentation README.md diff --git a/archon-ui-main/.eslintrc.cjs b/archon-ui-main/.eslintrc.cjs index f7de173a..62100daa 100644 --- a/archon-ui-main/.eslintrc.cjs +++ b/archon-ui-main/.eslintrc.cjs @@ -6,28 +6,119 @@ module.exports = { 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', ], - ignorePatterns: ['dist', '.eslintrc.cjs'], + ignorePatterns: [ + 'dist', + '.eslintrc.cjs', + 'public', + '__mocks__', + '*.config.js', + '*.config.ts', + 'coverage', + 'node_modules', + 'src/features/**' // Biome handles this directory + ], parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, plugins: ['react-refresh'], rules: { + /** + * LINTING STRATEGY FOR ALPHA DEVELOPMENT: + * + * Development: Warnings don't block local development, allowing rapid iteration + * CI/PR: Run with --max-warnings 0 to treat warnings as errors before merge + * + * Philosophy: + * - Strict typing where it helps AI assistants (Claude Code, Copilot, etc.) + * - Pragmatic flexibility for alpha-stage rapid development + * - Console.log allowed locally but caught in CI + * - Progressive enhancement: stricter rules in /features (new code) vs /components (legacy) + */ + + // React Refresh 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], - '@typescript-eslint/no-unused-vars': ['warn', { + + // TypeScript - Pragmatic strictness for AI-assisted development + '@typescript-eslint/no-explicit-any': 'warn', // Visible but won't block development + '@typescript-eslint/no-non-null-assertion': 'warn', // Allow when developer is certain + '@typescript-eslint/no-empty-function': 'warn', // Sometimes needed for placeholders + '@typescript-eslint/ban-types': 'error', // Keep strict - prevents real issues + + // Help AI assistants understand code intent + '@typescript-eslint/explicit-function-return-type': ['warn', { + allowExpressions: true, + allowTypedFunctionExpressions: true, + allowHigherOrderFunctions: true, + allowDirectConstAssertionInArrowFunctions: true, + }], + + // Better TypeScript patterns + '@typescript-eslint/prefer-as-const': 'error', + + // Variable and import management - strict with escape hatches + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', - ignoreRestSiblings: true + ignoreRestSiblings: true, + destructuredArrayIgnorePattern: '^_' }], - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/ban-types': 'off', - '@typescript-eslint/no-non-null-assertion': 'warn', - '@typescript-eslint/no-inferrable-types': 'off', + + // React hooks - warn to allow intentional omissions during development 'react-hooks/exhaustive-deps': 'warn', - 'no-case-declarations': 'off', - 'no-constant-condition': 'warn', - 'prefer-const': 'warn', - 'no-undef': 'off', + + // Console usage - warn locally, CI treats as error + 'no-console': ['warn', { allow: ['error', 'warn'] }], // console.log caught but not blocking + + // General code quality + 'prefer-const': 'error', + 'no-var': 'error', + 'no-constant-condition': 'error', + 'no-debugger': 'warn', // Warn in dev, error in CI + 'no-alert': 'error', + + // Disable rules that conflict with TypeScript + 'no-undef': 'off', // TypeScript handles this better + 'no-unused-vars': 'off', // Use @typescript-eslint/no-unused-vars instead }, -} + + // Override rules for specific file types and directories + overrides: [ + { + // Stricter rules for new vertical slice architecture + files: ['src/features/**/*.{ts,tsx}'], + rules: { + '@typescript-eslint/no-explicit-any': 'error', // No any in new code + '@typescript-eslint/explicit-function-return-type': ['error', { + allowExpressions: true, + allowTypedFunctionExpressions: true, + }], + 'no-console': ['error', { allow: ['error', 'warn'] }], // Stricter console usage + } + }, + { + // More lenient for legacy components being migrated + files: ['src/components/**/*.{ts,tsx}', 'src/services/**/*.{ts,tsx}'], + rules: { + '@typescript-eslint/no-explicit-any': 'warn', // Still visible during migration + '@typescript-eslint/explicit-function-return-type': 'off', // Not required for legacy + 'no-console': 'warn', // Warn during migration + } + }, + { + // Test files - most lenient but still helpful + files: ['**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}', 'test/**/*'], + rules: { + '@typescript-eslint/no-explicit-any': 'warn', // OK in tests but still visible + '@typescript-eslint/no-non-null-assertion': 'off', // Fine in tests + '@typescript-eslint/no-empty-function': 'off', // Mock functions need this + '@typescript-eslint/explicit-function-return-type': 'off', + 'no-console': 'off', // Debugging in tests is fine + } + } + ] +}; \ No newline at end of file diff --git a/archon-ui-main/__mocks__/lucide-react.tsx b/archon-ui-main/__mocks__/lucide-react.tsx deleted file mode 100644 index a3553fe1..00000000 --- a/archon-ui-main/__mocks__/lucide-react.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import React from 'react' -import { vi } from 'vitest' - -const createMockIcon = (name: string) => { - const MockIcon = React.forwardRef(({ className, ...props }: any, ref: any) => ( - - {name} - - )) - MockIcon.displayName = name - return MockIcon -} - -// Export all icons used in the app -export const Settings = createMockIcon('Settings') -export const Check = createMockIcon('Check') -export const CheckCircle = createMockIcon('CheckCircle') -export const X = createMockIcon('X') -export const XCircle = createMockIcon('XCircle') -export const Eye = createMockIcon('Eye') -export const EyeOff = createMockIcon('EyeOff') -export const Save = createMockIcon('Save') -export const Loader = createMockIcon('Loader') -export const Loader2 = createMockIcon('Loader2') -export const RefreshCw = createMockIcon('RefreshCw') -export const Play = createMockIcon('Play') -export const Pause = createMockIcon('Pause') -export const Square = createMockIcon('Square') -export const FileText = createMockIcon('FileText') -export const Download = createMockIcon('Download') -export const Upload = createMockIcon('Upload') -export const ChevronDown = createMockIcon('ChevronDown') -export const ChevronUp = createMockIcon('ChevronUp') -export const ChevronLeft = createMockIcon('ChevronLeft') -export const ChevronRight = createMockIcon('ChevronRight') -export const Plus = createMockIcon('Plus') -export const Minus = createMockIcon('Minus') -export const Edit = createMockIcon('Edit') -export const Edit2 = createMockIcon('Edit2') -export const Edit3 = createMockIcon('Edit3') -export const Trash = createMockIcon('Trash') -export const Trash2 = createMockIcon('Trash2') -export const User = createMockIcon('User') -export const Users = createMockIcon('Users') -export const Bot = createMockIcon('Bot') -export const Database = createMockIcon('Database') -export const Server = createMockIcon('Server') -export const Globe = createMockIcon('Globe') -export const Search = createMockIcon('Search') -export const Filter = createMockIcon('Filter') -export const Copy = createMockIcon('Copy') -export const ExternalLink = createMockIcon('ExternalLink') -export const Info = createMockIcon('Info') -export const AlertCircle = createMockIcon('AlertCircle') -export const AlertTriangle = createMockIcon('AlertTriangle') -export const Zap = createMockIcon('Zap') -export const Code = createMockIcon('Code') -export const Terminal = createMockIcon('Terminal') -export const Book = createMockIcon('Book') -export const BookOpen = createMockIcon('BookOpen') -export const Folder = createMockIcon('Folder') -export const FolderOpen = createMockIcon('FolderOpen') -export const File = createMockIcon('File') -export const Hash = createMockIcon('Hash') -export const Tag = createMockIcon('Tag') -export const Clock = createMockIcon('Clock') -export const Calendar = createMockIcon('Calendar') -export const MapPin = createMockIcon('MapPin') -export const Link = createMockIcon('Link') -export const Mail = createMockIcon('Mail') -export const Phone = createMockIcon('Phone') -export const Home = createMockIcon('Home') -export const Menu = createMockIcon('Menu') -export const MoreHorizontal = createMockIcon('MoreHorizontal') -export const MoreVertical = createMockIcon('MoreVertical') -export const Refresh = createMockIcon('Refresh') -export const RotateCcw = createMockIcon('RotateCcw') -export const RotateCw = createMockIcon('RotateCw') -export const Sun = createMockIcon('Sun') -export const Moon = createMockIcon('Moon') -export const Monitor = createMockIcon('Monitor') -export const Wifi = createMockIcon('Wifi') -export const WifiOff = createMockIcon('WifiOff') -export const Volume2 = createMockIcon('Volume2') -export const VolumeX = createMockIcon('VolumeX') -export const BarChart = createMockIcon('BarChart') -export const PieChart = createMockIcon('PieChart') -export const TrendingUp = createMockIcon('TrendingUp') -export const TrendingDown = createMockIcon('TrendingDown') -export const ArrowUp = createMockIcon('ArrowUp') -export const ArrowDown = createMockIcon('ArrowDown') -export const ArrowLeft = createMockIcon('ArrowLeft') -export const ArrowRight = createMockIcon('ArrowRight') -export const Send = createMockIcon('Send') -export const MessageSquare = createMockIcon('MessageSquare') -export const MessageCircle = createMockIcon('MessageCircle') -export const Heart = createMockIcon('Heart') -export const Star = createMockIcon('Star') -export const Bookmark = createMockIcon('Bookmark') -export const Share = createMockIcon('Share') -export const Share2 = createMockIcon('Share2') -export const Maximize = createMockIcon('Maximize') -export const Minimize = createMockIcon('Minimize') -export const Expand = createMockIcon('Expand') -export const Shrink = createMockIcon('Shrink') -export const Move = createMockIcon('Move') -export const Shuffle = createMockIcon('Shuffle') -export const Repeat = createMockIcon('Repeat') -export const StopCircle = createMockIcon('StopCircle') -export const SkipBack = createMockIcon('SkipBack') -export const SkipForward = createMockIcon('SkipForward') -export const FastForward = createMockIcon('FastForward') -export const Rewind = createMockIcon('Rewind') -export const Camera = createMockIcon('Camera') -export const Image = createMockIcon('Image') -export const Video = createMockIcon('Video') -export const Mic = createMockIcon('Mic') -export const MicOff = createMockIcon('MicOff') -export const Headphones = createMockIcon('Headphones') -export const Speaker = createMockIcon('Speaker') -export const Bell = createMockIcon('Bell') -export const BellOff = createMockIcon('BellOff') -export const Shield = createMockIcon('Shield') -export const ShieldCheck = createMockIcon('ShieldCheck') -export const ShieldAlert = createMockIcon('ShieldAlert') -export const Key = createMockIcon('Key') -export const Lock = createMockIcon('Lock') -export const Unlock = createMockIcon('Unlock') -export const LogIn = createMockIcon('LogIn') -export const LogOut = createMockIcon('LogOut') -export const UserPlus = createMockIcon('UserPlus') -export const UserMinus = createMockIcon('UserMinus') -export const UserCheck = createMockIcon('UserCheck') -export const UserX = createMockIcon('UserX') -export const Package = createMockIcon('Package') -export const Package2 = createMockIcon('Package2') -export const ShoppingCart = createMockIcon('ShoppingCart') -export const ShoppingBag = createMockIcon('ShoppingBag') -export const CreditCard = createMockIcon('CreditCard') -export const DollarSign = createMockIcon('DollarSign') -export const Percent = createMockIcon('Percent') -export const Activity = createMockIcon('Activity') -export const Cpu = createMockIcon('Cpu') -export const HardDrive = createMockIcon('HardDrive') -export const MemoryStick = createMockIcon('MemoryStick') -export const Smartphone = createMockIcon('Smartphone') -export const Tablet = createMockIcon('Tablet') -export const Laptop = createMockIcon('Laptop') -export const Monitor2 = createMockIcon('Monitor2') -export const Tv = createMockIcon('Tv') -export const Watch = createMockIcon('Watch') -export const Gamepad2 = createMockIcon('Gamepad2') -export const Mouse = createMockIcon('Mouse') -export const Keyboard = createMockIcon('Keyboard') -export const Printer = createMockIcon('Printer') -export const Scanner = createMockIcon('Scanner') -export const Webcam = createMockIcon('Webcam') -export const Bluetooth = createMockIcon('Bluetooth') -export const Usb = createMockIcon('Usb') -export const Zap2 = createMockIcon('Zap2') -export const Battery = createMockIcon('Battery') -export const BatteryCharging = createMockIcon('BatteryCharging') -export const Plug = createMockIcon('Plug') -export const Power = createMockIcon('Power') -export const PowerOff = createMockIcon('PowerOff') -export const BarChart2 = createMockIcon('BarChart2') -export const BarChart3 = createMockIcon('BarChart3') -export const BarChart4 = createMockIcon('BarChart4') -export const LineChart = createMockIcon('LineChart') -export const PieChart2 = createMockIcon('PieChart2') -export const Layers = createMockIcon('Layers') -export const Layers2 = createMockIcon('Layers2') -export const Layers3 = createMockIcon('Layers3') -export const Grid = createMockIcon('Grid') -export const Grid2x2 = createMockIcon('Grid2x2') -export const Grid3x3 = createMockIcon('Grid3x3') -export const List = createMockIcon('List') -export const ListChecks = createMockIcon('ListChecks') -export const ListTodo = createMockIcon('ListTodo') -export const CheckSquare = createMockIcon('CheckSquare') -export const Square2 = createMockIcon('Square2') -export const Circle = createMockIcon('Circle') -export const CircleCheck = createMockIcon('CircleCheck') -export const CircleX = createMockIcon('CircleX') -export const CircleDot = createMockIcon('CircleDot') -export const Target = createMockIcon('Target') -export const Focus = createMockIcon('Focus') -export const Crosshair = createMockIcon('Crosshair') -export const Locate = createMockIcon('Locate') -export const LocateFixed = createMockIcon('LocateFixed') -export const Navigation = createMockIcon('Navigation') -export const Navigation2 = createMockIcon('Navigation2') -export const Compass = createMockIcon('Compass') -export const Map = createMockIcon('Map') -export const TestTube = createMockIcon('TestTube') -export const FlaskConical = createMockIcon('FlaskConical') -export const Bug = createMockIcon('Bug') -export const GitBranch = createMockIcon('GitBranch') -export const GitCommit = createMockIcon('GitCommit') -export const GitMerge = createMockIcon('GitMerge') -export const GitPullRequest = createMockIcon('GitPullRequest') -export const Github = createMockIcon('Github') -export const Gitlab = createMockIcon('Gitlab') -export const Bitbucket = createMockIcon('Bitbucket') -export const Network = createMockIcon('Network') -export const GitGraph = createMockIcon('GitGraph') -export const ListFilter = createMockIcon('ListFilter') -export const CheckSquare2 = createMockIcon('CheckSquare2') -export const CircleSlash2 = createMockIcon('CircleSlash2') -export const Clock3 = createMockIcon('Clock3') -export const GitCommitHorizontal = createMockIcon('GitCommitHorizontal') -export const CalendarDays = createMockIcon('CalendarDays') -export const Sparkles = createMockIcon('Sparkles') -export const Layout = createMockIcon('Layout') -export const Table = createMockIcon('Table') -export const Columns = createMockIcon('Columns') -export const GitPullRequestDraft = createMockIcon('GitPullRequestDraft') -export const BrainCircuit = createMockIcon('BrainCircuit') -export const Wrench = createMockIcon('Wrench') -export const PlugZap = createMockIcon('PlugZap') -export const BoxIcon = createMockIcon('BoxIcon') -export const Box = createMockIcon('Box') -export const Brain = createMockIcon('Brain') -export const LinkIcon = createMockIcon('LinkIcon') -export const Sparkle = createMockIcon('Sparkle') -export const FolderTree = createMockIcon('FolderTree') -export const Lightbulb = createMockIcon('Lightbulb') -export const Rocket = createMockIcon('Rocket') -export const Building = createMockIcon('Building') -export const FileCode = createMockIcon('FileCode') -export const FileJson = createMockIcon('FileJson') -export const Braces = createMockIcon('Braces') -export const Binary = createMockIcon('Binary') -export const Palette = createMockIcon('Palette') -export const Paintbrush = createMockIcon('Paintbrush') -export const Type = createMockIcon('Type') -export const Heading = createMockIcon('Heading') -export const AlignLeft = createMockIcon('AlignLeft') -export const AlignCenter = createMockIcon('AlignCenter') -export const AlignRight = createMockIcon('AlignRight') -export const Bold = createMockIcon('Bold') -export const Italic = createMockIcon('Italic') -export const Underline = createMockIcon('Underline') -export const Strikethrough = createMockIcon('Strikethrough') -export const FileCheck = createMockIcon('FileCheck') -export const FileX = createMockIcon('FileX') -export const FilePlus = createMockIcon('FilePlus') -export const FileMinus = createMockIcon('FileMinus') -export const FolderPlus = createMockIcon('FolderPlus') -export const FolderMinus = createMockIcon('FolderMinus') -export const FolderCheck = createMockIcon('FolderCheck') -export const FolderX = createMockIcon('FolderX') -export const startMCPServer = createMockIcon('startMCPServer') -export const Pin = createMockIcon('Pin') -export const CheckCircle2 = createMockIcon('CheckCircle2') -export const Clipboard = createMockIcon('Clipboard') -export const LayoutGrid = createMockIcon('LayoutGrid') -export const Pencil = createMockIcon('Pencil') -export const MousePointer = createMockIcon('MousePointer') -export const GripVertical = createMockIcon('GripVertical') -export const History = createMockIcon('History') -export const PlusCircle = createMockIcon('PlusCircle') -export const MinusCircle = createMockIcon('MinusCircle') -export const ChevronDownIcon = createMockIcon('ChevronDownIcon') -export const FileIcon = createMockIcon('FileIcon') -export const AlertCircleIcon = createMockIcon('AlertCircleIcon') -export const Clock4 = createMockIcon('Clock4') -export const XIcon = createMockIcon('XIcon') -export const CheckIcon = createMockIcon('CheckIcon') -export const TrashIcon = createMockIcon('TrashIcon') -export const EyeIcon = createMockIcon('EyeIcon') -export const EditIcon = createMockIcon('EditIcon') -export const DownloadIcon = createMockIcon('DownloadIcon') -export const RefreshIcon = createMockIcon('RefreshIcon') -export const SearchIcon = createMockIcon('SearchIcon') -export const FilterIcon = createMockIcon('FilterIcon') -export const PlusIcon = createMockIcon('PlusIcon') -export const FolderIcon = createMockIcon('FolderIcon') -export const FileTextIcon = createMockIcon('FileTextIcon') -export const BookOpenIcon = createMockIcon('BookOpenIcon') -export const DatabaseIcon = createMockIcon('DatabaseIcon') -export const GlobeIcon = createMockIcon('GlobeIcon') -export const TagIcon = createMockIcon('TagIcon') -export const CalendarIcon = createMockIcon('CalendarIcon') -export const ClockIcon = createMockIcon('ClockIcon') -export const UserIcon = createMockIcon('UserIcon') -export const SettingsIcon = createMockIcon('SettingsIcon') -export const InfoIcon = createMockIcon('InfoIcon') -export const WarningIcon = createMockIcon('WarningIcon') -export const ErrorIcon = createMockIcon('ErrorIcon') \ No newline at end of file diff --git a/archon-ui-main/biome.json b/archon-ui-main/biome.json new file mode 100644 index 00000000..2461476d --- /dev/null +++ b/archon-ui-main/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json", + "files": { + "includes": ["src/features/**", "src/components/layout/**"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 120, + "bracketSpacing": true, + "attributePosition": "auto" + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSameLine": false + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": { + "level": "on" + } + } + } + } +} diff --git a/archon-ui-main/docs/socket-memoization-patterns.md b/archon-ui-main/docs/socket-memoization-patterns.md deleted file mode 100644 index 4edaae0d..00000000 --- a/archon-ui-main/docs/socket-memoization-patterns.md +++ /dev/null @@ -1,255 +0,0 @@ -# Socket & Memoization Patterns - -## Quick Reference - -### DO: -- ✅ Track optimistic updates to prevent double-renders -- ✅ Memoize socket event handlers with useCallback -- ✅ Check if incoming data actually differs from current state -- ✅ Use debouncing for rapid UI updates (drag & drop) -- ✅ Clean up socket listeners in useEffect cleanup - -### DON'T: -- ❌ Update state without checking if data changed -- ❌ Create new handler functions on every render -- ❌ Apply server updates that match pending optimistic updates -- ❌ Forget to handle the "modal open" edge case - -## Pattern Examples - -### Optimistic Update Pattern - -```typescript -import { useOptimisticUpdates } from '../../hooks/useOptimisticUpdates'; - -const MyComponent = () => { - const { addPendingUpdate, isPendingUpdate } = useOptimisticUpdates(); - - const handleLocalUpdate = (task: Task) => { - // Track the optimistic update - addPendingUpdate({ - id: task.id, - timestamp: Date.now(), - data: task, - operation: 'update' - }); - - // Update local state immediately - setTasks(prev => prev.map(t => t.id === task.id ? task : t)); - - // Persist to server - api.updateTask(task); - }; - - const handleServerUpdate = useCallback((task: Task) => { - // Skip if this is our own update echoing back - if (isPendingUpdate(task.id, task)) { - console.log('Skipping own optimistic update'); - return; - } - - // Apply server update - setTasks(prev => prev.map(t => t.id === task.id ? task : t)); - }, [isPendingUpdate]); -}; -``` - -### Socket Handler Pattern - -```typescript -import { useSocketSubscription } from '../../hooks/useSocketSubscription'; - -const MyComponent = () => { - // Option 1: Using the hook - useSocketSubscription( - socketService, - 'data_updated', - (data) => { - console.log('Data updated:', data); - // Handle update - }, - [/* dependencies */] - ); - - // Option 2: Manual memoization - const handleUpdate = useCallback((message: any) => { - const data = message.data || message; - - setItems(prev => { - // Check if data actually changed - const existing = prev.find(item => item.id === data.id); - if (existing && JSON.stringify(existing) === JSON.stringify(data)) { - return prev; // No change, prevent re-render - } - - return prev.map(item => item.id === data.id ? data : item); - }); - }, []); - - useEffect(() => { - socketService.addMessageHandler('update', handleUpdate); - return () => { - socketService.removeMessageHandler('update', handleUpdate); - }; - }, [handleUpdate]); -}; -``` - -### Debounced Reordering Pattern - -```typescript -const useReordering = () => { - const debouncedPersist = useMemo( - () => debounce(async (items: Item[]) => { - try { - await api.updateOrder(items); - } catch (error) { - console.error('Failed to persist order:', error); - // Rollback or retry logic - } - }, 500), - [] - ); - - const handleReorder = useCallback((dragIndex: number, dropIndex: number) => { - // Update UI immediately - setItems(prev => { - const newItems = [...prev]; - const [draggedItem] = newItems.splice(dragIndex, 1); - newItems.splice(dropIndex, 0, draggedItem); - - // Update order numbers - return newItems.map((item, index) => ({ - ...item, - order: index + 1 - })); - }); - - // Persist changes (debounced) - debouncedPersist(items); - }, [items, debouncedPersist]); -}; -``` - -## WebSocket Service Configuration - -### Deduplication - -The enhanced WebSocketService now includes automatic deduplication: - -```typescript -// Configure deduplication window (default: 100ms) -socketService.setDeduplicationWindow(200); // 200ms window - -// Duplicate messages within the window are automatically filtered -``` - -### Connection Management - -```typescript -// Always check connection state before critical operations -if (socketService.isConnected()) { - socketService.send({ type: 'update', data: payload }); -} - -// Monitor connection state -socketService.addStateChangeHandler((state) => { - if (state === WebSocketState.CONNECTED) { - console.log('Connected - refresh data'); - } -}); -``` - -## Common Patterns - -### 1. State Equality Checks - -Always check if incoming data actually differs from current state: - -```typescript -// ❌ BAD - Always triggers re-render -setTasks(prev => prev.map(t => t.id === id ? newTask : t)); - -// ✅ GOOD - Only updates if changed -setTasks(prev => { - const existing = prev.find(t => t.id === id); - if (existing && deepEqual(existing, newTask)) return prev; - return prev.map(t => t.id === id ? newTask : t); -}); -``` - -### 2. Modal State Handling - -Be aware of modal state when applying updates: - -```typescript -const handleSocketUpdate = useCallback((data) => { - if (isModalOpen && editingItem?.id === data.id) { - console.warn('Update received while editing - consider skipping or merging'); - // Option 1: Skip the update - // Option 2: Merge with current edits - // Option 3: Show conflict resolution UI - } - - // Normal update flow -}, [isModalOpen, editingItem]); -``` - -### 3. Cleanup Pattern - -Always clean up socket listeners: - -```typescript -useEffect(() => { - const handlers = [ - { event: 'create', handler: handleCreate }, - { event: 'update', handler: handleUpdate }, - { event: 'delete', handler: handleDelete } - ]; - - // Add all handlers - handlers.forEach(({ event, handler }) => { - socket.addMessageHandler(event, handler); - }); - - // Cleanup - return () => { - handlers.forEach(({ event, handler }) => { - socket.removeMessageHandler(event, handler); - }); - }; -}, [handleCreate, handleUpdate, handleDelete]); -``` - -## Performance Tips - -1. **Measure First**: Use React DevTools Profiler before optimizing -2. **Batch Updates**: Group related state changes -3. **Debounce Rapid Changes**: Especially for drag & drop operations -4. **Use Stable References**: Memoize callbacks passed to child components -5. **Avoid Deep Equality Checks**: Use optimized comparison for large objects - -## Debugging - -Enable verbose logging for troubleshooting: - -```typescript -// In development -if (process.env.NODE_ENV === 'development') { - console.log('[Socket] Message received:', message); - console.log('[Socket] Deduplication result:', isDuplicate); - console.log('[Optimistic] Pending updates:', pendingUpdates); -} -``` - -## Migration Guide - -To migrate existing components: - -1. Import `useOptimisticUpdates` hook -2. Wrap socket handlers with `useCallback` -3. Add optimistic update tracking to local changes -4. Check for pending updates in socket handlers -5. Test with React DevTools Profiler - -Remember: The goal is to eliminate unnecessary re-renders while maintaining real-time synchronization across all connected clients. \ No newline at end of file diff --git a/archon-ui-main/package-lock.json b/archon-ui-main/package-lock.json index 831b1a92..7d367133 100644 --- a/archon-ui-main/package-lock.json +++ b/archon-ui-main/package-lock.json @@ -8,11 +8,17 @@ "name": "archon-ui", "version": "0.1.0", "dependencies": { - "@milkdown/crepe": "^7.5.0", - "@milkdown/kit": "^7.5.0", - "@milkdown/plugin-history": "^7.5.0", - "@milkdown/preset-commonmark": "^7.5.0", - "@xyflow/react": "^12.3.0", + "@mdxeditor/editor": "^3.42.0", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toast": "^1.2.15", + "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-query": "^5.85.8", + "@tanstack/react-query-devtools": "^5.85.8", "clsx": "latest", "date-fns": "^4.1.0", "fractional-indexing": "^3.2.0", @@ -23,25 +29,26 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.3.1", + "react-markdown": "^10.1.0", "react-router-dom": "^6.26.2", - "socket.io-client": "^4.8.1", "tailwind-merge": "latest", "zod": "^3.25.46" }, "devDependencies": { + "@biomejs/biome": "2.2.2", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^14.3.1", "@testing-library/user-event": "^14.5.2", "@types/node": "^20.19.0", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.1", - "@typescript-eslint/eslint-plugin": "^5.54.0", - "@typescript-eslint/parser": "^5.54.0", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", "@vitejs/plugin-react": "^4.2.1", "@vitest/coverage-v8": "^1.6.0", "@vitest/ui": "^1.6.0", "autoprefixer": "latest", - "eslint": "^8.50.0", + "eslint": "^8.57.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.1", "jsdom": "^24.1.0", @@ -264,6 +271,7 @@ "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" @@ -273,6 +281,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -306,6 +315,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.0" @@ -396,6 +406,7 @@ "version": "7.28.1", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -412,6 +423,169 @@ "dev": true, "license": "MIT" }, + "node_modules/@biomejs/biome": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.2.tgz", + "integrity": "sha512-j1omAiQWCkhuLgwpMKisNKnsM6W8Xtt1l0WZmqY/dFj8QPNkIoTvk4tSsi40FaAAkBE1PU0AFG2RWFBWenAn+w==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.2.2", + "@biomejs/cli-darwin-x64": "2.2.2", + "@biomejs/cli-linux-arm64": "2.2.2", + "@biomejs/cli-linux-arm64-musl": "2.2.2", + "@biomejs/cli-linux-x64": "2.2.2", + "@biomejs/cli-linux-x64-musl": "2.2.2", + "@biomejs/cli-win32-arm64": "2.2.2", + "@biomejs/cli-win32-x64": "2.2.2" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.2.tgz", + "integrity": "sha512-6ePfbCeCPryWu0CXlzsWNZgVz/kBEvHiPyNpmViSt6A2eoDf4kXs3YnwQPzGjy8oBgQulrHcLnJL0nkCh80mlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.2.tgz", + "integrity": "sha512-Tn4JmVO+rXsbRslml7FvKaNrlgUeJot++FkvYIhl1OkslVCofAtS35MPlBMhXgKWF9RNr9cwHanrPTUUXcYGag==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.2.tgz", + "integrity": "sha512-JfrK3gdmWWTh2J5tq/rcWCOsImVyzUnOS2fkjhiYKCQ+v8PqM+du5cfB7G1kXas+7KQeKSWALv18iQqdtIMvzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.2.tgz", + "integrity": "sha512-/MhYg+Bd6renn6i1ylGFL5snYUn/Ct7zoGVKhxnro3bwekiZYE8Kl39BSb0MeuqM+72sThkQv4TnNubU9njQRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.2.tgz", + "integrity": "sha512-Ogb+77edO5LEP/xbNicACOWVLt8mgC+E1wmpUakr+O4nKwLt9vXe74YNuT3T1dUBxC/SnrVmlzZFC7kQJEfquQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.2.tgz", + "integrity": "sha512-ZCLXcZvjZKSiRY/cFANKg+z6Fhsf9MHOzj+NrDQcM+LbqYRT97LyCLWy2AS+W2vP+i89RyRM+kbGpUzbRTYWig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.2.tgz", + "integrity": "sha512-wBe2wItayw1zvtXysmHJQoQqXlTzHSpQRyPpJKiNIR21HzH/CrZRDFic1C1jDdp+zAPtqhNExa0owKMbNwW9cQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.2.tgz", + "integrity": "sha512-DAuHhHekGfiGb6lCcsT4UyxQmVwQiBCBUMwVra/dcOSs9q8OhfaZgey51MlekT3p8UwRqtXQfFuEJBhJNdLZwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@codemirror/autocomplete": { "version": "6.18.6", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", @@ -764,6 +938,19 @@ "crelt": "^1.0.5" } }, + "node_modules/@codemirror/merge": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/merge/-/merge-6.10.2.tgz", + "integrity": "sha512-rmHzVkt5FnCtsi0IgvDIDjh/J4LmbfOboB7FMvVl21IHO0p1QM6jSwjkBjBD3D+c+T79OabEqoduCqvJCBV8Yg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/highlight": "^1.0.0", + "style-mod": "^4.1.0" + } + }, "node_modules/@codemirror/search": { "version": "6.5.11", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", @@ -784,18 +971,6 @@ "@marijn/find-cluster-break": "^1.0.0" } }, - "node_modules/@codemirror/theme-one-dark": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", - "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/highlight": "^1.0.0" - } - }, "node_modules/@codemirror/view": { "version": "6.38.1", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz", @@ -808,6 +983,61 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@codesandbox/nodebox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz", + "integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==", + "license": "SEE LICENSE IN ./LICENSE", + "dependencies": { + "outvariant": "^1.4.0", + "strict-event-emitter": "^0.4.3" + } + }, + "node_modules/@codesandbox/sandpack-client": { + "version": "2.19.8", + "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz", + "integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==", + "license": "Apache-2.0", + "dependencies": { + "@codesandbox/nodebox": "0.1.8", + "buffer": "^6.0.3", + "dequal": "^2.0.2", + "mime-db": "^1.52.0", + "outvariant": "1.4.0", + "static-browser-server": "1.0.3" + } + }, + "node_modules/@codesandbox/sandpack-react": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-react/-/sandpack-react-2.20.0.tgz", + "integrity": "sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ==", + "license": "Apache-2.0", + "dependencies": { + "@codemirror/autocomplete": "^6.4.0", + "@codemirror/commands": "^6.1.3", + "@codemirror/lang-css": "^6.0.1", + "@codemirror/lang-html": "^6.4.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.3.2", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.7.1", + "@codesandbox/sandpack-client": "^2.19.8", + "@lezer/highlight": "^1.1.3", + "@react-hook/intersection-observer": "^3.1.1", + "@stitches/core": "^1.2.6", + "anser": "^2.1.1", + "clean-set": "^1.1.2", + "dequal": "^2.0.2", + "escape-carriage": "^1.3.1", + "lz-string": "^1.4.4", + "react-devtools-inline": "4.4.0", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19", + "react-dom": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1418,28 +1648,56 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz", - "integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.9" + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz", - "integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.1", - "@floating-ui/utils": "^0.2.9" + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.16", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.16.tgz", + "integrity": "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.6", + "@floating-ui/utils": "^0.2.10", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, "node_modules/@floating-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, "node_modules/@humanwhocodes/config-array": { @@ -1589,6 +1847,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -1602,6 +1861,261 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lexical/clipboard": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.33.1.tgz", + "integrity": "sha512-Qd3/Cm3TW2DFQv58kMtLi86u5YOgpBdf+o7ySbXz55C613SLACsYQBB3X5Vu5hTx/t/ugYOpII4HkiatW6d9zA==", + "license": "MIT", + "dependencies": { + "@lexical/html": "0.33.1", + "@lexical/list": "0.33.1", + "@lexical/selection": "0.33.1", + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/code": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/code/-/code-0.33.1.tgz", + "integrity": "sha512-E0Y/+1znkqVpP52Y6blXGAduoZek9SSehJN+vbH+4iQKyFwTA7JB+jd5C5/K0ik55du9X7SN/oTynByg7lbcAA==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.33.1", + "lexical": "0.33.1", + "prismjs": "^1.30.0" + } + }, + "node_modules/@lexical/devtools-core": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.33.1.tgz", + "integrity": "sha512-3yHu5diNtjwhoe2q/x9as6n6rIfA+QO2CfaVjFRkam8rkAW6zUzQT1D0fQdE8nOfWvXBgY1mH/ZLP4dDXBdG5Q==", + "license": "MIT", + "dependencies": { + "@lexical/html": "0.33.1", + "@lexical/link": "0.33.1", + "@lexical/mark": "0.33.1", + "@lexical/table": "0.33.1", + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" + } + }, + "node_modules/@lexical/dragon": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.33.1.tgz", + "integrity": "sha512-UQ6DLkcDAr83wA1vz3sUgtcpYcMifC4sF0MieZAoMzFrna6Ekqj7OJ7g8Lo7m7AeuT4NETRVDsjIEDdrQMKLLA==", + "license": "MIT", + "dependencies": { + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/hashtag": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.33.1.tgz", + "integrity": "sha512-M3IsDe4cifggMBZgYAVT7hCLWcwQ3dIcUPdr9Xc6wDQQQdEqOQYB0PO//9bSYUVq+BNiiTgysc+TtlM7PiJfiw==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/history": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.33.1.tgz", + "integrity": "sha512-Bk0h3D6cFkJ7w3HKvqQua7n6Xfz7nR7L3gLDBH9L0nsS4MM9+LteSEZPUe0kj4VuEjnxufYstTc9HA2aNLKxnQ==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/html": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.33.1.tgz", + "integrity": "sha512-t14vu4eKa6BWz1N7/rwXgXif1k4dj73dRvllWJgfXum+a36vn1aySNYOlOfqWXF7k1b3uJmoqsWK7n/1ASnimw==", + "license": "MIT", + "dependencies": { + "@lexical/selection": "0.33.1", + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/link": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.33.1.tgz", + "integrity": "sha512-JCTu7Fft2J2kgfqJiWnGei+UMIXVKiZKaXzuHCuGQTFu92DeCyd02azBaFazZHEkSqCIFZ0DqVV2SpIJmd0Ygw==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/list": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.33.1.tgz", + "integrity": "sha512-PXp56dWADSThc9WhwWV4vXhUc3sdtCqsfPD3UQNGUZ9rsAY1479rqYLtfYgEmYPc8JWXikQCAKEejahCJIm8OQ==", + "license": "MIT", + "dependencies": { + "@lexical/selection": "0.33.1", + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/mark": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/mark/-/mark-0.33.1.tgz", + "integrity": "sha512-tGdOf1e694lnm/HyWUKEkEWjDyfhCBFG7u8iRKNpsYTpB3M1FsJUXbphE2bb8MyWfhHbaNxnklupSSaSPzO88A==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/markdown": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.33.1.tgz", + "integrity": "sha512-p5zwWNF70pELRx60wxE8YOFVNiNDkw7gjKoYqkED23q5hj4mcqco9fQf6qeeZChjxLKjfyT6F1PpWgxmlBlxBw==", + "license": "MIT", + "dependencies": { + "@lexical/code": "0.33.1", + "@lexical/link": "0.33.1", + "@lexical/list": "0.33.1", + "@lexical/rich-text": "0.33.1", + "@lexical/text": "0.33.1", + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/offset": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/offset/-/offset-0.33.1.tgz", + "integrity": "sha512-3YIlUs43QdKSBLEfOkuciE2tn9loxVmkSs/HgaIiLYl0Edf1W00FP4ItSmYU4De5GopXsHq6+Y3ry4pU/ciUiQ==", + "license": "MIT", + "dependencies": { + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/overflow": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.33.1.tgz", + "integrity": "sha512-3BDq1lOw567FeCk4rN2ellKwoXTM9zGkGuKnSGlXS1JmtGGGSvT+uTANX3KOOfqTNSrOkrwoM+3hlFv7p6VpiQ==", + "license": "MIT", + "dependencies": { + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/plain-text": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.33.1.tgz", + "integrity": "sha512-2HxdhAx6bwF8y5A9P0q3YHsYbhUo4XXm+GyKJO87an8JClL2W+GYLTSDbfNWTh4TtH95eG+UYLOjNEgyU6tsWA==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.33.1", + "@lexical/selection": "0.33.1", + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/react": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/react/-/react-0.33.1.tgz", + "integrity": "sha512-ylnUmom5h8PY+Z14uDmKLQEoikTPN77GRM0NRCIdtbWmOQqOq/5BhuCzMZE1WvpL5C6n3GtK6IFnsMcsKmVOcw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.8", + "@lexical/devtools-core": "0.33.1", + "@lexical/dragon": "0.33.1", + "@lexical/hashtag": "0.33.1", + "@lexical/history": "0.33.1", + "@lexical/link": "0.33.1", + "@lexical/list": "0.33.1", + "@lexical/mark": "0.33.1", + "@lexical/markdown": "0.33.1", + "@lexical/overflow": "0.33.1", + "@lexical/plain-text": "0.33.1", + "@lexical/rich-text": "0.33.1", + "@lexical/table": "0.33.1", + "@lexical/text": "0.33.1", + "@lexical/utils": "0.33.1", + "@lexical/yjs": "0.33.1", + "lexical": "0.33.1", + "react-error-boundary": "^3.1.4" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" + } + }, + "node_modules/@lexical/rich-text": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.33.1.tgz", + "integrity": "sha512-ZBIsj4LwmamRBCGjJiPSLj7N/XkUDv/pnYn5Rp0BL42WpOiQLvOoGLrZxgUJZEmRPQnx42ZgLKVgrWHsyjuoAA==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.33.1", + "@lexical/selection": "0.33.1", + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/selection": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.33.1.tgz", + "integrity": "sha512-KXPkdCDdVfIUXmkwePu9DAd3kLjL0aAqL5G9CMCFsj7RG9lLvvKk7kpivrAIbRbcsDzO44QwsFPisZHbX4ioXA==", + "license": "MIT", + "dependencies": { + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/table": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.33.1.tgz", + "integrity": "sha512-pzB11i1Y6fzmy0IPUKJyCdhVBgXaNOxJUxrQJWdKNYCh1eMwwMEQvj+8inItd/11aUkjcdHjwDTht8gL2UHKiQ==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.33.1", + "@lexical/utils": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/text": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/text/-/text-0.33.1.tgz", + "integrity": "sha512-CnyU3q3RytXXWVSvC5StOKISzFAPGK9MuesNDDGyZk7yDK+J98gV6df4RBKfqwcokFMThpkUlvMeKe1+S2y25A==", + "license": "MIT", + "dependencies": { + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/utils": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.33.1.tgz", + "integrity": "sha512-eKysPjzEE9zD+2af3WRX5U3XbeNk0z4uv1nXGH3RG15uJ4Huzjht82hzsQpCFUobKmzYlQaQs5y2IYKE2puipQ==", + "license": "MIT", + "dependencies": { + "@lexical/list": "0.33.1", + "@lexical/selection": "0.33.1", + "@lexical/table": "0.33.1", + "lexical": "0.33.1" + } + }, + "node_modules/@lexical/yjs": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.33.1.tgz", + "integrity": "sha512-Zx1rabMm/Zjk7n7YQMIQLUN+tqzcg1xqcgNpEHSfK1GA8QMPXCPvXWFT3ZDC4tfZOSy/YIqpVUyWZAomFqRa+g==", + "license": "MIT", + "dependencies": { + "@lexical/offset": "0.33.1", + "@lexical/selection": "0.33.1", + "lexical": "0.33.1" + }, + "peerDependencies": { + "yjs": ">=13.5.22" + } + }, "node_modules/@lezer/common": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", @@ -1785,414 +2299,88 @@ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", "license": "MIT" }, - "node_modules/@milkdown/components": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/components/-/components-7.15.1.tgz", - "integrity": "sha512-yGfjSi7VaRtiyoJA/KGVQtXpig2GYFe7uFAC6kRwskJZF/LQH/+hjjTgpPIcGn+AhKghtJ049ZXnJkgFU45YYA==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.5.1", - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/exception": "7.15.1", - "@milkdown/plugin-tooltip": "7.15.1", - "@milkdown/preset-commonmark": "7.15.1", - "@milkdown/preset-gfm": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/transformer": "7.15.1", - "@milkdown/utils": "7.15.1", - "@types/lodash.debounce": "^4.0.7", - "@types/lodash.throttle": "^4.1.9", - "clsx": "^2.0.0", - "dompurify": "^3.2.5", - "lodash.debounce": "^4.0.8", - "lodash.throttle": "^4.1.1", - "nanoid": "^5.0.9", - "tslib": "^2.8.1", - "unist-util-visit": "^5.0.0", - "vue": "^3.5.13" - }, - "peerDependencies": { - "@codemirror/language": "^6", - "@codemirror/state": "^6", - "@codemirror/view": "^6" - } - }, - "node_modules/@milkdown/components/node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/@milkdown/core": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/core/-/core-7.15.1.tgz", - "integrity": "sha512-jcuKZnZ9rrffwpAFq+0pMIwfxnchZOCFSIQT7NQnsOhzFXnCNXu69cxzlcK3CZExDgkmivHM62xFsjN9l7vTdg==", - "license": "MIT", - "dependencies": { - "@milkdown/ctx": "7.15.1", - "@milkdown/exception": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/transformer": "7.15.1", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "tslib": "^2.8.1", - "unified": "^11.0.3" - } - }, - "node_modules/@milkdown/crepe": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/crepe/-/crepe-7.15.1.tgz", - "integrity": "sha512-2n63s4vBzTO0IBTO/nIsS/XKyPIpOXjiIwXgkIXkbzNt/sPA6hEwtvEJDCz7uzRo/MCxZu0jLjPW/OpVMakLNg==", + "node_modules/@mdxeditor/editor": { + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/@mdxeditor/editor/-/editor-3.42.0.tgz", + "integrity": "sha512-nQN07RkTm842T477IjPqp1FhWCQMpmbLToOVrc6EjSI60aHifwzva+eqYmElHFKE2jyGiD5FsaQXri1SSORJNg==", "license": "MIT", "dependencies": { "@codemirror/commands": "^6.2.4", - "@codemirror/language": "^6.10.1", - "@codemirror/language-data": "^6.3.1", - "@codemirror/state": "^6.4.1", - "@codemirror/theme-one-dark": "^6.1.2", - "@codemirror/view": "^6.16.0", - "@floating-ui/dom": "^1.5.1", - "@milkdown/kit": "7.15.1", - "@types/lodash-es": "^4.17.12", - "clsx": "^2.0.0", + "@codemirror/lang-markdown": "^6.2.3", + "@codemirror/language-data": "^6.5.1", + "@codemirror/merge": "^6.4.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.23.0", + "@codesandbox/sandpack-react": "^2.20.0", + "@lexical/clipboard": "^0.33.1", + "@lexical/link": "^0.33.1", + "@lexical/list": "^0.33.1", + "@lexical/markdown": "^0.33.1", + "@lexical/plain-text": "^0.33.1", + "@lexical/react": "^0.33.1", + "@lexical/rich-text": "^0.33.1", + "@lexical/selection": "^0.33.1", + "@lexical/utils": "^0.33.1", + "@mdxeditor/gurx": "^1.1.4", + "@radix-ui/colors": "^3.0.0", + "@radix-ui/react-dialog": "^1.1.11", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-popover": "^1.1.11", + "@radix-ui/react-popper": "^1.2.4", + "@radix-ui/react-select": "^2.2.2", + "@radix-ui/react-toggle-group": "^1.1.7", + "@radix-ui/react-toolbar": "^1.1.7", + "@radix-ui/react-tooltip": "^1.2.4", + "classnames": "^2.3.2", + "cm6-theme-basic-light": "^0.2.0", "codemirror": "^6.0.1", - "katex": "^0.16.0", - "lodash-es": "^4.17.21", - "nanoid": "^5.0.9", - "prosemirror-virtual-cursor": "^0.4.2", - "remark-math": "^6.0.0", - "tslib": "^2.8.1", - "unist-util-visit": "^5.0.0", - "vue": "^3.5.13" - } - }, - "node_modules/@milkdown/crepe/node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" + "downshift": "^7.6.0", + "js-yaml": "4.1.0", + "lexical": "^0.33.1", + "mdast-util-directive": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-frontmatter": "^2.0.1", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-highlight-mark": "^1.2.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "micromark-extension-directive": "^3.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.1", + "micromark-extension-highlight-mark": "^1.2.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs": "^3.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.1", + "micromark-util-symbol": "^2.0.0", + "react-hook-form": "^7.56.1", + "unidiff": "^1.0.2" }, "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/@milkdown/ctx": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/ctx/-/ctx-7.15.1.tgz", - "integrity": "sha512-MO2EymuAmcT9TVcbZVr0TriFMAPP1d1p/cWVbyqZXKsxK1sRzNxJCpdPm20LD2e2qJt6pRziIf/ugGww1Tvf7A==", - "license": "MIT", - "dependencies": { - "@milkdown/exception": "7.15.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/exception": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/exception/-/exception-7.15.1.tgz", - "integrity": "sha512-QMpT/8SYM1CIuptHrOKzaelZd4ZU1j9mz3m2EwF4Ql0PNOXoWW50/P7gtr71foyTu3fPyXA9f8/GaTkihD/b/Q==", - "license": "MIT", - "dependencies": { - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/kit": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/kit/-/kit-7.15.1.tgz", - "integrity": "sha512-wmUt9mN+rfJgCz11c3z2E8ExIKhd2QLdFPCPb8OHamebsf+td5nS0HX2vpvkaumgD4AQA0KCbMs9WmqChG/K7w==", - "license": "MIT", - "dependencies": { - "@milkdown/components": "7.15.1", - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/plugin-block": "7.15.1", - "@milkdown/plugin-clipboard": "7.15.1", - "@milkdown/plugin-cursor": "7.15.1", - "@milkdown/plugin-history": "7.15.1", - "@milkdown/plugin-indent": "7.15.1", - "@milkdown/plugin-listener": "7.15.1", - "@milkdown/plugin-slash": "7.15.1", - "@milkdown/plugin-tooltip": "7.15.1", - "@milkdown/plugin-trailing": "7.15.1", - "@milkdown/plugin-upload": "7.15.1", - "@milkdown/preset-commonmark": "7.15.1", - "@milkdown/preset-gfm": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/transformer": "7.15.1", - "@milkdown/utils": "7.15.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/plugin-block": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/plugin-block/-/plugin-block-7.15.1.tgz", - "integrity": "sha512-ltftyP6brSs5N3q9mJhcauqfuDuGIGm2dsXwpibsRsO8WbCptVpQPjHCNGJp+/Y+bLrRas5DAz+cqFvfDvCDYA==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.5.1", - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/exception": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/utils": "7.15.1", - "@types/lodash.throttle": "^4.1.9", - "lodash.throttle": "^4.1.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/plugin-clipboard": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/plugin-clipboard/-/plugin-clipboard-7.15.1.tgz", - "integrity": "sha512-mtq+CQhuCR/bVKHep588OsrIHxQAve85VHIPHPaU768c6jkQhGlr82a0bp90hhzTMREWDqxsFrJAxPM+PDtugA==", - "license": "MIT", - "dependencies": { - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/utils": "7.15.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/plugin-cursor": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/plugin-cursor/-/plugin-cursor-7.15.1.tgz", - "integrity": "sha512-HZloO+PpoXvdf854aflIA1pq5cmoRHNvaiC3QCeywAz6y0EHFr0NSJRQQZwXIefdbi5l/CP/lkc9dJJotzgEng==", - "license": "MIT", - "dependencies": { - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/utils": "7.15.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/plugin-history": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/plugin-history/-/plugin-history-7.15.1.tgz", - "integrity": "sha512-2LkYbZYuix7LUI/sR1NQO5oZOjcT9E6wJhDHcMmeO8XoIO6r0q8STdH7jvITkB/Rr9wNRXfI+V86hvsfB0aMbw==", - "license": "MIT", - "dependencies": { - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/utils": "7.15.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/plugin-indent": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/plugin-indent/-/plugin-indent-7.15.1.tgz", - "integrity": "sha512-D3asSTw6Jvyn3TRVOGNNwhslL0OgnU0Fi9G1JOt9nsaqDIuTMQQhapJzr3VCZn1ko9hdlYUKBQnPkAXngNZKjg==", - "license": "MIT", - "dependencies": { - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/utils": "7.15.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/plugin-listener": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/plugin-listener/-/plugin-listener-7.15.1.tgz", - "integrity": "sha512-lnpFzAmhJK0+No0R4utWNx31cDunBqkdBGMBbV6571SHgfVIHw/T8z64t8Fo7xNt9OjcgKee877tk6TaL98HiQ==", - "license": "MIT", - "dependencies": { - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/utils": "7.15.1", - "@types/lodash.debounce": "^4.0.7", - "lodash.debounce": "^4.0.8", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/plugin-slash": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/plugin-slash/-/plugin-slash-7.15.1.tgz", - "integrity": "sha512-b/wvpr7+hRgNsftu3XbUMHEKOKUSdNN+HXMhTqTIheB/m/Y7zSdPL3kXMQC1ZRwHyDu1oL6lUuMCMCJ8cDiMvg==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.5.1", - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/exception": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/utils": "7.15.1", - "@types/lodash.debounce": "^4.0.7", - "lodash.debounce": "^4.0.8", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/plugin-tooltip": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/plugin-tooltip/-/plugin-tooltip-7.15.1.tgz", - "integrity": "sha512-Q/TwzqM4CRSTmz0+E/amtNTgk7DJpAOjjCR4am02N2HbYP7GcL92mC1pEx//tus1AW9+LOdF+cmWyObHGkC7Vg==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.5.1", - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/exception": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/utils": "7.15.1", - "@types/lodash.throttle": "^4.1.9", - "lodash.throttle": "^4.1.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/plugin-trailing": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/plugin-trailing/-/plugin-trailing-7.15.1.tgz", - "integrity": "sha512-zsECiNOMta4bIy+4a+BplmMwWfrhy3SYcm1kH6DjvkpoBG6LtZC4fblEnlW4feHzDwXVLHkbac5urR4rsZxLcA==", - "license": "MIT", - "dependencies": { - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/utils": "7.15.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/plugin-upload": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/plugin-upload/-/plugin-upload-7.15.1.tgz", - "integrity": "sha512-9sU2GRERc7lhQ8mSANZ3v1531pmVRImJx4Pr73oB7VPRDS8GbnhtuVI6VewoVh3zJkM8MSE4G4L/TfWXfa2UGQ==", - "license": "MIT", - "dependencies": { - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/exception": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/utils": "7.15.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/preset-commonmark": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/preset-commonmark/-/preset-commonmark-7.15.1.tgz", - "integrity": "sha512-P1dewR9TGe8VFIE5F+W9g/2QQzf47EZ+Uq4CF5mYAFbjzPHwJDgfN4vA/o43feXFNxu5cm4UQFWzSCtDNZFTWg==", - "license": "MIT", - "dependencies": { - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/exception": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/transformer": "7.15.1", - "@milkdown/utils": "7.15.1", - "remark-inline-links": "^7.0.0", - "tslib": "^2.8.1", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.1" - } - }, - "node_modules/@milkdown/preset-gfm": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/preset-gfm/-/preset-gfm-7.15.1.tgz", - "integrity": "sha512-cmQsx1lwWGi7vv/8Kx92dToWpqKWLDp5OZSWE0eiLCtAV87v+vL/bT6xDrjqmlpFjA5WEM7ah+Ki3EpqLsRfng==", - "license": "MIT", - "dependencies": { - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/exception": "7.15.1", - "@milkdown/preset-commonmark": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/transformer": "7.15.1", - "@milkdown/utils": "7.15.1", - "prosemirror-safari-ime-span": "^1.0.1", - "remark-gfm": "^4.0.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/prose": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/prose/-/prose-7.15.1.tgz", - "integrity": "sha512-8xSgiC6qk8j9zDbRZiWcdclr4vIxU6tnzMBg/Kr4pIEOsir0KA+c6kPNFj7T91BeaV9ksCsWOmYfBwd2SptCXQ==", - "license": "MIT", - "dependencies": { - "@milkdown/exception": "7.15.1", - "prosemirror-changeset": "^2.2.1", - "prosemirror-commands": "^1.6.2", - "prosemirror-dropcursor": "^1.8.1", - "prosemirror-gapcursor": "^1.3.2", - "prosemirror-history": "^1.4.1", - "prosemirror-inputrules": "^1.4.0", - "prosemirror-keymap": "^1.2.2", - "prosemirror-model": "^1.24.1", - "prosemirror-schema-list": "^1.5.0", - "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.7.0", - "prosemirror-transform": "^1.10.2", - "prosemirror-view": "^1.37.1", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/transformer": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/transformer/-/transformer-7.15.1.tgz", - "integrity": "sha512-Hwp0swHmvN2D6iM67mnoP7wPeiDipz/GDdyO7CfnYjUsUQcRHzMouoqJ91dp9bO+f4EJ0Vr+8C1qIYRAn4ZDhA==", - "license": "MIT", - "dependencies": { - "@milkdown/exception": "7.15.1", - "@milkdown/prose": "7.15.1", - "remark": "^15.0.1", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "tslib": "^2.8.1", - "unified": "^11.0.3" - } - }, - "node_modules/@milkdown/utils": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@milkdown/utils/-/utils-7.15.1.tgz", - "integrity": "sha512-CvApKO84xdIGOUqvVeyDKRyN+PYqi8WNC9im7YWis2EojaSEleX7GMVOoWUHcB8xSdyuY+yJzPguMsx3QKuHIg==", - "license": "MIT", - "dependencies": { - "@milkdown/core": "7.15.1", - "@milkdown/ctx": "7.15.1", - "@milkdown/exception": "7.15.1", - "@milkdown/prose": "7.15.1", - "@milkdown/transformer": "7.15.1", - "nanoid": "^5.0.9", - "tslib": "^2.8.1" - } - }, - "node_modules/@milkdown/utils/node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" + "node": ">=16" }, + "peerDependencies": { + "react": ">= 18 || >= 19", + "react-dom": ">= 18 || >= 19" + } + }, + "node_modules/@mdxeditor/gurx": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@mdxeditor/gurx/-/gurx-1.2.3.tgz", + "integrity": "sha512-5DQOlEx46oN9spggrC8husAGAhVoEFBGIYKN48es08XhRUbSU6l5bcIQYwRrQaY8clU1tExIcXzw8/fNnoxjpg==", + "license": "MIT", "engines": { - "node": "^18 || >=20" + "node": ">=16" + }, + "peerDependencies": { + "react": ">= 18 || >= 19", + "react-dom": ">= 18 || >= 19" } }, "node_modules/@nodelib/fs.scandir": { @@ -2233,6 +2421,12 @@ "node": ">= 8" } }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2251,6 +2445,946 @@ "dev": true, "license": "MIT" }, + "node_modules/@radix-ui/colors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz", + "integrity": "sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==", + "license": "MIT" + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", + "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-toggle-group": "1.1.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, "node_modules/@react-dnd/asap": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", @@ -2269,6 +3403,28 @@ "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==", "license": "MIT" }, + "node_modules/@react-hook/intersection-observer": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-hook/intersection-observer/-/intersection-observer-3.1.2.tgz", + "integrity": "sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==", + "license": "MIT", + "dependencies": { + "@react-hook/passive-layout-effect": "^1.2.0", + "intersection-observer": "^0.10.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/@react-hook/passive-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz", + "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/@remix-run/router": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", @@ -2572,12 +3728,65 @@ "dev": true, "license": "MIT" }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "node_modules/@stitches/core": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", + "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==", "license": "MIT" }, + "node_modules/@tanstack/query-core": { + "version": "5.87.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.87.0.tgz", + "integrity": "sha512-gRZig2csRl71i/HEAHlE9TOmMqKKs9WkMAqIUlzagH+sNtgjvqxwaVo2HmfNGe+iDWUak0ratSkiRv0m/Y8ijg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.86.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.86.0.tgz", + "integrity": "sha512-/JDw9BP80eambEK/EsDMGAcsL2VFT+8F5KCOwierjPU7QP8Wt1GT32yJpn3qOinBM8/zS3Jy36+F0GiyJp411A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.87.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.87.0.tgz", + "integrity": "sha512-3uRCGHo7KWHl6h7ptzLd5CbrjTQP5Q/37aC1cueClkSN4t/OaNFmfGolgs1AoA0kFjP/OZxTY2ytQoifyJzpWQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.87.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.87.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.87.0.tgz", + "integrity": "sha512-OeOSKsPyLcTVLdn391iNeRqYFEmpYJrY9t+FjKpaC6ql0SyRu2XT3mKYJIfYczhMMlwOIlbJkNaifBveertV8Q==", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.86.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.87.0", + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -2784,55 +3993,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", - "license": "MIT" - }, - "node_modules/@types/d3-transition": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", - "license": "MIT", - "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" - } - }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -2846,9 +4006,17 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -2865,45 +4033,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/katex": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", - "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/lodash.debounce": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", - "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/lodash.throttle": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz", - "integrity": "sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -2933,14 +4062,12 @@ "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.23", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", - "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -2951,26 +4078,19 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" } }, "node_modules/@types/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", "dev": true, "license": "MIT" }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -2978,33 +4098,34 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -3013,26 +4134,27 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -3041,17 +4163,17 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -3059,26 +4181,26 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -3087,13 +4209,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -3101,22 +4223,23 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -3128,45 +4251,70 @@ } } }, - "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -3177,7 +4325,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, "license": "ISC" }, "node_modules/@vitejs/plugin-react": { @@ -3424,165 +4571,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@vue/compiler-core": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.18.tgz", - "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.0", - "@vue/shared": "3.5.18", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-core/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/@vue/compiler-core/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz", - "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.18", - "@vue/shared": "3.5.18" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz", - "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.0", - "@vue/compiler-core": "3.5.18", - "@vue/compiler-dom": "3.5.18", - "@vue/compiler-ssr": "3.5.18", - "@vue/shared": "3.5.18", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.17", - "postcss": "^8.5.6", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz", - "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.18", - "@vue/shared": "3.5.18" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.18.tgz", - "integrity": "sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.18" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.18.tgz", - "integrity": "sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.18", - "@vue/shared": "3.5.18" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.18.tgz", - "integrity": "sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.18", - "@vue/runtime-core": "3.5.18", - "@vue/shared": "3.5.18", - "csstype": "^3.1.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.18.tgz", - "integrity": "sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.18", - "@vue/shared": "3.5.18" - }, - "peerDependencies": { - "vue": "3.5.18" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.18.tgz", - "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==", - "license": "MIT" - }, - "node_modules/@xyflow/react": { - "version": "12.6.4", - "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.6.4.tgz", - "integrity": "sha512-/dOQ43Nu217cwHzy7f8kNUrFMeJJENzftVgT2VdFFHi6fHlG83pF+gLmvkRW9Be7alCsR6G+LFxxCdsQQbazHg==", - "license": "MIT", - "dependencies": { - "@xyflow/system": "0.0.61", - "classcat": "^5.0.3", - "zustand": "^4.4.0" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@xyflow/system": { - "version": "0.0.61", - "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.61.tgz", - "integrity": "sha512-TsZG/Ez8dzxX6/Ol44LvFqVZsYvyz6dpDlAQZZk6hTL7JLGO5vN3dboRJqMwU8/Qtr5IEv5YBzojjAwIqW1HCA==", - "license": "MIT", - "dependencies": { - "@types/d3-drag": "^3.0.7", - "@types/d3-selection": "^3.0.10", - "@types/d3-transition": "^3.0.8", - "@types/d3-zoom": "^3.0.8", - "d3-drag": "^3.0.0", - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0" - } - }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -3595,7 +4587,6 @@ "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" @@ -3641,6 +4632,12 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/anser": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/anser/-/anser-2.3.2.tgz", + "integrity": "sha512-PMqBCBvrOVDRqLGooQb+z+t1Q0PiPyurUQeZRR5uHBOVZcW8B04KMmnT12USnhpNX2wCPagWzLVppQMUG3u0Dw==", + "license": "MIT" + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3699,9 +4696,20 @@ "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/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -3827,6 +4835,26 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3897,6 +4925,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -4054,6 +5106,36 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -4105,10 +5187,16 @@ "node": ">= 6" } }, - "node_modules/classcat": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", - "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/clean-set": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/clean-set/-/clean-set-1.1.2.tgz", + "integrity": "sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==", "license": "MIT" }, "node_modules/clsx": { @@ -4120,6 +5208,18 @@ "node": ">=6" } }, + "node_modules/cm6-theme-basic-light": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cm6-theme-basic-light/-/cm6-theme-basic-light-0.2.0.tgz", + "integrity": "sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==", + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, "node_modules/codemirror": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", @@ -4168,6 +5268,16 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -4178,6 +5288,12 @@ "node": ">= 6" } }, + "node_modules/compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4274,109 +5390,17 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", "license": "ISC", "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" + "es5-ext": "^0.10.64", + "type": "^2.7.2" }, "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" + "node": ">=0.12" } }, "node_modules/data-urls": { @@ -4548,6 +5572,12 @@ "node": ">=6" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", @@ -4639,13 +5669,32 @@ "dev": true, "license": "MIT" }, - "node_modules/dompurify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", - "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/downshift": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.2.tgz", + "integrity": "sha512-iOv+E1Hyt3JDdL9yYcOgW7nZ7GQ2Uz6YbggwXvKUSleetYhU2nXD482Rz6CzvM4lvI1At34BYruKAL4swRGxaA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.12.0" } }, "node_modules/dunder-proto": { @@ -4684,66 +5733,6 @@ "dev": true, "license": "MIT" }, - "node_modules/engine.io-client": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", - "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1", - "xmlhttprequest-ssl": "~2.1.1" - } - }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io-client/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/entities": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", @@ -4827,6 +5816,46 @@ "node": ">= 0.4" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -4876,6 +5905,12 @@ "node": ">=6" } }, + "node_modules/escape-carriage": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz", + "integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -4969,20 +6004,6 @@ "eslint": ">=8.40" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -5039,6 +6060,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -5103,14 +6139,28 @@ "node": ">=4.0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/estree-walker": { @@ -5133,6 +6183,16 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -5157,6 +6217,15 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -5223,6 +6292,19 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -5344,6 +6426,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -5481,6 +6571,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -5672,6 +6771,46 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -5707,6 +6846,16 @@ "dev": true, "license": "MIT" }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -5758,6 +6907,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5824,6 +6993,12 @@ "dev": true, "license": "ISC" }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -5839,6 +7014,36 @@ "node": ">= 0.4" } }, + "node_modules/intersection-observer": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.10.0.tgz", + "integrity": "sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==", + "license": "W3C-20150513" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -5966,6 +7171,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5999,6 +7214,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -6208,6 +7433,17 @@ "dev": true, "license": "ISC" }, + "node_modules/isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", + "license": "MIT", + "peer": true, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -6298,7 +7534,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -6395,31 +7630,6 @@ "node": ">=6" } }, - "node_modules/katex": { - "version": "0.16.22", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", - "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6430,6 +7640,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -6444,6 +7663,34 @@ "node": ">= 0.8.0" } }, + "node_modules/lexical": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.33.1.tgz", + "integrity": "sha512-+kiCS/GshQmCs/meMb8MQT4AMvw3S3Ef0lSCv2Xi6Itvs59OD+NjQWNfYkDteIbKtVE/w0Yiqh56VyGwIb8UcA==", + "license": "MIT" + }, + "node_modules/lib0": { + "version": "0.2.114", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz", + "integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "isomorphic.js": "^0.2.4" + }, + "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -6504,18 +7751,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6523,12 +7758,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "license": "MIT" - }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -6584,7 +7813,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, "license": "MIT", "bin": { "lz-string": "bin/bin.js" @@ -6594,6 +7822,7 @@ "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -6654,30 +7883,20 @@ "node": ">= 0.4" } }, - "node_modules/mdast-util-definitions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", - "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", "unist-util-visit-parents": "^6.0.0" }, "funding": { @@ -6685,18 +7904,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mdast-util-from-markdown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", @@ -6721,59 +7928,36 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { + "node_modules/mdast-util-frontmatter": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", + "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" + "micromark-extension-frontmatter": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mdast-util-gfm-strikethrough": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", @@ -6822,19 +8006,86 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-math": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", - "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "node_modules/mdast-util-highlight-mark": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-highlight-mark/-/mdast-util-highlight-mark-1.2.2.tgz", + "integrity": "sha512-OYumVoytj+B9YgwzBhBcYUCLYHIPvJtAvwnMyKhUXbfUFuER5S+FDZyu9fadUxm2TCT5fRYK3jQXh2ioWAxrMw==", "license": "MIT", "dependencies": { + "micromark-extension-highlight-mark": "1.2.0" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", - "longest-streak": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.1.0", - "unist-util-remove-position": "^5.0.0" + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, "funding": { "type": "opencollective", @@ -6855,6 +8106,27 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-markdown": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", @@ -6975,54 +8247,33 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" }, @@ -7066,19 +8317,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/micromark-extension-gfm-task-list-item": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", @@ -7096,19 +8334,116 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-math": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", - "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "node_modules/micromark-extension-highlight-mark": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-extension-highlight-mark/-/micromark-extension-highlight-mark-1.2.0.tgz", + "integrity": "sha512-huGtbd/9kQsMk8u7nrVMaS5qH/47yDG6ZADggo5Owz5JoY8wdfQjfuy118/QiYNCvdFuFDbzT0A7K7Hp2cBsXA==", "license": "MIT", "dependencies": { - "@types/katex": "^0.16.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "uvu": "^0.5.6" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", "devlop": "^1.0.0", - "katex": "^0.16.0", + "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", @@ -7158,6 +8493,33 @@ "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, "node_modules/micromark-factory-space": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", @@ -7359,6 +8721,31 @@ ], "license": "MIT" }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", @@ -7506,7 +8893,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -7606,6 +8992,15 @@ "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", "license": "MIT" }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -7638,6 +9033,7 @@ "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", @@ -7659,12 +9055,11 @@ "dev": true, "license": "MIT" }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true, - "license": "MIT" + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" }, "node_modules/node-releases": { "version": "2.0.19", @@ -7733,7 +9128,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7854,10 +9248,10 @@ "node": ">= 0.8.0" } }, - "node_modules/orderedmap": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", - "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "node_modules/outvariant": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", + "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==", "license": "MIT" }, "node_modules/p-limit": { @@ -7912,6 +9306,31 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -8017,6 +9436,7 @@ "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": { @@ -8085,6 +9505,7 @@ "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", @@ -8277,181 +9698,31 @@ "node": ">=6" } }, - "node_modules/prosemirror-changeset": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz", - "integrity": "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==", + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", "dependencies": { - "prosemirror-transform": "^1.0.0" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" } }, - "node_modules/prosemirror-commands": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", - "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.10.2" - } + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" }, - "node_modules/prosemirror-dropcursor": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", - "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.1.0", - "prosemirror-view": "^1.1.0" - } - }, - "node_modules/prosemirror-gapcursor": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", - "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", - "license": "MIT", - "dependencies": { - "prosemirror-keymap": "^1.0.0", - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-view": "^1.0.0" - } - }, - "node_modules/prosemirror-history": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", - "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.2.2", - "prosemirror-transform": "^1.0.0", - "prosemirror-view": "^1.31.0", - "rope-sequence": "^1.3.0" - } - }, - "node_modules/prosemirror-inputrules": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.0.tgz", - "integrity": "sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" - } - }, - "node_modules/prosemirror-keymap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", - "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "w3c-keyname": "^2.2.0" - } - }, - "node_modules/prosemirror-model": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.1.tgz", - "integrity": "sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==", - "license": "MIT", - "dependencies": { - "orderedmap": "^2.0.0" - } - }, - "node_modules/prosemirror-safari-ime-span": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/prosemirror-safari-ime-span/-/prosemirror-safari-ime-span-1.0.2.tgz", - "integrity": "sha512-QJqD8s1zE/CuK56kDsUhndh5hiHh/gFnAuPOA9ytva2s85/ZEt2tNWeALTJN48DtWghSKOmiBsvVn2OlnJ5H2w==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.4.3", - "prosemirror-view": "^1.33.8" - }, - "funding": { - "url": "https://github.com/sponsors/ocavue" - } - }, - "node_modules/prosemirror-schema-list": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", - "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.7.3" - } - }, - "node_modules/prosemirror-state": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", - "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-transform": "^1.0.0", - "prosemirror-view": "^1.27.0" - } - }, - "node_modules/prosemirror-tables": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.7.1.tgz", - "integrity": "sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==", - "license": "MIT", - "dependencies": { - "prosemirror-keymap": "^1.2.2", - "prosemirror-model": "^1.25.0", - "prosemirror-state": "^1.4.3", - "prosemirror-transform": "^1.10.3", - "prosemirror-view": "^1.39.1" - } - }, - "node_modules/prosemirror-transform": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.4.tgz", - "integrity": "sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.21.0" - } - }, - "node_modules/prosemirror-view": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.40.0.tgz", - "integrity": "sha512-2G3svX0Cr1sJjkD/DYWSe3cfV5VPVTBOxI9XQEGWJDFEpsZb/gh4MV29ctv+OJx2RFX4BLt09i+6zaGM/ldkCw==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.20.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.1.0" - } - }, - "node_modules/prosemirror-virtual-cursor": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/prosemirror-virtual-cursor/-/prosemirror-virtual-cursor-0.4.2.tgz", - "integrity": "sha512-pUMKnIuOhhnMcgIJUjhIQTVJruBEGxfMBVQSrK0g2qhGPDm1i12KdsVaFw15dYk+29tZcxjMeR7P5VDKwmbwJg==", + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/ocavue" - }, - "peerDependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-view": "^1.0.0" - }, - "peerDependenciesMeta": { - "prosemirror-model": { - "optional": true - }, - "prosemirror-state": { - "optional": true - }, - "prosemirror-view": { - "optional": true - } + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/psl": { @@ -8517,6 +9788,15 @@ "node": ">=0.10.0" } }, + "node_modules/react-devtools-inline": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz", + "integrity": "sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==", + "license": "MIT", + "dependencies": { + "es6-symbol": "^3" + } + }, "node_modules/react-dnd": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", @@ -8569,13 +9849,71 @@ "react": "^18.3.1" } }, + "node_modules/react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/react-hook-form": { + "version": "7.62.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", + "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -8586,6 +9924,53 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "6.30.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", @@ -8618,6 +10003,28 @@ "react-dom": ">=16.8" } }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -8685,71 +10092,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/remark": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", - "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-inline-links": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/remark-inline-links/-/remark-inline-links-7.0.0.tgz", - "integrity": "sha512-4uj1pPM+F495ySZhTIB6ay2oSkTsKgmYaKk/q5HIdhX2fuyLEegpjWa0VdJRJ01sgOqAFo7MBKdDUejIYBMVMQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-definitions": "^6.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-math": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", - "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-math": "^3.0.0", - "micromark-extension-math": "^3.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/remark-parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", @@ -8766,15 +10108,17 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", "license": "MIT", "dependencies": { + "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" }, "funding": { "type": "opencollective", @@ -8887,12 +10231,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/rope-sequence": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", - "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", - "license": "MIT" - }, "node_modules/rrweb-cssom": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", @@ -8924,6 +10262,18 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -9162,77 +10512,26 @@ "node": ">=8" } }, - "node_modules/socket.io-client": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", - "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.2", - "engine.io-client": "~6.6.1", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -9240,6 +10539,18 @@ "dev": true, "license": "MIT" }, + "node_modules/static-browser-server": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz", + "integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==", + "license": "Apache-2.0", + "dependencies": { + "@open-draft/deferred-promise": "^2.1.0", + "dotenv": "^16.0.3", + "mime-db": "^1.52.0", + "outvariant": "^1.3.0" + } + }, "node_modules/std-env": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", @@ -9261,6 +10572,12 @@ "node": ">= 0.4" } }, + "node_modules/strict-event-emitter": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", + "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", + "license": "MIT" + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -9331,6 +10648,20 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -9423,6 +10754,24 @@ "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", "license": "MIT" }, + "node_modules/style-to-js": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", + "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.9" + } + }, + "node_modules/style-to-object": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", + "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -9526,6 +10875,12 @@ "dev": true, "license": "MIT" }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, "node_modules/tailwind-merge": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz", @@ -9698,6 +11053,16 @@ "node": ">=18" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -9708,6 +11073,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -9772,28 +11150,11 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" }, "node_modules/type-check": { "version": "0.4.0", @@ -9835,7 +11196,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -9859,6 +11220,24 @@ "devOptional": true, "license": "MIT" }, + "node_modules/unidiff": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unidiff/-/unidiff-1.0.4.tgz", + "integrity": "sha512-ynU0vsAXw0ir8roa+xPCUHmnJ5goc5BTM2Kuc3IJd8UwgaeRs7VSD5+eeaQL+xp1JtB92hu/Zy/Lgy7RZcr1pQ==", + "license": "MIT", + "dependencies": { + "diff": "^5.1.0" + } + }, + "node_modules/unidiff/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -9891,14 +11270,26 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-remove-position": { + "node_modules/unist-util-position": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", - "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", "license": "MIT", "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" }, "funding": { "type": "opencollective", @@ -10009,13 +11400,47 @@ "requires-port": "^1.0.0" } }, - "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/util-deprecate": { @@ -10025,6 +11450,33 @@ "dev": true, "license": "MIT" }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uvu/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -10209,27 +11661,6 @@ } } }, - "node_modules/vue": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.18.tgz", - "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.18", - "@vue/compiler-sfc": "3.5.18", - "@vue/runtime-dom": "3.5.18", - "@vue/server-renderer": "3.5.18", - "@vue/shared": "3.5.18" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", @@ -10547,14 +11978,6 @@ "dev": true, "license": "MIT" }, - "node_modules/xmlhttprequest-ssl": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", - "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -10575,6 +11998,24 @@ "node": ">= 14.6" } }, + "node_modules/yjs": { + "version": "13.6.27", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz", + "integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==", + "license": "MIT", + "peer": true, + "dependencies": { + "lib0": "^0.2.99" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -10607,34 +12048,6 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/archon-ui-main/package.json b/archon-ui-main/package.json index fc6a1d1a..336132fe 100644 --- a/archon-ui-main/package.json +++ b/archon-ui-main/package.json @@ -7,6 +7,14 @@ "dev": "npx vite", "build": "npx vite build", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "lint:files": "eslint --ext .js,.jsx,.ts,.tsx", + "biome": "biome check", + "biome:fix": "biome check --write", + "biome:format": "biome format --write", + "biome:lint": "biome lint", + "biome:ai": "biome check --reporter=json", + "biome:ai-fix": "biome check --write --reporter=json", + "biome:ci": "biome ci", "preview": "npx vite preview", "test": "vitest", "test:ui": "vitest --ui", @@ -18,11 +26,17 @@ "seed:projects": "node --loader ts-node/esm ../scripts/seed-project-data.ts" }, "dependencies": { - "@milkdown/crepe": "^7.5.0", - "@milkdown/kit": "^7.5.0", - "@milkdown/plugin-history": "^7.5.0", - "@milkdown/preset-commonmark": "^7.5.0", - "@xyflow/react": "^12.3.0", + "@mdxeditor/editor": "^3.42.0", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toast": "^1.2.15", + "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-query": "^5.85.8", + "@tanstack/react-query-devtools": "^5.85.8", "clsx": "latest", "date-fns": "^4.1.0", "fractional-indexing": "^3.2.0", @@ -33,25 +47,26 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.3.1", + "react-markdown": "^10.1.0", "react-router-dom": "^6.26.2", - "socket.io-client": "^4.8.1", "tailwind-merge": "latest", "zod": "^3.25.46" }, "devDependencies": { + "@biomejs/biome": "2.2.2", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^14.3.1", "@testing-library/user-event": "^14.5.2", "@types/node": "^20.19.0", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.1", - "@typescript-eslint/eslint-plugin": "^5.54.0", - "@typescript-eslint/parser": "^5.54.0", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", "@vitejs/plugin-react": "^4.2.1", "@vitest/coverage-v8": "^1.6.0", "@vitest/ui": "^1.6.0", "autoprefixer": "latest", - "eslint": "^8.50.0", + "eslint": "^8.57.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.1", "jsdom": "^24.1.0", diff --git a/archon-ui-main/src/App.tsx b/archon-ui-main/src/App.tsx index 42af02ac..2a0cdc22 100644 --- a/archon-ui-main/src/App.tsx +++ b/archon-ui-main/src/App.tsx @@ -1,13 +1,17 @@ import { useState, useEffect } from 'react'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { KnowledgeBasePage } from './pages/KnowledgeBasePage'; import { SettingsPage } from './pages/SettingsPage'; import { MCPPage } from './pages/MCPPage'; import { OnboardingPage } from './pages/OnboardingPage'; -import { MainLayout } from './components/layouts/MainLayout'; +import { MainLayout } from './components/layout/MainLayout'; import { ThemeProvider } from './contexts/ThemeContext'; import { ToastProvider } from './contexts/ToastContext'; +import { ToastProvider as FeaturesToastProvider } from './features/ui/components/ToastProvider'; import { SettingsProvider, useSettings } from './contexts/SettingsContext'; +import { TooltipProvider } from './features/ui/primitives/tooltip'; import { ProjectPage } from './pages/ProjectPage'; import { DisconnectScreenOverlay } from './components/DisconnectScreenOverlay'; import { ErrorBoundaryWithBugReport } from './components/bug-report/ErrorBoundaryWithBugReport'; @@ -15,6 +19,28 @@ import { MigrationBanner } from './components/ui/MigrationBanner'; import { serverHealthService } from './services/serverHealthService'; import { useMigrationStatus } from './hooks/useMigrationStatus'; +// Create a client with optimized settings for our polling use case +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + // Keep data fresh for 2 seconds by default + staleTime: 2000, + // Cache data for 5 minutes + gcTime: 5 * 60 * 1000, + // Retry failed requests 3 times + retry: 3, + // Refetch on window focus + refetchOnWindowFocus: true, + // Don't refetch on reconnect by default (we handle this manually) + refetchOnReconnect: false, + }, + mutations: { + // Retry mutations once on failure + retry: 1, + }, + }, +}); + const AppRoutes = () => { const { projectsEnabled } = useSettings(); @@ -25,7 +51,10 @@ const AppRoutes = () => { } /> } /> {projectsEnabled ? ( - } /> + <> + } /> + } /> + ) : ( } /> )} @@ -102,12 +131,21 @@ const AppContent = () => { export function App() { return ( - - - - - - - + + + + + + + + + + + + + {import.meta.env.VITE_SHOW_DEVTOOLS === 'true' && ( + + )} + ); } \ No newline at end of file diff --git a/archon-ui-main/src/components/ProjectCreationProgressCard.tsx b/archon-ui-main/src/components/ProjectCreationProgressCard.tsx deleted file mode 100644 index aa56d861..00000000 --- a/archon-ui-main/src/components/ProjectCreationProgressCard.tsx +++ /dev/null @@ -1,289 +0,0 @@ -import React, { useState } from 'react'; -import { Card } from './ui/Card'; -import { motion, AnimatePresence } from 'framer-motion'; -import { - CheckCircle, - XCircle, - Loader2, - FileText, - ChevronDown, - ChevronUp, - RotateCcw, - Clock, - Bot, - BrainCircuit, - BookOpen, - Database, - AlertCircle -} from 'lucide-react'; -import { Button } from './ui/Button'; -import { ProjectCreationProgressData } from '../services/projectCreationProgressService'; - -interface ProjectCreationProgressCardProps { - progressData: ProjectCreationProgressData; - onComplete?: (data: ProjectCreationProgressData) => void; - onError?: (error: string) => void; - onRetry?: () => void; - connectionStatus?: 'connected' | 'connecting' | 'disconnected' | 'error'; -} - -export const ProjectCreationProgressCard: React.FC = ({ - progressData, - onComplete, - onError, - onRetry, - connectionStatus = 'connected' -}) => { - const [showLogs, setShowLogs] = useState(false); - const [hasCompletedRef] = useState({ value: false }); - const [hasErroredRef] = useState({ value: false }); - - // Handle completion/error events - React.useEffect(() => { - if (progressData.status === 'completed' && onComplete && !hasCompletedRef.value) { - hasCompletedRef.value = true; - onComplete(progressData); - } else if (progressData.status === 'error' && onError && !hasErroredRef.value) { - hasErroredRef.value = true; - onError(progressData.error || 'Project creation failed'); - } - }, [progressData.status, onComplete, onError, progressData, hasCompletedRef, hasErroredRef]); - - const getStatusIcon = () => { - switch (progressData.status) { - case 'completed': - return ; - case 'error': - return ; - case 'initializing_agents': - return ; - case 'generating_docs': - case 'processing_requirements': - case 'ai_generation': - return ; - case 'finalizing_docs': - return ; - case 'saving_to_database': - return ; - default: - return ; - } - }; - - const getStatusColor = () => { - switch (progressData.status) { - case 'completed': - return 'text-green-500'; - case 'error': - return 'text-red-500'; - case 'initializing_agents': - return 'text-blue-500'; - case 'generating_docs': - case 'processing_requirements': - case 'ai_generation': - return 'text-purple-500'; - case 'finalizing_docs': - return 'text-indigo-500'; - case 'saving_to_database': - return 'text-green-500'; - default: - return 'text-blue-500'; - } - }; - - const getStatusText = () => { - switch (progressData.status) { - case 'starting': - return 'Starting project creation...'; - case 'initializing_agents': - return 'Initializing AI agents...'; - case 'generating_docs': - return 'Generating documentation...'; - case 'processing_requirements': - return 'Processing requirements...'; - case 'ai_generation': - return 'AI is creating project docs...'; - case 'finalizing_docs': - return 'Finalizing documents...'; - case 'saving_to_database': - return 'Saving to database...'; - case 'completed': - return 'Project created successfully!'; - case 'error': - return 'Project creation failed'; - default: - return 'Processing...'; - } - }; - - const isActive = progressData.status !== 'completed' && progressData.status !== 'error'; - - return ( - - {/* Header */} -
-
- {getStatusIcon()} -
-

- Creating Project: {progressData.project?.title || 'New Project'} -

-

- {getStatusText()} -

-
-
- - {progressData.eta && isActive && ( -
- - {progressData.eta} -
- )} -
- - {/* Connection Status Indicator */} - {connectionStatus !== 'connected' && ( -
-
- {connectionStatus === 'connecting' && } - {connectionStatus === 'disconnected' && } - {connectionStatus === 'error' && } - - {connectionStatus === 'connecting' && 'Connecting to progress stream...'} - {connectionStatus === 'disconnected' && 'Disconnected from progress stream'} - {connectionStatus === 'error' && 'Connection error - retrying...'} - -
-
- )} - - {/* Progress Bar */} -
-
- - Progress - - - {progressData.percentage}% - -
-
- -
-
- - {/* Step Information */} - {progressData.step && ( -
-
- Current Step: - - {progressData.step} - -
-
- )} - - {/* Error Information */} - {progressData.status === 'error' && ( -
-
- Error: {progressData.error || 'Project creation failed'} - {progressData.progressId && ( -
- Progress ID: {progressData.progressId} -
- )} -
-
- )} - - {/* Debug Information - Show when stuck on starting status */} - {progressData.status === 'starting' && progressData.percentage === 0 && connectionStatus === 'connected' && ( -
-
- Debug: Connected to progress stream but no updates received yet. -
- Progress ID: {progressData.progressId} -
-
- Check browser console for Socket.IO connection details. -
-
-
- )} - - {/* Duration (when completed) */} - {progressData.status === 'completed' && progressData.duration && ( -
-
- Completed in: {progressData.duration} -
-
- )} - - {/* Console Logs */} - {progressData.logs && progressData.logs.length > 0 && ( -
- - - - {showLogs && ( - -
-
- {progressData.logs.map((log, index) => ( -
- {log} -
- ))} -
-
-
- )} -
-
- )} - - {/* Action Buttons */} - {progressData.status === 'error' && onRetry && ( -
- -
- )} -
- ); -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/layouts/ArchonChatPanel.tsx b/archon-ui-main/src/components/agent-chat/ArchonChatPanel.tsx similarity index 86% rename from archon-ui-main/src/components/layouts/ArchonChatPanel.tsx rename to archon-ui-main/src/components/agent-chat/ArchonChatPanel.tsx index b30fef6e..4d72a6e1 100644 --- a/archon-ui-main/src/components/layouts/ArchonChatPanel.tsx +++ b/archon-ui-main/src/components/agent-chat/ArchonChatPanel.tsx @@ -41,24 +41,14 @@ export const ArchonChatPanel: React.FC = props => { const chatPanelRef = useRef(null); const sessionIdRef = useRef(null); /** - * Initialize chat session and WebSocket connection + * Initialize chat session and connection */ const initializeChat = React.useCallback(async () => { try { - // Check if WebSocket is enabled - const enableWebSocket = import.meta.env.VITE_ENABLE_WEBSOCKET !== 'false'; - if (!enableWebSocket) { - console.warn('WebSocket connection is disabled by environment configuration'); - setConnectionError('Agent chat is currently disabled'); - setConnectionStatus('offline'); - setIsInitialized(true); - return; - } - setConnectionStatus('connecting'); - // Add a small delay to prevent WebSocket race conditions on page refresh - await new Promise(resolve => setTimeout(resolve, 500)); + // Yield to next frame to avoid initialization race conditions + await new Promise(resolve => requestAnimationFrame(resolve)); // Create a new chat session try { @@ -68,68 +58,57 @@ export const ArchonChatPanel: React.FC = props => { setSessionId(session_id); sessionIdRef.current = session_id; - // Subscribe to connection status changes - agentChatService.onStatusChange(session_id, (status) => { - setConnectionStatus(status); - if (status === 'offline') { - setConnectionError('Chat is offline. Please try reconnecting.'); - } else if (status === 'online') { - setConnectionError(null); - } else if (status === 'connecting') { - setConnectionError('Reconnecting...'); - } - }); + // Load initial chat history + try { + const history = await agentChatService.getChatHistory(session_id); + console.log(`[CHAT PANEL] Loaded chat history:`, history); + setMessages(history || []); + } catch (error) { + console.error('Failed to load chat history:', error); + // Initialize with empty messages if history can't be loaded + setMessages([]); + } - // Load session data to get initial messages - const session = await agentChatService.getSession(session_id); - console.log(`[CHAT PANEL] Loaded session:`, session); - console.log(`[CHAT PANEL] Session agent_type: "${session.agent_type}"`); - console.log(`[CHAT PANEL] First message:`, session.messages?.[0]); - setMessages(session.messages || []); - - // Connect WebSocket for real-time communication - agentChatService.connectWebSocket( - session_id, - (message: ChatMessage) => { - setMessages(prev => [...prev, message]); - setConnectionError(null); // Clear any previous errors on successful message - setConnectionStatus('online'); - }, - (typing: boolean) => { - setIsTyping(typing); - }, - (chunk: string) => { - // Handle streaming chunks - setStreamingMessage(prev => prev + chunk); - setIsStreaming(true); - }, - () => { - // Handle stream completion - setIsStreaming(false); - setStreamingMessage(''); - }, - (error: Event) => { - console.error('WebSocket error:', error); - // Don't set error message here, let the status handler manage it - }, - (event: CloseEvent) => { - console.log('WebSocket closed:', event); - // Don't set error message here, let the status handler manage it - } - ); + // Start polling for new messages (will fail gracefully if backend is down) + try { + await agentChatService.streamMessages( + session_id, + (message: ChatMessage) => { + setMessages(prev => [...prev, message]); + setConnectionError(null); // Clear any previous errors on successful message + setConnectionStatus('online'); + }, + (error: Error) => { + console.error('Message streaming error:', error); + setConnectionStatus('offline'); + setConnectionError('Chat service is offline. Messages will not be received.'); + } + ); + } catch (error) { + console.error('Failed to start message streaming:', error); + // Continue anyway - the chat will work in offline mode + } setIsInitialized(true); setConnectionStatus('online'); setConnectionError(null); } catch (error) { console.error('Failed to initialize chat session:', error); - setConnectionError('Failed to initialize chat. Server may be offline.'); + if (error instanceof Error && error.message.includes('not available')) { + setConnectionError('Agent chat service is disabled. Enable it in docker-compose to use this feature.'); + } else { + setConnectionError('Failed to initialize chat. Server may be offline.'); + } setConnectionStatus('offline'); } } catch (error) { console.error('Failed to initialize chat:', error); - setConnectionError('Failed to connect to agent. Server may be offline.'); + if (error instanceof Error && error.message.includes('not available')) { + setConnectionError('Agent chat service is disabled. Enable it in docker-compose to use this feature.'); + } else { + setConnectionError('Failed to connect to agent. Server may be offline.'); + } setConnectionStatus('offline'); } }, []); @@ -146,8 +125,8 @@ export const ArchonChatPanel: React.FC = props => { return () => { if (sessionIdRef.current) { console.log('[CHAT PANEL] Component unmounting, cleaning up session:', sessionIdRef.current); - agentChatService.disconnectWebSocket(sessionIdRef.current); - agentChatService.offStatusChange(sessionIdRef.current); + // Stop streaming messages when component unmounts + agentChatService.stopStreaming(sessionIdRef.current); } }; }, []); // Empty deps = only on unmount diff --git a/archon-ui-main/src/components/common/DeleteConfirmModal.tsx b/archon-ui-main/src/components/common/DeleteConfirmModal.tsx new file mode 100644 index 00000000..5ee55c88 --- /dev/null +++ b/archon-ui-main/src/components/common/DeleteConfirmModal.tsx @@ -0,0 +1,93 @@ +import React, { useId } from 'react'; +import { Trash2 } from 'lucide-react'; + +interface DeleteConfirmModalProps { + itemName: string; + onConfirm: () => void; + onCancel: () => void; + type: "project" | "task" | "client"; +} + +export const DeleteConfirmModal: React.FC = ({ + itemName, + onConfirm, + onCancel, + type, +}) => { + const titleId = useId(); + const descId = useId(); + const TITLES: Record = { + project: "Delete Project", + task: "Delete Task", + client: "Delete MCP Client", + }; + + const MESSAGES: Record string> = { + project: (n) => `Are you sure you want to delete the "${n}" project? This will also delete all associated tasks and documents and cannot be undone.`, + task: (n) => `Are you sure you want to delete the "${n}" task? This action cannot be undone.`, + client: (n) => `Are you sure you want to delete the "${n}" client? This will permanently remove its configuration and cannot be undone.`, + }; + + return ( +
{ if (e.key === 'Escape') onCancel(); }} + aria-hidden={false} + data-testid="modal-backdrop" + > +
e.stopPropagation()} + > +
+
+
+ +
+
+

+ {TITLES[type]} +

+

+ This action cannot be undone +

+
+
+ +

+ {MESSAGES[type](itemName)} +

+ +
+ + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/archon-ui-main/src/components/knowledge-base/AddKnowledgeModal.tsx b/archon-ui-main/src/components/knowledge-base/AddKnowledgeModal.tsx new file mode 100644 index 00000000..dec8299e --- /dev/null +++ b/archon-ui-main/src/components/knowledge-base/AddKnowledgeModal.tsx @@ -0,0 +1,407 @@ +import { useState } from 'react'; +import { + LinkIcon, + Upload, + BoxIcon, + Brain, + Plus +} from 'lucide-react'; +import { Card } from '../ui/Card'; +import { Button } from '../ui/Button'; +import { Input } from '../ui/Input'; +import { Badge } from '../ui/Badge'; +import { GlassCrawlDepthSelector } from '../ui/GlassCrawlDepthSelector'; +import { useToast } from '../../contexts/ToastContext'; +import { knowledgeBaseService } from '../../services/knowledgeBaseService'; +import { CrawlProgressData } from '../../types/crawl'; + +interface AddKnowledgeModalProps { + onClose: () => void; + onSuccess: () => void; + onStartCrawl: (progressId: string, initialData: Partial) => void; +} + +export const AddKnowledgeModal = ({ + onClose, + onSuccess, + onStartCrawl +}: AddKnowledgeModalProps) => { + const [method, setMethod] = useState<'url' | 'file'>('url'); + const [url, setUrl] = useState(''); + const [tags, setTags] = useState([]); + const [newTag, setNewTag] = useState(''); + const [knowledgeType, setKnowledgeType] = useState<'technical' | 'business'>('technical'); + const [selectedFile, setSelectedFile] = useState(null); + const [loading, setLoading] = useState(false); + const [crawlDepth, setCrawlDepth] = useState(2); + const [showDepthTooltip, setShowDepthTooltip] = useState(false); + const { showToast } = useToast(); + + // URL validation function + const validateUrl = async (url: string): Promise<{ isValid: boolean; error?: string; formattedUrl?: string }> => { + try { + let formattedUrl = url.trim(); + if (!formattedUrl.startsWith('http://') && !formattedUrl.startsWith('https://')) { + formattedUrl = `https://${formattedUrl}`; + } + + let urlObj; + try { + urlObj = new URL(formattedUrl); + } catch { + return { isValid: false, error: 'Please enter a valid URL format' }; + } + + const hostname = urlObj.hostname; + if (!hostname || hostname === 'localhost' || /^\d+\.\d+\.\d+\.\d+$/.test(hostname)) { + return { isValid: true, formattedUrl }; + } + + if (!hostname.includes('.')) { + return { isValid: false, error: 'Please enter a valid domain name' }; + } + + const parts = hostname.split('.'); + const tld = parts[parts.length - 1]; + if (tld.length < 2) { + return { isValid: false, error: 'Please enter a valid domain with a proper extension' }; + } + + // Optional DNS check + try { + const response = await fetch(`https://dns.google/resolve?name=${hostname}&type=A`, { + method: 'GET', + headers: { 'Accept': 'application/json' } + }); + + if (response.ok) { + const dnsResult = await response.json(); + if (dnsResult.Status === 0 && dnsResult.Answer?.length > 0) { + return { isValid: true, formattedUrl }; + } else { + return { isValid: false, error: `Domain "${hostname}" could not be resolved` }; + } + } + } catch { + // Allow URL even if DNS check fails + console.warn('DNS check failed, allowing URL anyway'); + } + + return { isValid: true, formattedUrl }; + } catch { + return { isValid: false, error: 'URL validation failed' }; + } + }; + + const handleSubmit = async () => { + try { + setLoading(true); + + if (method === 'url') { + if (!url.trim()) { + showToast('Please enter a URL', 'error'); + return; + } + + showToast('Validating URL...', 'info'); + const validation = await validateUrl(url); + + if (!validation.isValid) { + showToast(validation.error || 'Invalid URL', 'error'); + return; + } + + const formattedUrl = validation.formattedUrl!; + setUrl(formattedUrl); + + // Detect crawl type based on URL + const crawlType = detectCrawlType(formattedUrl); + + const result = await knowledgeBaseService.crawlUrl({ + url: formattedUrl, + knowledge_type: knowledgeType, + tags, + max_depth: crawlDepth + }); + + if ((result as any).progressId) { + onStartCrawl((result as any).progressId, { + status: 'initializing', + progress: 0, + currentStep: 'Starting crawl', + crawlType, + currentUrl: formattedUrl, + originalCrawlParams: { + url: formattedUrl, + knowledge_type: knowledgeType, + tags, + max_depth: crawlDepth + } + }); + + showToast(`Starting ${crawlType} crawl...`, 'success'); + onClose(); + } else { + showToast((result as any).message || 'Crawling started', 'success'); + onSuccess(); + } + } else { + if (!selectedFile) { + showToast('Please select a file', 'error'); + return; + } + + const result = await knowledgeBaseService.uploadDocument(selectedFile, { + knowledge_type: knowledgeType, + tags + }); + + if (result.success && result.progressId) { + onStartCrawl(result.progressId, { + currentUrl: `file://${selectedFile.name}`, + progress: 0, + status: 'starting', + uploadType: 'document', + fileName: selectedFile.name, + fileType: selectedFile.type, + originalUploadParams: { + file: selectedFile, + knowledge_type: knowledgeType, + tags + } + }); + + showToast('Document upload started', 'success'); + onClose(); + } else { + showToast(result.message || 'Document uploaded', 'success'); + onSuccess(); + } + } + } catch (error) { + console.error('Failed to add knowledge:', error); + showToast('Failed to add knowledge source', 'error'); + } finally { + setLoading(false); + } + }; + + // Helper to detect crawl type + const detectCrawlType = (url: string): 'sitemap' | 'llms-txt' | 'normal' => { + if (url.includes('sitemap.xml')) return 'sitemap'; + if (url.includes('llms') && url.endsWith('.txt')) return 'llms-txt'; + return 'normal'; + }; + + return ( +
+ +

+ Add Knowledge Source +

+ + {/* Knowledge Type Selection */} +
+ +
+ + +
+
+ + {/* Source Type Selection */} +
+ + +
+ + {/* URL Input */} + {method === 'url' && ( +
+ setUrl(e.target.value)} + placeholder="https://example.com or example.com" + accentColor="blue" + /> + {url && !url.startsWith('http://') && !url.startsWith('https://') && ( +

+ ℹ️ Will automatically add https:// prefix +

+ )} +
+ )} + + {/* File Upload */} + {method === 'file' && ( +
+ +
+ setSelectedFile(e.target.files?.[0] || null)} + className="sr-only" + /> + +
+

+ Supports PDF, MD, DOC up to 10MB +

+
+ )} + + {/* Crawl Depth - Only for URLs */} + {method === 'url' && ( +
+ + + +
+ )} + + {/* Tags */} +
+ +
+ {tags.map((tag) => ( + + {tag} + + + ))} +
+ setNewTag(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && newTag.trim()) { + setTags([...tags, newTag.trim()]); + setNewTag(''); + } + }} + placeholder="Add tags..." + accentColor="purple" + /> +
+ + {/* Action Buttons */} +
+ + +
+
+
+ ); +}; \ No newline at end of file diff --git a/archon-ui-main/src/components/knowledge-base/CrawlingProgressCard.tsx b/archon-ui-main/src/components/knowledge-base/CrawlingProgressCard.tsx index 2ee9af14..f5eeb5aa 100644 --- a/archon-ui-main/src/components/knowledge-base/CrawlingProgressCard.tsx +++ b/archon-ui-main/src/components/knowledge-base/CrawlingProgressCard.tsx @@ -1,50 +1,80 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { - ChevronDown, - ChevronUp, - AlertTriangle, - CheckCircle, - Clock, - Globe, + Activity, + AlertTriangle, + CheckCircle, + ChevronDown, + ChevronUp, + Clock, + Globe, FileText, RotateCcw, X, + FileCode, + Upload, Search, - Download, Cpu, Database, Code, Zap, - Square + Square, + Layers, + Download } from 'lucide-react'; import { Card } from '../ui/Card'; import { Button } from '../ui/Button'; -import { CrawlProgressData } from '../../services/crawlProgressService'; +import { Badge } from '../ui/Badge'; +import { CrawlProgressData } from '../../types/crawl'; +import { useCrawlProgressPolling } from '../../hooks/useCrawlQueries'; import { useTerminalScroll } from '../../hooks/useTerminalScroll'; -import { knowledgeBaseService } from '../../services/knowledgeBaseService'; interface CrawlingProgressCardProps { - progressData: CrawlProgressData; - onComplete: (data: CrawlProgressData) => void; - onError: (error: string) => void; - onProgress?: (data: CrawlProgressData) => void; + progressId: string; + initialData?: Partial; + onComplete?: (data: CrawlProgressData) => void; + onError?: (error: string) => void; onRetry?: () => void; onDismiss?: () => void; onStop?: () => void; } -interface ProgressStep { - id: string; - label: string; - icon: React.ReactNode; - percentage: number; - status: 'pending' | 'active' | 'completed' | 'error'; - message?: string; -} +// Simple mapping of backend status to UI display +const STATUS_CONFIG = { + // Common statuses + 'starting': { label: 'Starting', icon: , color: 'blue' }, + 'initializing': { label: 'Initializing', icon: , color: 'blue' }, + + // Crawl statuses + 'analyzing': { label: 'Analyzing URL', icon: , color: 'purple' }, + 'crawling': { label: 'Crawling Pages', icon: , color: 'blue' }, + 'processing': { label: 'Processing Content', icon: , color: 'cyan' }, + 'source_creation': { label: 'Creating Source', icon: , color: 'indigo' }, + 'document_storage': { label: 'Storing Documents', icon: , color: 'green' }, + 'code_extraction': { label: 'Extracting Code', icon: , color: 'yellow' }, + 'finalization': { label: 'Finalizing', icon: , color: 'orange' }, + + // Upload statuses + 'reading': { label: 'Reading File', icon: , color: 'blue' }, + 'extracting': { label: 'Extracting Text', icon: , color: 'blue' }, + 'chunking': { label: 'Chunking Content', icon: , color: 'blue' }, + 'creating_source': { label: 'Creating Source', icon: , color: 'blue' }, + 'summarizing': { label: 'Generating Summary', icon: , color: 'purple' }, + 'storing': { label: 'Storing Chunks', icon: , color: 'green' }, + + // End states + 'completed': { label: 'Completed', icon: , color: 'green' }, + 'error': { label: 'Error', icon: , color: 'red' }, + 'failed': { label: 'Failed', icon: , color: 'red' }, + 'cancelled': { label: 'Cancelled', icon: , color: 'gray' }, + 'stopping': { label: 'Stopping', icon: , color: 'orange' }, +} as const; export const CrawlingProgressCard: React.FC = ({ - progressData, + progressId, + initialData, + onComplete, + onError, onRetry, onDismiss, onStop @@ -53,575 +83,499 @@ export const CrawlingProgressCard: React.FC = ({ const [showLogs, setShowLogs] = useState(false); const [isStopping, setIsStopping] = useState(false); - // Use the terminal scroll hook for auto-scrolling logs - const logsContainerRef = useTerminalScroll([progressData.logs], showLogs); - - // Handle stop crawl action - const handleStopCrawl = async () => { - console.log('🛑 Stop button clicked!'); - console.log('🛑 Progress data:', progressData); - console.log('🛑 Progress ID:', progressData.progressId); - console.log('🛑 Is stopping:', isStopping); - console.log('🛑 onStop callback:', onStop); + // Track completion/error handling + const [hasHandledCompletion, setHasHandledCompletion] = useState(false); + const [hasHandledError, setHasHandledError] = useState(false); + + // Poll for progress updates + const { data: progressData } = useCrawlProgressPolling(progressId, { + onError: (error: Error) => { + if (error.message === 'Resource no longer exists') { + if (onDismiss) { + onDismiss(); + } + } + } + }); + + // Merge polled data with initial data - preserve important fields + const displayData = progressData ? { + ...initialData, + ...progressData, + // Ensure we don't lose these fields during polling + currentUrl: progressData.currentUrl || progressData.current_url || initialData?.currentUrl, + crawlType: progressData.crawlType || progressData.crawl_type || initialData?.crawlType, + } : { + progressId, + status: 'starting', + progress: 0, + message: 'Initializing...', + ...initialData + } as CrawlProgressData; + + // Use terminal scroll hook for logs + const logsContainerRef = useTerminalScroll( + displayData?.logs || [], + showLogs + ); + + // Handle status changes + useEffect(() => { + if (!progressData) return; - if (!progressData.progressId || isStopping) { - console.log('🛑 Stopping early - no progress ID or already stopping'); - return; + if (progressData.status === 'completed' && !hasHandledCompletion && onComplete) { + setHasHandledCompletion(true); + onComplete(progressData); + } else if ((progressData.status === 'error' || progressData.status === 'failed') && !hasHandledError && onError) { + setHasHandledError(true); + onError(progressData.error || 'Unknown error'); + } + }, [progressData?.status, hasHandledCompletion, hasHandledError, onComplete, onError]); + + // Get current status config with better fallback + const statusConfig = (() => { + const config = STATUS_CONFIG[displayData.status as keyof typeof STATUS_CONFIG]; + if (config) { + return config; } + // Better fallbacks based on progress + if (displayData.progress >= 100) { + return STATUS_CONFIG.completed; + } + if (displayData.progress > 90) { + return STATUS_CONFIG.finalization; + } + + // Log unknown statuses for debugging + console.warn(`Unknown status: ${displayData.status}, progress: ${displayData.progress}%, message: ${displayData.message}`); + + return STATUS_CONFIG.processing; + })(); + + // Debug log for status transitions + useEffect(() => { + if (displayData.status === 'finalization' || + (displayData.status === 'starting' && displayData.progress > 90)) { + console.log('Status transition debug:', { + status: displayData.status, + progress: displayData.progress, + message: displayData.message, + hasStatusConfig: !!STATUS_CONFIG[displayData.status as keyof typeof STATUS_CONFIG] + }); + } + }, [displayData.status, displayData.progress]); + + // Determine crawl type display + const getCrawlTypeDisplay = () => { + const crawlType = displayData.crawlType || + (displayData.uploadType === 'document' ? 'upload' : 'normal'); + + switch (crawlType) { + case 'sitemap': + return { icon: , label: 'Sitemap Crawl' }; + case 'llms-txt': + case 'text_file': + return { icon: , label: 'LLMs.txt Import' }; + case 'upload': + return { icon: , label: 'Document Upload' }; + default: + return { icon: , label: 'Web Crawl' }; + } + }; + + const crawlType = getCrawlTypeDisplay(); + + // Handle stop + const handleStop = async () => { + if (isStopping || !onStop) return; + setIsStopping(true); try { - setIsStopping(true); - console.log('🛑 Stopping crawl with progress ID:', progressData.progressId); - - // Optimistic UI update - immediately show stopping status - progressData.status = 'stopping'; - - // Call the onStop callback if provided - this will handle localStorage and API call - if (onStop) { - console.log('🛑 Calling onStop callback'); - onStop(); - } - } catch (error) { - console.error('Failed to stop crawl:', error); - // Revert optimistic update on error - progressData.status = progressData.status === 'stopping' ? 'processing' : progressData.status; + onStop(); } finally { setIsStopping(false); } }; - - // Calculate individual progress steps based on current status and percentage - const getProgressSteps = (): ProgressStep[] => { - // Check if this is an upload operation - const isUpload = progressData.uploadType === 'document'; + + // Get progress steps based on type + const getProgressSteps = () => { + const isUpload = displayData.uploadType === 'document'; - const steps: ProgressStep[] = isUpload ? [ - { - id: 'reading', - label: 'Reading File', - icon: , - percentage: 0, - status: 'pending' - }, - { - id: 'extracting', - label: 'Text Extraction', - icon: , - percentage: 0, - status: 'pending' - }, - { - id: 'chunking', - label: 'Content Chunking', - icon: , - percentage: 0, - status: 'pending' - }, - { - id: 'creating_source', - label: 'Creating Source', - icon: , - percentage: 0, - status: 'pending' - }, - { - id: 'summarizing', - label: 'AI Summary', - icon: , - percentage: 0, - status: 'pending' - }, - { - id: 'storing', - label: 'Storing Chunks', - icon: , - percentage: 0, - status: 'pending' - } + const steps = isUpload ? [ + 'reading', 'extracting', 'chunking', 'creating_source', 'summarizing', 'storing' ] : [ - { - id: 'analyzing', - label: 'URL Analysis', - icon: , - percentage: 0, - status: 'pending' - }, - { - id: 'crawling', - label: 'Web Crawling', - icon: , - percentage: 0, - status: 'pending' - }, - { - id: 'processing', - label: 'Content Processing', - icon: , - percentage: 0, - status: 'pending' - }, - { - id: 'source_creation', - label: 'Source Creation', - icon: , - percentage: 0, - status: 'pending' - }, - { - id: 'document_storage', - label: 'Document Storage', - icon: , - percentage: 0, - status: 'pending' - }, - { - id: 'code_storage', - label: 'Code Examples', - icon: , - percentage: 0, - status: 'pending' - }, - { - id: 'finalization', - label: 'Finalization', - icon: , - percentage: 0, - status: 'pending' - } + 'analyzing', 'crawling', 'processing', 'source_creation', 'document_storage', 'code_extraction', 'finalization' ]; - - // Map current status directly to step progress - const currentStatus = progressData.status; - const currentPercentage = progressData.percentage || 0; - - // Normalize status to handle backend/frontend naming differences - const normalizedStatus = currentStatus === 'code_extraction' ? 'code_storage' : currentStatus; - - // Define step order for completion tracking - const stepOrder = isUpload - ? ['reading', 'extracting', 'chunking', 'creating_source', 'summarizing', 'storing'] - : ['analyzing', 'crawling', 'processing', 'source_creation', 'document_storage', 'code_storage', 'finalization']; - // Update step progress based on current status - steps.forEach((step) => { - const stepIndex = stepOrder.indexOf(step.id); - const currentStepIndex = stepOrder.indexOf(normalizedStatus); + return steps.map(stepId => { + const config = STATUS_CONFIG[stepId as keyof typeof STATUS_CONFIG]; + const currentIndex = steps.indexOf(displayData.status || ''); + const stepIndex = steps.indexOf(stepId); - if (currentStatus === 'error') { - if (stepIndex <= currentStepIndex) { - step.status = stepIndex === currentStepIndex ? 'error' : 'completed'; - step.percentage = stepIndex === currentStepIndex ? currentPercentage : 100; - } else { - step.status = 'pending'; - step.percentage = 0; - } - } else if (currentStatus === 'completed') { - step.status = 'completed'; - step.percentage = 100; - } else if (step.id === normalizedStatus) { - // This is the active step - step.status = 'active'; - // Calculate phase-specific percentage based on overall progress - // Each phase has a range in the overall progress: - // analyzing: 0-5%, crawling: 5-20%, processing/source_creation: 10-20%, - // document_storage: 20-85%, code_storage: 85-95%, finalization: 95-100% - const phaseRanges = { - 'analyzing': { start: 0, end: 5 }, - 'crawling': { start: 5, end: 20 }, - 'processing': { start: 10, end: 15 }, - 'source_creation': { start: 15, end: 20 }, - 'document_storage': { start: 20, end: 85 }, - 'code_storage': { start: 85, end: 95 }, - 'code_extraction': { start: 85, end: 95 }, - 'finalization': { start: 95, end: 100 } - }; - - const range = phaseRanges[step.id as keyof typeof phaseRanges]; - if (range && currentPercentage >= range.start) { - // Calculate percentage within this phase - const phaseProgress = ((currentPercentage - range.start) / (range.end - range.start)) * 100; - step.percentage = Math.min(Math.round(phaseProgress), 100); - } else { - step.percentage = currentPercentage; - } - } else if (stepIndex < currentStepIndex) { - // Previous steps are completed - step.status = 'completed'; - step.percentage = 100; - } else { - // Future steps are pending - step.status = 'pending'; - step.percentage = 0; - } - - // Set specific messages based on current status - if (step.status === 'active') { - // Always use the log message from backend if available - if (progressData.log) { - step.message = progressData.log; - } else if (!progressData.log) { - // Only use fallback messages if no log provided - if (isUpload) { - switch (step.id) { - case 'reading': - step.message = `Reading ${progressData.fileName || 'file'}...`; - break; - case 'extracting': - step.message = `Extracting text from ${progressData.fileType || 'document'}...`; - break; - case 'chunking': - step.message = 'Breaking into chunks...'; - break; - case 'creating_source': - step.message = 'Creating source entry...'; - break; - case 'summarizing': - step.message = 'Generating AI summary...'; - break; - case 'storing': - step.message = 'Storing in database...'; - break; - } - } else { - switch (step.id) { - case 'analyzing': - step.message = 'Detecting URL type...'; - break; - case 'crawling': - step.message = `${progressData.processedPages || 0} of ${progressData.totalPages || 0} pages`; - break; - case 'processing': - step.message = 'Chunking content...'; - break; - case 'source_creation': - step.message = 'Creating source records...'; - break; - case 'document_storage': - if (progressData.completedBatches !== undefined && progressData.totalBatches) { - step.message = `Batch ${progressData.completedBatches}/${progressData.totalBatches} - Saving to database...`; - } else { - step.message = 'Saving to database...'; - } - break; - case 'code_storage': - step.message = 'Extracting code blocks...'; - break; - case 'finalization': - step.message = 'Completing crawl...'; - break; - } - } - } - } else if (step.status === 'completed' && step.percentage === 100 && currentPercentage < 95) { - // Add message for completed steps when overall progress is still ongoing - const isTextFile = progressData.currentUrl && - (progressData.currentUrl.endsWith('.txt') || progressData.currentUrl.endsWith('.md')); - - switch (step.id) { - case 'crawling': - step.message = isTextFile ? 'Text file fetched, processing content...' : 'Crawling complete, processing...'; - break; - case 'analyzing': - step.message = 'Analysis complete'; - break; - case 'processing': - step.message = 'Processing complete'; - break; - case 'source_creation': - step.message = 'Source created'; - break; - } + let status: 'pending' | 'active' | 'completed' | 'error' = 'pending'; + + if (displayData.status === 'completed') { + status = 'completed'; + } else if (displayData.status === 'error' || displayData.status === 'failed') { + status = stepIndex <= currentIndex ? 'error' : 'pending'; + } else if (stepIndex < currentIndex) { + status = 'completed'; + } else if (stepIndex === currentIndex) { + status = 'active'; } + + return { + id: stepId, + label: config.label, + icon: config.icon, + status + }; }); - - return steps; }; - + const progressSteps = getProgressSteps(); - const overallStatus = progressData.status; - - const getOverallStatusDisplay = () => { - const isUpload = progressData.uploadType === 'document'; - - switch (overallStatus) { - case 'starting': - return { - text: isUpload ? 'Starting upload...' : 'Starting crawl...', - color: 'blue' as const, - icon: - }; - case 'completed': - return { - text: isUpload ? 'Upload completed!' : 'Crawling completed!', - color: 'green' as const, - icon: - }; - case 'error': - return { - text: isUpload ? 'Upload failed' : 'Crawling failed', - color: 'pink' as const, - icon: - }; - case 'stale': - return { - text: isUpload ? 'Upload appears stuck' : 'Crawl appears stuck', - color: 'pink' as const, - icon: - }; - case 'reading': - return { - text: 'Reading file...', - color: 'blue' as const, - icon: - }; - case 'extracting': - return { - text: 'Extracting text...', - color: 'blue' as const, - icon: - }; - case 'chunking': - return { - text: 'Processing content...', - color: 'blue' as const, - icon: - }; - case 'creating_source': - return { - text: 'Creating source...', - color: 'blue' as const, - icon: - }; - case 'summarizing': - return { - text: 'Generating summary...', - color: 'blue' as const, - icon: - }; - case 'storing': - return { - text: 'Storing chunks...', - color: 'blue' as const, - icon: - }; - case 'source_creation': - return { - text: 'Creating source records...', - color: 'blue' as const, - icon: - }; - case 'document_storage': - return { - text: progressData.completedBatches !== undefined && progressData.totalBatches - ? `Document Storage: ${progressData.completedBatches}/${progressData.totalBatches} batches` - : 'Storing documents...', - color: 'blue' as const, - icon: - }; - case 'code_storage': - case 'code_extraction': - return { - text: 'Processing code examples...', - color: 'blue' as const, - icon: - }; - case 'finalization': - return { - text: 'Finalizing...', - color: 'blue' as const, - icon: - }; - case 'cancelled': - return { - text: isUpload ? 'Upload cancelled' : 'Crawling cancelled', - color: 'pink' as const, - icon: - }; - case 'stopping': - return { - text: isUpload ? 'Stopping upload...' : 'Stopping crawl...', - color: 'pink' as const, - icon: - }; - default: - const activeStep = progressSteps.find(step => step.status === 'active'); - return { - text: activeStep ? activeStep.label : 'Processing...', - color: 'blue' as const, - icon: activeStep ? activeStep.icon : - }; - } - }; - - const status = getOverallStatusDisplay(); - - const formatNumber = (num: number): string => { - return num.toLocaleString(); - }; - - const getStepStatusColor = (stepStatus: string, isProcessingContinuing: boolean = false) => { - switch (stepStatus) { - case 'completed': - return isProcessingContinuing - ? 'text-green-600 dark:text-green-400 bg-green-100 dark:bg-green-500/10 animate-pulse' - : 'text-green-600 dark:text-green-400 bg-green-100 dark:bg-green-500/10'; - case 'active': - return 'text-blue-600 dark:text-blue-400 bg-blue-100 dark:bg-blue-500/10'; - case 'error': - return 'text-pink-600 dark:text-pink-400 bg-pink-100 dark:bg-pink-500/10'; - default: - return 'text-gray-400 dark:text-gray-600 bg-gray-100 dark:bg-gray-500/10'; - } - }; - + const isActive = !['completed', 'error', 'failed', 'cancelled'].includes(displayData.status || ''); + return ( - - {/* Status Header */} + + {/* Header */}
-
- {status.icon} -
-
-

- {status.text} -

- {progressData.currentUrl && ( + + {crawlType.icon} + {crawlType.label} + + +
+
+ + {statusConfig.label} + + {isActive && ( + + {statusConfig.icon} + + )} +
+ {displayData.currentUrl && (

- {progressData.currentUrl} + {displayData.currentUrl}

)}
- - {/* Stop Button - only show for active crawls */} - {progressData.status !== 'completed' && - progressData.status !== 'error' && - progressData.status !== 'cancelled' && - onStop && ( -
- { - e.preventDefault(); - e.stopPropagation(); - console.log('🛑 Button click event triggered'); - handleStopCrawl(); - }} - disabled={isStopping} - data-testid="crawling-progress-stop" - className={` - relative rounded-full border-2 transition-all duration-300 p-2 - border-red-400 hover:border-red-300 - ${isStopping ? - 'bg-gray-100 dark:bg-gray-800 opacity-50 cursor-not-allowed' : - 'bg-gradient-to-b from-gray-900 to-black cursor-pointer' - } - shadow-[0_0_8px_rgba(239,68,68,0.6)] hover:shadow-[0_0_12px_rgba(239,68,68,0.8)] - `} - whileHover={{ scale: isStopping ? 1 : 1.05 }} - whileTap={{ scale: isStopping ? 1 : 0.95 }} - title={isStopping ? "Stopping..." : "Stop Crawl"} - > - {/* Simplified glow - no overflow issues */} - - - {/* Stop icon with simpler glow */} - - - - -
+ + {/* Stop button */} + {isActive && onStop && ( + )} -
- + {/* Main Progress Bar */} - {progressData.status !== 'completed' && progressData.status !== 'error' && ( + {isActive && (
Overall Progress - {Math.round(Math.max(0, Math.min(100, progressData.percentage || 0)))}% + {Math.round(displayData.progress || 0)}%
-
+
+ + {/* Current message with numeric progress */} + {displayData.message && ( +

+ {displayData.message} + {displayData.status === 'crawling' && displayData.totalPages !== undefined && displayData.totalPages > 0 && ( + + ({displayData.processedPages || 0}/{displayData.totalPages} pages) + + )} +

+ )}
)} - - {/* Show parallel workers info when available */} - {progressData.parallelWorkers && progressData.parallelWorkers > 1 && - progressData.status === 'document_storage' && ( -
+ + {/* Finalization Progress */} + {isActive && displayData.status === 'finalization' && ( +
- - - Processing with {progressData.parallelWorkers} parallel workers + + + Finalizing Results
- {progressData.totalJobs && ( -
- Total batches to process: {progressData.totalJobs} +

+ Completing crawl and saving final metadata... +

+
+ )} + + {/* Crawling Statistics - Show detailed crawl progress */} + {isActive && displayData.status === 'crawling' && (displayData.totalPages > 0 || displayData.processedPages > 0) && ( +
+
+ + + Crawling Progress + +
+
+
+
Pages Discovered
+
+ {displayData.totalPages || 0} +
+
+
+
Pages Processed
+
+ {displayData.processedPages || 0} +
+
+
+ {displayData.currentUrl && ( +
+
Currently crawling:
+
+ {displayData.currentUrl} +
)}
)} - - {/* Show info when crawling is complete but processing continues */} - {progressData.status === 'document_storage' && progressData.percentage < 30 && ( -
-
- - - Content fetched successfully. Processing and storing documents... + + {/* Code Extraction Progress - Special handling for long-running step */} + {isActive && displayData.status === 'code_extraction' && ( +
+
+ + + Extracting Code Examples
+ + {/* Show document scanning progress if available */} + {(displayData.completedDocuments !== undefined || displayData.totalDocuments !== undefined) && + displayData.completedDocuments < displayData.totalDocuments && ( +
+
+ Scanning documents: {displayData.completedDocuments || 0} / {displayData.totalDocuments || 0} +
+
+
+
+
+ )} + + {/* Show summary generation progress */} + {(displayData.completedSummaries !== undefined || displayData.totalSummaries !== undefined) && displayData.totalSummaries > 0 && ( +
+
+ Generating summaries: {displayData.completedSummaries || 0} / {displayData.totalSummaries || 0} +
+
+
+
+
+ )} + + {/* Show code blocks found and stored */} +
+ {displayData.codeBlocksFound !== undefined && ( +
+
Code Blocks Found
+
+ {displayData.codeBlocksFound} +
+
+ )} + {displayData.codeExamplesStored !== undefined && ( +
+
Examples Stored
+
+ {displayData.codeExamplesStored} +
+
+ )} +
+ + {/* Fallback to details if main fields not available */} + {!displayData.codeBlocksFound && displayData.details?.codeBlocksFound !== undefined && ( +
+
+ + {displayData.details.codeBlocksFound} + + + code blocks found + +
+ {displayData.details?.totalChunks && ( +
+ Scanning chunk {displayData.details.currentChunk || 0} of {displayData.details.totalChunks} +
+ )} +
+ )} + +

+ {displayData.completedSummaries !== undefined && displayData.totalSummaries > 0 + ? `Generating AI summaries for ${displayData.totalSummaries} code examples...` + : displayData.completedDocuments !== undefined && displayData.totalDocuments > 0 + ? `Scanning ${displayData.totalDocuments} document(s) for code blocks...` + : 'Analyzing content for code examples...'} +

+
+ )} + + {/* Real-time Processing Stats */} + {isActive && displayData.status === 'document_storage' && ( +
+ {displayData.details?.currentChunk !== undefined && displayData.details?.totalChunks && ( +
+
Chunks Processing
+
+ {displayData.details.currentChunk} / {displayData.details.totalChunks} +
+
+ {Math.round((displayData.details.currentChunk / displayData.details.totalChunks) * 100)}% complete +
+
+ )} + + {displayData.details?.embeddingsCreated !== undefined && ( +
+
Embeddings
+
+ {displayData.details.embeddingsCreated} +
+
created
+
+ )} + + {displayData.details?.codeBlocksFound !== undefined && displayData.status === 'code_extraction' && ( +
+
Code Blocks
+
+ {displayData.details.codeBlocksFound} +
+
extracted
+
+ )} + + {displayData.details?.chunksPerSecond && ( +
+
Processing Speed
+
+ {displayData.details.chunksPerSecond.toFixed(1)} +
+
chunks/sec
+
+ )} + + {displayData.details?.estimatedTimeRemaining && ( +
+
Time Remaining
+
+ {Math.ceil(displayData.details.estimatedTimeRemaining / 60)}m +
+
estimated
+
+ )} +
+ )} + + {/* Batch Processing Info - Enhanced */} + {(() => { + const shouldShowBatch = displayData.totalBatches && displayData.totalBatches > 0 && isActive && displayData.status === 'document_storage'; + return shouldShowBatch; + })() && ( +
+
+
+ + + Batch Processing + +
+ + {displayData.completedBatches || 0}/{displayData.totalBatches} batches + +
+ + {/* Batch progress bar */} +
+ +
+ +
+ {displayData.activeWorkers !== undefined && ( +
+ {displayData.activeWorkers} parallel {displayData.activeWorkers === 1 ? 'worker' : 'workers'} +
+ )} + + {displayData.currentBatch && displayData.totalChunksInBatch && ( +
+ Current: {displayData.chunksInBatch || 0}/{displayData.totalChunksInBatch} chunks +
+ )} + + {displayData.details?.totalChunks && ( +
+ Total progress: {displayData.details.currentChunk || 0}/{displayData.details.totalChunks} chunks processed +
+ )} +
)} - - {/* Detailed Progress Toggle */} - {progressData.status !== 'completed' && progressData.status !== 'error' && ( + + {/* Detailed Progress Steps */} + {isActive && (
)} - - {/* Multi-Progress Bars */} + - {showDetailedProgress && progressData.status !== 'completed' && progressData.status !== 'error' && ( + {showDetailedProgress && isActive && ( = ({ transition={{ duration: 0.3 }} className="overflow-hidden mb-4" > -
- {/* Always show progress steps */} +
{progressSteps.map((step) => ( -
-
-
- {step.status === 'active' && progressData.status !== 'completed' ? ( - - {step.icon} - - ) : ( - step.icon - )} -
-
-
- - {step.label} - - - {Math.round(step.percentage)}% - -
-
- -
- {step.message && ( -

- {step.message} -

- )} -
+
+
+ {step.status === 'active' ? ( + + {step.icon} + + ) : ( + step.icon + )}
- - {/* Show simplified batch progress for document_storage step */} - {step.id === 'document_storage' && (step.status === 'active' || step.status === 'completed') && - progressData.total_batches && progressData.total_batches > 0 && ( - - {/* Batch progress info */} -
-
- - Batch Progress +
+ + {step.label} + + + {/* Show detailed progress for active step */} + {step.status === 'active' && ( +
+ {step.id === 'document_storage' && displayData.completedBatches !== undefined && displayData.totalBatches ? ( + Batch {displayData.completedBatches + 1} of {displayData.totalBatches} + ) : step.id === 'code_extraction' && displayData.details?.codeBlocksFound !== undefined ? ( + {displayData.details.codeBlocksFound} code blocks found + ) : step.id === 'crawling' && (displayData.processedPages !== undefined || displayData.totalPages !== undefined) ? ( + + {displayData.processedPages !== undefined ? displayData.processedPages : '?'} of {displayData.totalPages !== undefined ? displayData.totalPages : '?'} pages -
- {progressData.active_workers && progressData.active_workers > 0 && ( - - - {progressData.active_workers} {progressData.active_workers === 1 ? 'worker' : 'workers'} - - )} - - {progressData.completed_batches || 0}/{progressData.total_batches || 0} - -
-
- - {/* Single batch progress bar */} -
- -
- - {/* Current batch details */} - {progressData.current_batch && progressData.current_batch > 0 && ( -
- Processing batch {progressData.current_batch}: - {progressData.total_chunks_in_batch && progressData.total_chunks_in_batch > 0 && ( - - {progressData.chunks_in_batch || 0}/{progressData.total_chunks_in_batch} chunks processed - - )} -
- )} - - {/* Status text */} -
- Completed: {progressData.completed_batches || 0} batches - {progressData.current_batch && progressData.current_batch > 0 && - progressData.current_batch <= (progressData.total_batches || 0) && ( - • In Progress: 1 batch - )} -
+ ) : displayData.message ? ( + {displayData.message} + ) : null}
- - )} + )} +
))}
)} - - - {/* Progress Details */} -
- {progressData.uploadType === 'document' ? ( - // Upload-specific details - <> - {progressData.fileName && ( -
- File: - - {progressData.fileName} - -
- )} - {progressData.status === 'completed' && ( - <> - {progressData.chunksStored && ( -
- Chunks: - - {formatNumber(progressData.chunksStored)} chunks stored - -
- )} - {progressData.wordCount && ( -
- Words: - - {formatNumber(progressData.wordCount)} words processed - -
- )} - {progressData.sourceId && ( -
- Source ID: - - {progressData.sourceId} - -
- )} - - )} - - ) : ( - // Crawl-specific details - <> - {progressData.totalPages && progressData.processedPages !== undefined && ( -
- Pages: - - {progressData.processedPages} of {progressData.totalPages} pages processed - -
- )} - - {progressData.status === 'completed' && ( - <> - {progressData.chunksStored && ( -
- Chunks: - - {formatNumber(progressData.chunksStored)} chunks stored - -
- )} - {progressData.wordCount && ( -
- Words: - - {formatNumber(progressData.wordCount)} words processed - -
- )} - {progressData.duration && ( -
- Duration: - - {progressData.duration} - -
- )} - - )} - - )} -
- + + {/* Statistics */} + {(displayData.status === 'completed' || !isActive) && ( +
+ {displayData.totalPages && ( +
+ Pages: + + {displayData.processedPages || 0} / {displayData.totalPages} + +
+ )} + {displayData.chunksStored && ( +
+ Chunks: + + {displayData.chunksStored} + +
+ )} + {displayData.details?.embeddingsCreated && ( +
+ Embeddings: + + {displayData.details.embeddingsCreated} + +
+ )} + {displayData.details?.codeBlocksFound && ( +
+ Code Blocks: + + {displayData.details.codeBlocksFound} + +
+ )} +
+ )} + {/* Error Message */} - {progressData.status === 'error' && progressData.error && ( -
-

- {progressData.error} + {displayData.error && ( +

+

+ {displayData.error}

)} - + {/* Console Logs */} - {progressData.logs && progressData.logs.length > 0 && ( + {displayData.logs && displayData.logs.length > 0 && (
@@ -884,10 +722,10 @@ export const CrawlingProgressCard: React.FC = ({ >
- {progressData.logs.map((log, index) => ( + {displayData.logs.map((log, index) => (
{log}
@@ -899,28 +737,19 @@ export const CrawlingProgressCard: React.FC = ({
)} - + {/* Action Buttons */} - {(progressData.status === 'error' || progressData.status === 'cancelled' || progressData.status === 'stale') && (onRetry || onDismiss) && ( + {(displayData.status === 'error' || displayData.status === 'failed' || displayData.status === 'cancelled') && (
{onDismiss && ( - )} - {onRetry && progressData.status !== 'stale' && ( - )} @@ -928,4 +757,4 @@ export const CrawlingProgressCard: React.FC = ({ )} ); -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/archon-ui-main/src/components/knowledge-base/CrawlingTab.tsx b/archon-ui-main/src/components/knowledge-base/CrawlingTab.tsx new file mode 100644 index 00000000..4bd498d3 --- /dev/null +++ b/archon-ui-main/src/components/knowledge-base/CrawlingTab.tsx @@ -0,0 +1,112 @@ +import { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { CrawlingProgressCard } from './CrawlingProgressCard'; +import { CrawlProgressData } from '../../types/crawl'; +import { AlertCircle } from 'lucide-react'; + +interface CrawlingTabProps { + progressItems: CrawlProgressData[]; + onProgressComplete: (data: CrawlProgressData) => void; + onProgressError: (error: string, progressId?: string) => void; + onRetryProgress: (progressId: string) => void; + onStopProgress: (progressId: string) => void; + onDismissProgress: (progressId: string) => void; +} + +export const CrawlingTab = ({ + progressItems, + onProgressComplete, + onProgressError, + onRetryProgress, + onStopProgress, + onDismissProgress +}: CrawlingTabProps) => { + // Group progress items by type for better organization + const groupedItems = progressItems.reduce((acc, item) => { + const type = item.crawlType || (item.uploadType === 'document' ? 'upload' : 'normal'); + if (!acc[type]) acc[type] = []; + acc[type].push(item); + return acc; + }, {} as Record); + + const getSectionTitle = (type: string) => { + switch (type) { + case 'sitemap': return 'Sitemap Crawls'; + case 'llms-txt': return 'LLMs.txt Crawls'; + case 'upload': return 'Document Uploads'; + case 'refresh': return 'Refreshing Sources'; + default: return 'Web Crawls'; + } + }; + + const getSectionDescription = (type: string) => { + switch (type) { + case 'sitemap': + return 'Processing sitemap.xml files to discover and crawl all listed pages'; + case 'llms-txt': + return 'Extracting content from llms.txt files for AI model training'; + case 'upload': + return 'Processing uploaded documents and extracting content'; + case 'refresh': + return 'Re-crawling existing sources to update content'; + default: + return 'Recursively crawling websites to extract knowledge'; + } + }; + + if (progressItems.length === 0) { + return ( +
+ +

+ No Active Crawls +

+

+ Start crawling a website or uploading a document to see progress here +

+
+ ); + } + + return ( +
+ + {Object.entries(groupedItems).map(([type, items]) => ( + + {/* Section Header */} +
+

+ {getSectionTitle(type)} +

+

+ {getSectionDescription(type)} +

+
+ + {/* Progress Cards */} +
+ {items.map((progressData) => ( + onProgressError(error, progressData.progressId)} + onRetry={() => onRetryProgress(progressData.progressId)} + onDismiss={() => onDismissProgress(progressData.progressId)} + onStop={() => onStopProgress(progressData.progressId)} + /> + ))} +
+
+ ))} +
+
+ ); +}; \ No newline at end of file diff --git a/archon-ui-main/src/components/knowledge-base/DocumentBrowser.tsx b/archon-ui-main/src/components/knowledge-base/DocumentBrowser.tsx new file mode 100644 index 00000000..4373cc0b --- /dev/null +++ b/archon-ui-main/src/components/knowledge-base/DocumentBrowser.tsx @@ -0,0 +1,319 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { createPortal } from 'react-dom'; +import { Search, Filter, FileText, Globe, X } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Badge } from '../ui/Badge'; +import { Button } from '../ui/Button'; +import { knowledgeBaseService } from '../../services/knowledgeBaseService'; + +interface DocumentChunk { + id: string; + source_id: string; + content: string; + metadata?: any; + url?: string; +} + +interface DocumentBrowserProps { + sourceId: string; + isOpen: boolean; + onClose: () => void; +} + +const extractDomain = (url: string): string => { + try { + const urlObj = new URL(url); + const hostname = urlObj.hostname; + + // Remove 'www.' prefix if present + const withoutWww = hostname.startsWith('www.') ? hostname.slice(4) : hostname; + + // Keep full hostname (minus 'www.') to preserve subdomain-level filtering + return withoutWww; + } catch { + return url; // Return original if URL parsing fails + } +}; + +export const DocumentBrowser: React.FC = ({ + sourceId, + isOpen, + onClose, +}) => { + const [chunks, setChunks] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedDomain, setSelectedDomain] = useState('all'); + const [selectedChunkId, setSelectedChunkId] = useState(null); + const [error, setError] = useState(null); + + // Extract unique domains from chunks + const domains = useMemo(() => { + const domainSet = new Set(); + chunks.forEach(chunk => { + if (chunk.url) { + domainSet.add(extractDomain(chunk.url)); + } + }); + return Array.from(domainSet).sort(); + }, [chunks]); + + // Filter chunks based on search and domain + const filteredChunks = useMemo(() => { + return chunks.filter(chunk => { + // Search filter + const searchLower = searchQuery.toLowerCase(); + const searchMatch = !searchQuery || + chunk.content.toLowerCase().includes(searchLower) || + chunk.url?.toLowerCase().includes(searchLower); + + // Domain filter + const domainMatch = selectedDomain === 'all' || + (chunk.url && extractDomain(chunk.url) === selectedDomain); + + return searchMatch && domainMatch; + }); + }, [chunks, searchQuery, selectedDomain]); + + // Get selected chunk + const selectedChunk = useMemo(() => { + return filteredChunks.find(chunk => chunk.id === selectedChunkId) || filteredChunks[0]; + }, [filteredChunks, selectedChunkId]); + + // Load chunks when component opens + useEffect(() => { + if (isOpen && sourceId) { + loadChunks(); + } + }, [isOpen, sourceId]); + + const loadChunks = async () => { + try { + setLoading(true); + setError(null); + + const response = await knowledgeBaseService.getKnowledgeItemChunks(sourceId); + + if (response.success) { + setChunks(response.chunks); + // Auto-select first chunk if none selected + if (response.chunks.length > 0 && !selectedChunkId) { + setSelectedChunkId(response.chunks[0].id); + } + } else { + setError('Failed to load document chunks'); + } + } catch (error) { + console.error('Failed to load chunks:', error); + setError(error instanceof Error ? error.message : 'Failed to load document chunks'); + } finally { + setLoading(false); + } + }; + + const loadChunksWithDomainFilter = async (domain: string) => { + try { + setLoading(true); + setError(null); + + const domainFilter = domain === 'all' ? undefined : domain; + const response = await knowledgeBaseService.getKnowledgeItemChunks(sourceId, domainFilter); + + if (response.success) { + setChunks(response.chunks); + } else { + setError('Failed to load document chunks'); + } + } catch (error) { + console.error('Failed to load chunks with domain filter:', error); + setError(error instanceof Error ? error.message : 'Failed to load document chunks'); + } finally { + setLoading(false); + } + }; + + const handleDomainChange = (domain: string) => { + setSelectedDomain(domain); + // Note: We could reload with server-side filtering, but for now we'll do client-side filtering + // loadChunksWithDomainFilter(domain); + }; + + if (!isOpen) return null; + + return createPortal( + + e.stopPropagation()} + > + {/* Blue accent line at the top */} +
+ + {/* Sidebar */} +
+ {/* Sidebar Header */} +
+
+

+ Document Chunks ({(filteredChunks || []).length}) +

+
+ + {/* Search */} +
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-3 py-2 bg-gray-900/70 border border-gray-800 rounded-lg text-sm text-gray-300 placeholder-gray-600 focus:outline-none focus:border-blue-500/50 focus:ring-1 focus:ring-blue-500/20 transition-all" + /> +
+ + {/* Domain Filter */} +
+ + +
+
+ + {/* Document List */} +
+ {filteredChunks.length === 0 ? ( +
+ No documents found +
+ ) : ( + filteredChunks.map((chunk, index) => ( + + )) + )} +
+
+ + {/* Main Content Area */} +
+ {/* Header */} +
+
+

+ {selectedChunk ? `Document Chunk` : 'Document Browser'} +

+ {selectedChunk?.url && ( + + + {extractDomain(selectedChunk.url)} + + )} +
+ +
+ + {/* Content */} +
+ {loading ? ( +
+
+
+

Loading document chunks...

+
+
+ ) : !selectedChunk || filteredChunks.length === 0 ? ( +
+
+ +

Select a document chunk to view content

+
+
+ ) : ( +
+
+
+ {selectedChunk.url && ( +
+ {selectedChunk.url} +
+ )} + +
+
+ {selectedChunk.content || 'No content available'} +
+
+ + {selectedChunk.metadata && ( +
+
+ + View Metadata + +
+                            {JSON.stringify(selectedChunk.metadata, null, 2)}
+                          
+
+
+ )} +
+
+
+ )} +
+
+
+
, + document.body + ); +}; \ No newline at end of file diff --git a/archon-ui-main/src/components/knowledge-base/KnowledgeItemCard.tsx b/archon-ui-main/src/components/knowledge-base/KnowledgeItemCard.tsx index ede71170..0c6589de 100644 --- a/archon-ui-main/src/components/knowledge-base/KnowledgeItemCard.tsx +++ b/archon-ui-main/src/components/knowledge-base/KnowledgeItemCard.tsx @@ -129,6 +129,7 @@ interface KnowledgeItemCardProps { onDelete: (sourceId: string) => void; onUpdate?: () => void; onRefresh?: (sourceId: string) => void; + onBrowseDocuments?: (sourceId: string) => void; isSelectionMode?: boolean; isSelected?: boolean; onToggleSelection?: (event: React.MouseEvent) => void; @@ -139,6 +140,7 @@ export const KnowledgeItemCard = ({ onDelete, onUpdate, onRefresh, + onBrowseDocuments, isSelectionMode = false, isSelected = false, onToggleSelection @@ -151,6 +153,7 @@ export const KnowledgeItemCard = ({ const [showEditModal, setShowEditModal] = useState(false); const [loadedCodeExamples, setLoadedCodeExamples] = useState(null); const [isLoadingCodeExamples, setIsLoadingCodeExamples] = useState(false); + const [isRecrawling, setIsRecrawling] = useState(false); const statusColorMap = { active: 'green', @@ -210,8 +213,14 @@ export const KnowledgeItemCard = ({ }; const handleRefresh = () => { - if (onRefresh) { + if (onRefresh && !isRecrawling) { + setIsRecrawling(true); onRefresh(item.source_id); + // Temporary fix: Auto-reset after timeout + // TODO: Reset based on actual crawl completion status from polling + setTimeout(() => { + setIsRecrawling(false); + }, 60000); // Reset after 60 seconds as a fallback } }; @@ -369,15 +378,18 @@ export const KnowledgeItemCard = ({ {item.metadata.source_type === 'url' && ( )} @@ -444,13 +456,20 @@ export const KnowledgeItemCard = ({
)} - {/* Page count - orange neon container */} + {/* Page count - orange neon container (clickable for document browser) */}
{ + e.stopPropagation(); + if (onBrowseDocuments) { + onBrowseDocuments(item.source_id); + } + }} onMouseEnter={() => setShowPageTooltip(true)} onMouseLeave={() => setShowPageTooltip(false)} + title="Click to browse document chunks" > -
+
{Math.ceil( @@ -461,10 +480,13 @@ export const KnowledgeItemCard = ({ {/* Page count tooltip - positioned relative to the badge */} {showPageTooltip && (
-
- {(item.metadata.word_count || 0).toLocaleString()} words +
+ Click to Browse Documents
+
+ {(item.metadata.word_count || 0).toLocaleString()} words +
= {Math.ceil((item.metadata.word_count || 0) / 250).toLocaleString()} pages
diff --git a/archon-ui-main/src/components/layout/MainLayout.tsx b/archon-ui-main/src/components/layout/MainLayout.tsx new file mode 100644 index 00000000..da0b2696 --- /dev/null +++ b/archon-ui-main/src/components/layout/MainLayout.tsx @@ -0,0 +1,193 @@ +import { AlertCircle, WifiOff } from "lucide-react"; +import type React from "react"; +import { useEffect } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import { useToast } from "../../features/ui/hooks/useToast"; +import { cn } from "../../lib/utils"; +import { credentialsService } from "../../services/credentialsService"; +import { isLmConfigured } from "../../utils/onboarding"; + +// TEMPORARY: Import from old components until they're migrated to features +import { BackendStartupError } from "../BackendStartupError"; +import { useBackendHealth } from "./hooks/useBackendHealth"; +import { Navigation } from "./Navigation"; + +interface MainLayoutProps { + children: React.ReactNode; + className?: string; +} + +interface BackendStatusProps { + isHealthLoading: boolean; + isBackendError: boolean; + healthData: { ready: boolean } | undefined; +} + +/** + * Backend health indicator component + */ +function BackendStatus({ isHealthLoading, isBackendError, healthData }: BackendStatusProps) { + if (isHealthLoading) { + return ( +
+
+ Connecting... +
+ ); + } + + if (isBackendError) { + return ( +
+ + Backend Offline +
+ ); + } + + if (healthData?.ready === false) { + return ( +
+ + Backend Starting... +
+ ); + } + + return null; +} + +/** + * Modern main layout using TanStack Query and Radix UI patterns + * Uses CSS Grid for layout instead of fixed positioning + */ +export function MainLayout({ children, className }: MainLayoutProps) { + const navigate = useNavigate(); + const location = useLocation(); + const { showToast } = useToast(); + + // Backend health monitoring with TanStack Query + const { + data: healthData, + isError: isBackendError, + error: backendError, + isLoading: isHealthLoading, + failureCount, + } = useBackendHealth(); + + // Track if backend has completely failed (for showing BackendStartupError) + const backendStartupFailed = isBackendError && failureCount >= 5; + + // TEMPORARY: Handle onboarding redirect using old logic until migrated + useEffect(() => { + const checkOnboarding = async () => { + // Skip if backend failed to start + if (backendStartupFailed) { + return; + } + + // Skip if not ready, already on onboarding, or already dismissed + if (!healthData?.ready || location.pathname === "/onboarding") { + return; + } + + // Check if onboarding was already dismissed + if (localStorage.getItem("onboardingDismissed") === "true") { + return; + } + + try { + // Fetch credentials in parallel (using old service temporarily) + const [ragCreds, apiKeyCreds] = await Promise.all([ + credentialsService.getCredentialsByCategory("rag_strategy"), + credentialsService.getCredentialsByCategory("api_keys"), + ]); + + // Check if LM is configured (using old utility temporarily) + const configured = isLmConfigured(ragCreds, apiKeyCreds); + + if (!configured) { + // Redirect to onboarding + navigate("/onboarding", { replace: true }); + } + } catch (error) { + // Log error but don't block app + console.error("ONBOARDING_CHECK_FAILED:", error); + showToast(`Configuration check failed. You can manually configure in Settings.`, "warning"); + } + }; + + checkOnboarding(); + }, [healthData?.ready, backendStartupFailed, location.pathname, navigate, showToast]); + + // Show backend error toast (once) + useEffect(() => { + if (isBackendError && backendError) { + const errorMessage = backendError instanceof Error ? backendError.message : "Backend connection failed"; + showToast(`Backend unavailable: ${errorMessage}. Some features may not work.`, "error"); + } + }, [isBackendError, backendError, showToast]); + + return ( +
+ {/* TEMPORARY: Show backend startup error using old component */} + {backendStartupFailed && } + + {/* Fixed full-page background grid that doesn't scroll */} +
+ + {/* Floating Navigation */} +
+ + +
+ + {/* Main Content Area - matches old layout exactly */} +
+
+
{children}
+
+
+ + {/* TEMPORARY: Floating Chat Button (disabled) - from old layout */} +
+ + {/* Tooltip */} +
+
Coming Soon
+
Knowledge Assistant is under development
+
+
+
+
+ ); +} + +/** + * Layout variant without navigation for special pages + */ +export function MinimalLayout({ children, className }: MainLayoutProps) { + return ( +
+ {/* Background Grid Effect */} +
+ + {/* Centered Content */} +
{children}
+
+ ); +} diff --git a/archon-ui-main/src/components/layout/Navigation.tsx b/archon-ui-main/src/components/layout/Navigation.tsx new file mode 100644 index 00000000..e2f1e806 --- /dev/null +++ b/archon-ui-main/src/components/layout/Navigation.tsx @@ -0,0 +1,178 @@ +import { BookOpen, Settings } from "lucide-react"; +import type React from "react"; +import { Link, useLocation } from "react-router-dom"; +// TEMPORARY: Use old SettingsContext until settings are migrated +import { useSettings } from "../../contexts/SettingsContext"; +import { glassmorphism } from "../../features/ui/primitives/styles"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../../features/ui/primitives/tooltip"; +import { cn } from "../../lib/utils"; + +interface NavigationItem { + path: string; + icon: React.ReactNode; + label: string; + enabled?: boolean; +} + +interface NavigationProps { + className?: string; +} + +/** + * Modern navigation component using Radix UI patterns + * No fixed positioning - parent controls layout + */ +export function Navigation({ className }: NavigationProps) { + const location = useLocation(); + const { projectsEnabled } = useSettings(); + + // Navigation items configuration + const navigationItems: NavigationItem[] = [ + { + path: "/", + icon: , + label: "Knowledge Base", + enabled: true, + }, + { + path: "/mcp", + icon: ( + + + + + ), + label: "MCP Server", + enabled: true, + }, + { + path: "/settings", + icon: , + label: "Settings", + enabled: true, + }, + ]; + + const isProjectsActive = location.pathname.startsWith("/projects"); + + return ( + + ); +} diff --git a/archon-ui-main/src/components/layout/hooks/useBackendHealth.ts b/archon-ui-main/src/components/layout/hooks/useBackendHealth.ts new file mode 100644 index 00000000..91bb7fd3 --- /dev/null +++ b/archon-ui-main/src/components/layout/hooks/useBackendHealth.ts @@ -0,0 +1,47 @@ +import { useQuery } from "@tanstack/react-query"; +import { callAPIWithETag } from "../../../features/projects/shared/apiWithEtag"; +import type { HealthResponse } from "../types"; + +/** + * Hook to monitor backend health status using TanStack Query + * Uses ETag caching for bandwidth reduction (~70% savings per project docs) + */ +export function useBackendHealth() { + return useQuery({ + queryKey: ["backend", "health"], + queryFn: ({ signal }) => { + // Use existing ETag infrastructure with timeout + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + // Chain signals: React Query's signal + our timeout + if (signal) { + signal.addEventListener('abort', () => controller.abort()); + } + + return callAPIWithETag("/api/health", { + signal: controller.signal, + }).finally(() => { + clearTimeout(timeoutId); + }); + }, + // Retry configuration for startup scenarios + retry: (failureCount) => { + // Keep retrying during startup, up to 5 times + if (failureCount < 5) { + return true; + } + return false; + }, + retryDelay: (attemptIndex) => { + // Exponential backoff: 1.5s, 2.25s, 3.375s, etc. + return Math.min(1500 * 1.5 ** attemptIndex, 10000); + }, + // Refetch every 30 seconds when healthy + refetchInterval: 30000, + // Keep trying to connect on window focus + refetchOnWindowFocus: true, + // Consider data fresh for 20 seconds + staleTime: 20000, + }); +} diff --git a/archon-ui-main/src/components/layout/index.ts b/archon-ui-main/src/components/layout/index.ts new file mode 100644 index 00000000..10736d9b --- /dev/null +++ b/archon-ui-main/src/components/layout/index.ts @@ -0,0 +1,3 @@ +export { useBackendHealth } from "./hooks/useBackendHealth"; +export { MainLayout, MinimalLayout } from "./MainLayout"; +export { Navigation } from "./Navigation"; diff --git a/archon-ui-main/src/components/layout/types.ts b/archon-ui-main/src/components/layout/types.ts new file mode 100644 index 00000000..76ecf973 --- /dev/null +++ b/archon-ui-main/src/components/layout/types.ts @@ -0,0 +1,28 @@ +import type React from "react"; + +export interface NavigationItem { + path: string; + icon: React.ReactNode; + label: string; + enabled?: boolean; +} + +export interface HealthResponse { + ready: boolean; + message?: string; + server_status?: string; + credentials_status?: string; + database_status?: string; + uptime?: number; +} + +export interface AppSettings { + projectsEnabled: boolean; + theme?: "light" | "dark" | "system"; + // Add other settings as needed +} + +export interface OnboardingCheckResult { + shouldShowOnboarding: boolean; + reason: "dismissed" | "missing_rag" | "missing_api_key" | null; +} diff --git a/archon-ui-main/src/components/layouts/MainLayout.tsx b/archon-ui-main/src/components/layouts/MainLayout.tsx deleted file mode 100644 index acc9188a..00000000 --- a/archon-ui-main/src/components/layouts/MainLayout.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { SideNavigation } from './SideNavigation'; -import { ArchonChatPanel } from './ArchonChatPanel'; -import { X } from 'lucide-react'; -import { useToast } from '../../contexts/ToastContext'; -import { credentialsService } from '../../services/credentialsService'; -import { isLmConfigured } from '../../utils/onboarding'; -import { BackendStartupError } from '../BackendStartupError'; -/** - * Props for the MainLayout component - */ -interface MainLayoutProps { - children: React.ReactNode; -} -/** - * MainLayout - The main layout component for the application - * - * This component provides the overall layout structure including: - * - Side navigation - * - Main content area - * - Knowledge chat panel (slidable) - */ -export const MainLayout: React.FC = ({ - children -}) => { - // State to track if chat panel is open - const [isChatOpen, setIsChatOpen] = useState(false); - const { showToast } = useToast(); - const navigate = useNavigate(); - const location = useLocation(); - const [backendReady, setBackendReady] = useState(false); - const [backendStartupFailed, setBackendStartupFailed] = useState(false); - - // Check backend readiness - useEffect(() => { - - const checkBackendHealth = async (retryCount = 0) => { - const maxRetries = 3; // 3 retries total - const retryDelay = 1500; // 1.5 seconds between retries - - try { - // Create AbortController for proper timeout handling - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 5000); - - // Check if backend is responding with a simple health check - const response = await fetch(`${credentialsService['baseUrl']}/api/health`, { - method: 'GET', - signal: controller.signal - }); - - clearTimeout(timeoutId); - - if (response.ok) { - const healthData = await response.json(); - console.log('📋 Backend health check:', healthData); - - // Check if backend is truly ready (not just started) - if (healthData.ready === true) { - console.log('✅ Backend is fully initialized'); - setBackendReady(true); - setBackendStartupFailed(false); - } else { - // Backend is starting up but not ready yet - console.log(`🔄 Backend initializing... (attempt ${retryCount + 1}/${maxRetries}):`, healthData.message || 'Loading credentials...'); - - // Retry with shorter interval during initialization - if (retryCount < maxRetries) { - setTimeout(() => { - checkBackendHealth(retryCount + 1); - }, retryDelay); // Constant 1.5s retry during initialization - } else { - console.warn('Backend initialization taking too long - proceeding anyway'); - // Don't mark as failed yet, just not fully ready - setBackendReady(false); - } - } - } else { - throw new Error(`Backend health check failed: ${response.status}`); - } - } catch (error) { - // Handle AbortError separately for timeout - const errorMessage = error instanceof Error - ? (error.name === 'AbortError' ? 'Request timeout (5s)' : error.message) - : 'Unknown error'; - // Only log after first attempt to reduce noise during normal startup - if (retryCount > 0) { - console.log(`Backend not ready yet (attempt ${retryCount + 1}/${maxRetries}):`, errorMessage); - } - - // Retry if we haven't exceeded max retries - if (retryCount < maxRetries) { - setTimeout(() => { - checkBackendHealth(retryCount + 1); - }, retryDelay * Math.pow(1.5, retryCount)); // Exponential backoff for connection errors - } else { - console.error('Backend startup failed after maximum retries - showing error message'); - setBackendReady(false); - setBackendStartupFailed(true); - } - } - }; - - - // Start the health check process - setTimeout(() => { - checkBackendHealth(); - }, 1000); // Wait 1 second for initial app startup - }, []); // Empty deps - only run once on mount - - // Check for onboarding redirect after backend is ready - useEffect(() => { - const checkOnboarding = async () => { - // Skip if backend failed to start - if (backendStartupFailed) { - return; - } - - // Skip if not ready, already on onboarding, or already dismissed - if (!backendReady || location.pathname === '/onboarding') { - return; - } - - // Check if onboarding was already dismissed - if (localStorage.getItem('onboardingDismissed') === 'true') { - return; - } - - try { - // Fetch credentials in parallel - const [ragCreds, apiKeyCreds] = await Promise.all([ - credentialsService.getCredentialsByCategory('rag_strategy'), - credentialsService.getCredentialsByCategory('api_keys') - ]); - - // Check if LM is configured - const configured = isLmConfigured(ragCreds, apiKeyCreds); - - if (!configured) { - // Redirect to onboarding - navigate('/onboarding', { replace: true }); - } - } catch (error) { - // Detailed error handling per alpha principles - fail loud but don't block - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - const errorDetails = { - context: 'Onboarding configuration check', - pathname: location.pathname, - error: errorMessage, - timestamp: new Date().toISOString() - }; - - // Log with full context and stack trace - console.error('ONBOARDING_CHECK_FAILED:', errorDetails, error); - - // Make error visible to user but don't block app functionality - showToast( - `Configuration check failed: ${errorMessage}. You can manually configure in Settings.`, - 'warning' - ); - - // Let user continue - onboarding is optional, they can configure manually - } - }; - - checkOnboarding(); - }, [backendReady, backendStartupFailed, location.pathname, navigate, showToast]); - - return
- {/* Show backend startup error if backend failed to start */} - {backendStartupFailed && } - - {/* Fixed full-page background grid that doesn't scroll */} -
- {/* Floating Navigation */} -
- -
- {/* Main Content Area - no left margin to allow grid to extend full width */} -
-
-
{children}
-
-
- {/* Floating Chat Button - Only visible when chat is closed */} - {!isChatOpen && ( -
- - {/* Tooltip */} -
-
Coming Soon
-
Knowledge Assistant is under development
-
-
-
- )} - {/* Chat Sidebar - Slides in/out from right */} -
- {/* Close button - Only visible when chat is open */} - {isChatOpen && } - {/* Knowledge Chat Panel */} - -
-
; -}; diff --git a/archon-ui-main/src/components/layouts/SideNavigation.tsx b/archon-ui-main/src/components/layouts/SideNavigation.tsx deleted file mode 100644 index f4165032..00000000 --- a/archon-ui-main/src/components/layouts/SideNavigation.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import React, { useState } from 'react'; -import { Link, useLocation } from 'react-router-dom'; -import { BookOpen, HardDrive, Settings } from 'lucide-react'; -import { useSettings } from '../../contexts/SettingsContext'; -/** - * Interface for navigation items - */ -export interface NavigationItem { - path: string; - icon: React.ReactNode; - label: string; -} -/** - * Props for the SideNavigation component - */ -interface SideNavigationProps { - className?: string; - 'data-id'?: string; -} -/** - * Tooltip component for navigation items - */ -const NavTooltip: React.FC<{ - show: boolean; - label: string; - position?: 'left' | 'right'; -}> = ({ - show, - label, - position = 'right' -}) => { - if (!show) return null; - return
- {label} -
-
; -}; -/** - * SideNavigation - A vertical navigation component - * - * This component renders a navigation sidebar with icons and the application logo. - * It highlights the active route and provides hover effects. - */ -export const SideNavigation: React.FC = ({ - className = '', - 'data-id': dataId -}) => { - // State to track which tooltip is currently visible - const [activeTooltip, setActiveTooltip] = useState(null); - const { projectsEnabled } = useSettings(); - - // Default navigation items - const navigationItems: NavigationItem[] = [{ - path: '/', - icon: , - label: 'Knowledge Base' - }, { - path: '/mcp', - icon: , - label: 'MCP Server' - }, { - path: '/settings', - icon: , - label: 'Settings' - }]; - // Logo configuration - const logoSrc = "/logo-neon.png"; - const logoAlt = 'Knowledge Base Logo'; - // Get current location to determine active route - const location = useLocation(); - const isProjectsActive = location.pathname === '/projects' && projectsEnabled; - - const logoClassName = ` - logo-container p-2 relative rounded-lg transition-all duration-300 - ${isProjectsActive ? 'bg-gradient-to-b from-white/20 to-white/5 dark:from-white/10 dark:to-black/20 shadow-[0_5px_15px_-5px_rgba(59,130,246,0.3)] dark:shadow-[0_5px_15px_-5px_rgba(59,130,246,0.5)] transform scale-110' : ''} - ${projectsEnabled ? 'hover:bg-white/10 dark:hover:bg-white/5 cursor-pointer' : 'opacity-50 cursor-not-allowed'} - `; - - return
- {/* Logo - Conditionally clickable based on Projects enabled */} - {projectsEnabled ? ( - setActiveTooltip('logo')} - onMouseLeave={() => setActiveTooltip(null)} - > - {logoAlt} - {/* Active state decorations */} - {isProjectsActive && <> - - - } - - - ) : ( -
setActiveTooltip('logo')} - onMouseLeave={() => setActiveTooltip(null)} - > - {logoAlt} - -
- )} - {/* Navigation links */} - -
; -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/mcp/ClientCard.tsx b/archon-ui-main/src/components/mcp/ClientCard.tsx deleted file mode 100644 index 6ddbd059..00000000 --- a/archon-ui-main/src/components/mcp/ClientCard.tsx +++ /dev/null @@ -1,508 +0,0 @@ -import React, { useEffect, useState, useRef } from 'react'; -import { Server, Activity, Clock, ChevronRight, Hammer, Settings, Trash2, Plug, PlugZap } from 'lucide-react'; -import { Client } from './MCPClients'; -import { mcpClientService } from '../../services/mcpClientService'; -import { useToast } from '../../contexts/ToastContext'; - -interface ClientCardProps { - client: Client; - onSelect: () => void; - onEdit?: (client: Client) => void; - onDelete?: (client: Client) => void; - onConnectionChange?: () => void; -} - -export const ClientCard = ({ - client, - onSelect, - onEdit, - onDelete, - onConnectionChange -}: ClientCardProps) => { - const [isFlipped, setIsFlipped] = useState(false); - const [isHovered, setIsHovered] = useState(false); - const [isConnecting, setIsConnecting] = useState(false); - const particlesRef = useRef(null); - const { showToast } = useToast(); - - // Special styling for Archon client - const isArchonClient = client.name.includes('Archon') || client.name.includes('archon'); - - // Status-based styling - const statusConfig = { - online: { - color: isArchonClient ? 'archon' : 'cyan', - glow: isArchonClient ? 'shadow-[0_0_25px_rgba(59,130,246,0.7),0_0_15px_rgba(168,85,247,0.5)] dark:shadow-[0_0_35px_rgba(59,130,246,0.8),0_0_20px_rgba(168,85,247,0.7)]' : 'shadow-[0_0_15px_rgba(34,211,238,0.5)] dark:shadow-[0_0_20px_rgba(34,211,238,0.7)]', - border: isArchonClient ? 'border-blue-400/60 dark:border-blue-500/60' : 'border-cyan-400/50 dark:border-cyan-500/40', - badge: isArchonClient ? 'bg-blue-500/30 text-blue-400 border-blue-500/40' : 'bg-cyan-500/20 text-cyan-400 border-cyan-500/30', - pulse: isArchonClient ? 'bg-blue-400' : 'bg-cyan-400' - }, - offline: { - color: 'gray', - glow: 'shadow-[0_0_15px_rgba(156,163,175,0.3)] dark:shadow-[0_0_15px_rgba(156,163,175,0.4)]', - border: 'border-gray-400/30 dark:border-gray-600/30', - badge: 'bg-gray-500/20 text-gray-400 border-gray-500/30', - pulse: 'bg-gray-400' - }, - error: { - color: 'pink', - glow: 'shadow-[0_0_15px_rgba(236,72,153,0.5)] dark:shadow-[0_0_20px_rgba(236,72,153,0.7)]', - border: 'border-pink-400/50 dark:border-pink-500/40', - badge: 'bg-pink-500/20 text-pink-400 border-pink-500/30', - pulse: 'bg-pink-400' - } - }; - - // Handle mouse movement for bioluminescent effect - useEffect(() => { - if (!isArchonClient || !particlesRef.current) return; - - const currentMousePos = { x: 0, y: 0 }; - const glowOrganisms: HTMLDivElement[] = []; - let isMousePresent = false; - - const createBioluminescentOrganism = (targetX: number, targetY: number, delay = 0) => { - const organism = document.createElement('div'); - organism.className = 'absolute rounded-full pointer-events-none'; - - const startX = targetX + (Math.random() - 0.5) * 100; - const startY = targetY + (Math.random() - 0.5) * 100; - const size = 8 + Math.random() * 12; - - organism.style.left = `${startX}px`; - organism.style.top = `${startY}px`; - organism.style.width = `${size}px`; - organism.style.height = `${size}px`; - organism.style.transform = 'translate(-50%, -50%)'; - organism.style.opacity = '0'; - - const hues = [180, 200, 220, 240, 260, 280]; - const hue = hues[Math.floor(Math.random() * hues.length)]; - - organism.style.background = 'transparent'; - - organism.style.boxShadow = ` - 0 0 ${size * 2}px hsla(${hue}, 90%, 60%, 0.4), - 0 0 ${size * 4}px hsla(${hue}, 80%, 50%, 0.25), - 0 0 ${size * 6}px hsla(${hue}, 70%, 40%, 0.15), - 0 0 ${size * 8}px hsla(${hue}, 60%, 30%, 0.08) - `; - - organism.style.filter = `blur(${2 + Math.random() * 3}px) opacity(0.6)`; - - particlesRef.current?.appendChild(organism); - - setTimeout(() => { - const duration = 1200 + Math.random() * 800; - - organism.style.transition = `all ${duration}ms cubic-bezier(0.2, 0.0, 0.1, 1)`; - organism.style.left = `${targetX + (Math.random() - 0.5) * 50}px`; - organism.style.top = `${targetY + (Math.random() - 0.5) * 50}px`; - organism.style.opacity = '0.8'; - organism.style.transform = 'translate(-50%, -50%) scale(1.2)'; - - setTimeout(() => { - if (!isMousePresent) { - organism.style.transition = `all 2500ms cubic-bezier(0.6, 0.0, 0.9, 1)`; - organism.style.left = `${startX + (Math.random() - 0.5) * 300}px`; - organism.style.top = `${startY + (Math.random() - 0.5) * 300}px`; - organism.style.opacity = '0'; - organism.style.transform = 'translate(-50%, -50%) scale(0.2)'; - organism.style.filter = `blur(${8 + Math.random() * 5}px) opacity(0.2)`; - } - }, duration + 800); - - setTimeout(() => { - if (particlesRef.current?.contains(organism)) { - particlesRef.current.removeChild(organism); - const index = glowOrganisms.indexOf(organism); - if (index > -1) glowOrganisms.splice(index, 1); - } - }, duration + 2000); - - }, delay); - - return organism; - }; - - const spawnOrganismsTowardMouse = () => { - if (!isMousePresent) return; - - const count = 3 + Math.random() * 4; - for (let i = 0; i < count; i++) { - const organism = createBioluminescentOrganism( - currentMousePos.x, - currentMousePos.y, - i * 100 - ); - glowOrganisms.push(organism); - } - }; - - const handleMouseEnter = () => { - isMousePresent = true; - clearInterval(ambientInterval); - ambientInterval = setInterval(createAmbientGlow, 1500); - }; - - const handleMouseMove = (e: MouseEvent) => { - if (!particlesRef.current) return; - - const rect = particlesRef.current.getBoundingClientRect(); - currentMousePos.x = e.clientX - rect.left; - currentMousePos.y = e.clientY - rect.top; - - isMousePresent = true; - - if (Math.random() < 0.4) { - spawnOrganismsTowardMouse(); - } - }; - - const handleMouseLeave = () => { - setTimeout(() => { - isMousePresent = false; - clearInterval(ambientInterval); - }, 800); - }; - - const createAmbientGlow = () => { - if (!particlesRef.current || isMousePresent) return; - - const x = Math.random() * particlesRef.current.clientWidth; - const y = Math.random() * particlesRef.current.clientHeight; - const organism = createBioluminescentOrganism(x, y); - - organism.style.opacity = '0.3'; - organism.style.filter = `blur(${4 + Math.random() * 4}px) opacity(0.4)`; - organism.style.animation = 'pulse 4s ease-in-out infinite'; - organism.style.transform = 'translate(-50%, -50%) scale(0.8)'; - - glowOrganisms.push(organism); - }; - - let ambientInterval = setInterval(createAmbientGlow, 1500); - - const cardElement = particlesRef.current; - cardElement.addEventListener('mouseenter', handleMouseEnter); - cardElement.addEventListener('mousemove', handleMouseMove); - cardElement.addEventListener('mouseleave', handleMouseLeave); - - return () => { - cardElement.removeEventListener('mouseenter', handleMouseEnter); - cardElement.removeEventListener('mousemove', handleMouseMove); - cardElement.removeEventListener('mouseleave', handleMouseLeave); - clearInterval(ambientInterval); - }; - }, [isArchonClient]); - - const currentStatus = statusConfig[client.status]; - - // Handle card flip - const toggleFlip = (e: React.MouseEvent) => { - e.stopPropagation(); - setIsFlipped(!isFlipped); - }; - - // Handle edit - const handleEdit = (e: React.MouseEvent) => { - e.stopPropagation(); - onEdit?.(client); - }; - - // Handle connect/disconnect - const handleConnect = async (e: React.MouseEvent) => { - e.stopPropagation(); - setIsConnecting(true); - - try { - if (client.status === 'offline') { - await mcpClientService.connectClient(client.id); - showToast(`Connected to ${client.name}`, 'success'); - } else { - await mcpClientService.disconnectClient(client.id); - showToast(`Disconnected from ${client.name}`, 'success'); - } - - // The parent component should handle refreshing the client list - // No need to reload the entire page - onConnectionChange?.(); - } catch (error) { - showToast(error instanceof Error ? error.message : 'Connection operation failed', 'error'); - } finally { - setIsConnecting(false); - } - }; - - // Special background for Archon client - const archonBackground = isArchonClient ? 'bg-gradient-to-b from-white/80 via-blue-50/30 to-white/60 dark:from-white/10 dark:via-blue-900/10 dark:to-black/30' : 'bg-gradient-to-b from-white/80 to-white/60 dark:from-white/10 dark:to-black/30'; - - return ( -
setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > -
- {/* Front Side */} -
- {/* Particle container for Archon client */} - {isArchonClient && ( -
-
-
- )} - - {/* Subtle aurora glow effect for Archon client */} - {isArchonClient && ( -
-
-
- )} - - {/* Connect/Disconnect button */} - - - {/* Edit button - moved to be second from right */} - {onEdit && ( - - )} - - {/* Delete button - only for non-Archon clients */} - {!isArchonClient && onDelete && ( - - )} - - {/* Client info */} -
- {isArchonClient ? ( -
- Archon -
-
- ) : ( -
- -
- )} - -
-

- {client.name} -

-

- {client.ip} -

-
-
- -
-
- - Last seen: - - {client.lastSeen} - -
-
- - Version: - - {client.version} - -
-
- - Tools: - - {client.tools.length} available - -
- - {/* Error message display */} - {client.status === 'error' && client.lastError && ( -
-
-
-
-

Last Error:

-

- {client.lastError} -

-
-
-
- )} -
- - {/* Status badge - moved to bottom left */} -
-
-
- - -
- {client.status.charAt(0).toUpperCase() + client.status.slice(1)} -
-
- - {/* Tools button - with Hammer icon */} - -
- - {/* Back Side */} -
- {/* Subtle aurora glow effect for Archon client */} - {isArchonClient && ( -
-
-
- )} - - {/* Connect/Disconnect button - also on back side */} - - - {/* Edit button - also on back side */} - {onEdit && ( - - )} - - {/* Delete button on back side - only for non-Archon clients */} - {!isArchonClient && onDelete && ( - - )} - -

- - Available Tools ({client.tools.length}) -

- -
- {client.tools.length === 0 ? ( -
-

- {client.status === 'offline' - ? 'Client offline - tools unavailable' - : 'No tools discovered'} -

-
- ) : ( - client.tools.map(tool => ( -
-
- - {tool.name} - - -
-

- {tool.description} -

- {tool.parameters.length > 0 && ( -

- {tool.parameters.length} parameter{tool.parameters.length !== 1 ? 's' : ''} -

- )} -
- )) - )} -
- - {/* Status badge - also at bottom left on back side */} -
-
-
- - -
- {client.status.charAt(0).toUpperCase() + client.status.slice(1)} -
-
- - {/* Flip button - back to front */} - -
-
-
- ); -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/mcp/MCPClients.tsx b/archon-ui-main/src/components/mcp/MCPClients.tsx deleted file mode 100644 index 328832d5..00000000 --- a/archon-ui-main/src/components/mcp/MCPClients.tsx +++ /dev/null @@ -1,858 +0,0 @@ -import React, { useState, memo, useEffect } from 'react'; -import { Plus, Settings, Trash2, X } from 'lucide-react'; -import { ClientCard } from './ClientCard'; -import { ToolTestingPanel } from './ToolTestingPanel'; -import { Button } from '../ui/Button'; -import { mcpClientService, MCPClient, MCPClientConfig } from '../../services/mcpClientService'; -import { useToast } from '../../contexts/ToastContext'; -import { DeleteConfirmModal } from '../../pages/ProjectPage'; - -// Client interface (keeping for backward compatibility) -export interface Client { - id: string; - name: string; - status: 'online' | 'offline' | 'error'; - ip: string; - lastSeen: string; - version: string; - tools: Tool[]; - region?: string; - lastError?: string; -} - -// Tool interface -export interface Tool { - id: string; - name: string; - description: string; - parameters: ToolParameter[]; -} - -// Tool parameter interface -export interface ToolParameter { - name: string; - type: 'string' | 'number' | 'boolean' | 'array'; - required: boolean; - description?: string; -} - -export const MCPClients = memo(() => { - const [clients, setClients] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - // State for selected client and panel visibility - const [selectedClient, setSelectedClient] = useState(null); - const [isPanelOpen, setIsPanelOpen] = useState(false); - const [isAddClientModalOpen, setIsAddClientModalOpen] = useState(false); - - // State for edit drawer - const [editClient, setEditClient] = useState(null); - const [isEditDrawerOpen, setIsEditDrawerOpen] = useState(false); - - const { showToast } = useToast(); - - // State for delete confirmation modal - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); - const [clientToDelete, setClientToDelete] = useState(null); - - // Load clients when component mounts - useEffect(() => { - loadAllClients(); - - // Set up periodic status checks every 10 seconds - const statusInterval = setInterval(() => { - // Silently refresh client statuses without loading state - refreshClientStatuses(); - }, 10000); - - return () => clearInterval(statusInterval); - }, []); - - /** - * Refresh client statuses without showing loading state - */ - const refreshClientStatuses = async () => { - try { - const dbClients = await mcpClientService.getClients(); - - setClients(prevClients => - prevClients.map(client => { - const dbClient = dbClients.find(db => db.id === client.id); - if (dbClient) { - return { - ...client, - status: dbClient.status === 'connected' ? 'online' : - dbClient.status === 'error' ? 'error' : 'offline', - lastSeen: dbClient.last_seen ? new Date(dbClient.last_seen).toLocaleString() : 'Never', - lastError: dbClient.last_error || undefined - }; - } - return client; - }) - ); - } catch (error) { - console.warn('Failed to refresh client statuses:', error); - } - }; - - /** - * Load all clients: Archon (hardcoded) + real database clients - */ - const loadAllClients = async () => { - try { - setIsLoading(true); - setError(null); - - // Load ALL clients from database (including Archon) - let dbClients: MCPClient[] = []; - try { - dbClients = await mcpClientService.getClients(); - } catch (clientError) { - console.warn('Failed to load database clients:', clientError); - dbClients = []; - } - - // Convert database clients to our Client interface and load their tools - const convertedClients: Client[] = await Promise.all( - dbClients.map(async (dbClient) => { - const client = convertDbClientToClient(dbClient); - // Load tools for connected clients using universal method - if (client.status === 'online') { - await loadTools(client); - } - return client; - }) - ); - - // Set all clients (Archon will be included as a regular client) - setClients(convertedClients); - } catch (error) { - console.error('Failed to load MCP clients:', error); - setError(error instanceof Error ? error.message : 'Failed to load clients'); - setClients([]); - } finally { - setIsLoading(false); - } - }; - - /** - * Convert database MCP client to our Client interface - */ - const convertDbClientToClient = (dbClient: MCPClient): Client => { - // Map database status to our status types - const statusMap: Record = { - 'connected': 'online', - 'disconnected': 'offline', - 'connecting': 'offline', - 'error': 'error' - }; - - // Extract connection info (Streamable HTTP-only) - const config = dbClient.connection_config; - const ip = config.url || 'N/A'; - - return { - id: dbClient.id, - name: dbClient.name, - status: statusMap[dbClient.status] || 'offline', - ip, - lastSeen: dbClient.last_seen ? new Date(dbClient.last_seen).toLocaleString() : 'Never', - version: config.version || 'Unknown', - region: config.region || 'Unknown', - tools: [], // Will be loaded separately - lastError: dbClient.last_error || undefined - }; - }; - - /** - * Load tools from any MCP client using universal client service - */ - const loadTools = async (client: Client) => { - try { - const toolsResponse = await mcpClientService.getClientTools(client.id); - - // Convert client tools to our Tool interface format - const convertedTools: Tool[] = toolsResponse.tools.map((clientTool: any, index: number) => { - const parameters: ToolParameter[] = []; - - // Extract parameters from tool schema - if (clientTool.tool_schema?.inputSchema?.properties) { - const required = clientTool.tool_schema.inputSchema.required || []; - Object.entries(clientTool.tool_schema.inputSchema.properties).forEach(([name, schema]: [string, any]) => { - parameters.push({ - name, - type: schema.type === 'integer' ? 'number' : - schema.type === 'array' ? 'array' : - schema.type === 'boolean' ? 'boolean' : 'string', - required: required.includes(name), - description: schema.description || `${name} parameter` - }); - }); - } - - return { - id: `${client.id}-${index}`, - name: clientTool.tool_name, - description: clientTool.tool_description || 'No description available', - parameters - }; - }); - - client.tools = convertedTools; - console.log(`Loaded ${convertedTools.length} tools for client ${client.name}`); - } catch (error) { - console.error(`Failed to load tools for client ${client.name}:`, error); - client.tools = []; - } - }; - - /** - * Handle adding a new client - */ - const handleAddClient = async (clientConfig: MCPClientConfig) => { - try { - // Create client in database - const newClient = await mcpClientService.createClient(clientConfig); - - // Convert and add to local state - const convertedClient = convertDbClientToClient(newClient); - - // Try to load tools if client is connected - if (convertedClient.status === 'online') { - await loadTools(convertedClient); - } - - setClients(prev => [...prev, convertedClient]); - - // Close modal - setIsAddClientModalOpen(false); - - console.log('Client added successfully:', newClient.name); - } catch (error) { - console.error('Failed to add client:', error); - setError(error instanceof Error ? error.message : 'Failed to add client'); - throw error; // Re-throw so modal can handle it - } - }; - - // Handle client selection - const handleSelectClient = async (client: Client) => { - setSelectedClient(client); - setIsPanelOpen(true); - - // Refresh tools for the selected client if needed - if (client.tools.length === 0 && client.status === 'online') { - await loadTools(client); - - // Update the client in the list - setClients(prev => prev.map(c => c.id === client.id ? client : c)); - } - }; - - // Handle client editing - const handleEditClient = (client: Client) => { - setEditClient(client); - setIsEditDrawerOpen(true); - }; - - // Handle client deletion (triggers confirmation modal) - const handleDeleteClient = (client: Client) => { - setClientToDelete(client); - setShowDeleteConfirm(true); - }; - - // Refresh clients list (for after connection state changes) - const refreshClients = async () => { - try { - const dbClients = await mcpClientService.getClients(); - const convertedClients = await Promise.all( - dbClients.map(async (dbClient) => { - const client = convertDbClientToClient(dbClient); - if (client.status === 'online') { - await loadTools(client); - } - return client; - }) - ); - setClients(convertedClients); - } catch (error) { - console.error('Failed to refresh clients:', error); - setError(error instanceof Error ? error.message : 'Failed to refresh clients'); - } - }; - - // Confirm deletion and execute - const confirmDeleteClient = async () => { - if (!clientToDelete) return; - - try { - await mcpClientService.deleteClient(clientToDelete.id); - setClients(prev => prev.filter(c => c.id !== clientToDelete.id)); - showToast(`MCP Client "${clientToDelete.name}" deleted successfully`, 'success'); - } catch (error) { - console.error('Failed to delete MCP client:', error); - showToast(error instanceof Error ? error.message : 'Failed to delete MCP client', 'error'); - } finally { - setShowDeleteConfirm(false); - setClientToDelete(null); - } - }; - - // Cancel deletion - const cancelDeleteClient = () => { - setShowDeleteConfirm(false); - setClientToDelete(null); - }; - - if (isLoading) { - return ( -
-
-
-

Loading MCP clients...

-
-
- ); - } - - return ( -
- {/* Error display */} - {error && ( -
-

{error}

- -
- )} - - {/* Add Client Button */} -
-
-

MCP Clients

-

- Connect and manage your MCP-enabled applications -

-
- -
- - {/* Client Grid */} -
-
- {clients.map(client => ( - handleSelectClient(client)} - onEdit={() => handleEditClient(client)} - onDelete={() => handleDeleteClient(client)} - onConnectionChange={refreshClients} - /> - ))} -
-
- - {/* Tool Testing Panel */} - setIsPanelOpen(false)} - /> - - {/* Add Client Modal */} - {isAddClientModalOpen && ( - setIsAddClientModalOpen(false)} - onSubmit={handleAddClient} - /> - )} - - {/* Edit Client Drawer */} - {isEditDrawerOpen && editClient && ( - { - setIsEditDrawerOpen(false); - setEditClient(null); - }} - onUpdate={(updatedClient) => { - // Update the client in state or remove if deleted - setClients(prev => { - if (!updatedClient) { // If updatedClient is null, it means deletion - return prev.filter(c => c.id !== editClient?.id); // Remove the client that was being edited - } - return prev.map(c => c.id === updatedClient.id ? updatedClient : c); - }); - setIsEditDrawerOpen(false); - setEditClient(null); - }} - /> - )} - - {/* Delete Confirmation Modal for Clients */} - {showDeleteConfirm && clientToDelete && ( - - )} -
- ); -}); - -// Add Client Modal Component -interface AddClientModalProps { - isOpen: boolean; - onClose: () => void; - onSubmit: (config: MCPClientConfig) => Promise; -} - -const AddClientModal: React.FC = ({ isOpen, onClose, onSubmit }) => { - const [formData, setFormData] = useState({ - name: '', - url: '', - auto_connect: true - }); - const [isSubmitting, setIsSubmitting] = useState(false); - const [error, setError] = useState(null); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!formData.name.trim()) { - setError('Client name is required'); - return; - } - - setIsSubmitting(true); - setError(null); - - try { - // Validate URL - if (!formData.url.trim()) { - setError('MCP server URL is required'); - setIsSubmitting(false); - return; - } - - // Ensure URL is valid - try { - const url = new URL(formData.url); - if (!url.protocol.startsWith('http')) { - setError('URL must start with http:// or https://'); - setIsSubmitting(false); - return; - } - } catch (e) { - setError('Invalid URL format'); - setIsSubmitting(false); - return; - } - - const connection_config = { - url: formData.url.trim() - }; - - const clientConfig: MCPClientConfig = { - name: formData.name.trim(), - transport_type: 'http', - connection_config, - auto_connect: formData.auto_connect - }; - - await onSubmit(clientConfig); - - // Reset form on success - setFormData({ - name: '', - url: '', - auto_connect: true - }); - setError(null); - } catch (error) { - setError(error instanceof Error ? error.message : 'Failed to add client'); - } finally { - setIsSubmitting(false); - } - }; - - if (!isOpen) return null; - - return ( -
-
-
- -

- Add New MCP Client -

- -
- {/* Client Name */} -
- - setFormData(prev => ({ ...prev, name: e.target.value }))} - className="w-full px-3 py-2 bg-white/50 dark:bg-black/50 border border-gray-300 dark:border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-cyan-500" - placeholder="Enter client name" - required - /> -
- - {/* MCP Server URL */} -
- - setFormData(prev => ({ ...prev, url: e.target.value }))} - className="w-full px-3 py-2 bg-white/50 dark:bg-black/50 border border-gray-300 dark:border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-cyan-500" - placeholder="http://host.docker.internal:8051/mcp" - required - /> -

- The HTTP endpoint URL of the MCP server -

-

- Docker Note: Use host.docker.internal instead of localhost - to access services running on your host machine -

-
- - {/* Auto Connect */} -
- setFormData(prev => ({ ...prev, auto_connect: e.target.checked }))} - className="mr-2" - /> - -
- - {/* Error message */} - {error && ( -
- {error} -
- )} - - {/* Buttons */} -
- - -
-
-
-
- ); -}; - -// Edit Client Drawer Component -interface EditClientDrawerProps { - client: Client; - isOpen: boolean; - onClose: () => void; - onUpdate: (client: Client | null) => void; // Allow null to indicate deletion -} - -const EditClientDrawer: React.FC = ({ client, isOpen, onClose, onUpdate }) => { - const [editFormData, setEditFormData] = useState({ - name: client.name, - url: '', - auto_connect: true - }); - const [isSubmitting, setIsSubmitting] = useState(false); - const [error, setError] = useState(null); - const [isConnecting, setIsConnecting] = useState(false); - - // State for delete confirmation modal (moved here) - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); - const [clientToDelete, setClientToDelete] = useState(null); - - const { showToast } = useToast(); // Initialize useToast here - - // Load current client config when drawer opens - useEffect(() => { - if (isOpen && client) { - // Get client config from the API and populate form - loadClientConfig(); - } - }, [isOpen, client.id]); - - const loadClientConfig = async () => { - try { - const dbClient = await mcpClientService.getClient(client.id); - const config = dbClient.connection_config; - - setEditFormData({ - name: dbClient.name, - url: config.url || '', - auto_connect: dbClient.auto_connect - }); - } catch (error) { - console.error('Failed to load client config:', error); - setError('Failed to load client configuration'); - } - }; - - const handleUpdateSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setIsSubmitting(true); - setError(null); - - try { - // Validate URL - if (!editFormData.url.trim()) { - setError('MCP server URL is required'); - setIsSubmitting(false); - return; - } - - // Ensure URL is valid - try { - const url = new URL(editFormData.url); - if (!url.protocol.startsWith('http')) { - setError('URL must start with http:// or https://'); - setIsSubmitting(false); - return; - } - } catch (e) { - setError('Invalid URL format'); - setIsSubmitting(false); - return; - } - - const connection_config = { - url: editFormData.url.trim() - }; - - // Update client via API - const updatedClient = await mcpClientService.updateClient(client.id, { - name: editFormData.name, - transport_type: 'http', - connection_config, - auto_connect: editFormData.auto_connect - }); - - // Update local state - const convertedClient = { - ...client, - name: updatedClient.name, - ip: editFormData.url - }; - - onUpdate(convertedClient); - onClose(); - } catch (error) { - setError(error instanceof Error ? error.message : 'Failed to update client'); - } finally { - setIsSubmitting(false); - } - }; - - const handleConnect = async () => { - setIsConnecting(true); - try { - await mcpClientService.connectClient(client.id); - // Reload the client to get updated status - loadClientConfig(); - } catch (error) { - setError(error instanceof Error ? error.message : 'Failed to connect'); - } finally { - setIsConnecting(false); - } - }; - - const handleDisconnect = async () => { - try { - await mcpClientService.disconnectClient(client.id); - // Reload the client to get updated status - loadClientConfig(); - } catch (error) { - setError(error instanceof Error ? error.message : 'Failed to disconnect'); - } - }; - - const handleDelete = async () => { - if (confirm(`Are you sure you want to delete "${client.name}"?`)) { - try { - await mcpClientService.deleteClient(client.id); - onClose(); - // Trigger a reload of the clients list - window.location.reload(); - } catch (error) { - setError(error instanceof Error ? error.message : 'Failed to delete client'); - } - } - }; - - if (!isOpen) return null; - - return ( -
-
e.stopPropagation()} - > -
- -

- - Edit Client Configuration -

- -
- {/* Client Name */} -
- - setEditFormData(prev => ({ ...prev, name: e.target.value }))} - className="w-full px-3 py-2 bg-white/50 dark:bg-black/50 border border-gray-300 dark:border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-cyan-500" - required - /> -
- - {/* MCP Server URL */} -
- - setEditFormData(prev => ({ ...prev, url: e.target.value }))} - className="w-full px-3 py-2 bg-white/50 dark:bg-black/50 border border-gray-300 dark:border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-cyan-500" - placeholder="http://host.docker.internal:8051/mcp" - required - /> -

- The HTTP endpoint URL of the MCP server -

-

- Docker Note: Use host.docker.internal instead of localhost - to access services running on your host machine -

-
- - {/* Auto Connect */} -
- setEditFormData(prev => ({ ...prev, auto_connect: e.target.checked }))} - className="mr-2" - /> - -
- - {/* Error message */} - {error && ( -
- {error} -
- )} - - {/* Action Buttons */} -
-

Quick Actions

-
- - - - -
-
- - {/* Form Buttons */} -
- - -
-
-
-
- ); -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/mcp/ToolTestingPanel.tsx b/archon-ui-main/src/components/mcp/ToolTestingPanel.tsx deleted file mode 100644 index 98a9699b..00000000 --- a/archon-ui-main/src/components/mcp/ToolTestingPanel.tsx +++ /dev/null @@ -1,598 +0,0 @@ -import React, { useEffect, useState, useRef } from 'react'; -import { X, Play, ChevronDown, TerminalSquare, Copy, Check, MinusCircle, Maximize2, Minimize2, Hammer, GripHorizontal } from 'lucide-react'; -import { Client, Tool } from './MCPClients'; -import { Button } from '../ui/Button'; -import { mcpClientService } from '../../services/mcpClientService'; -import { ErrorAlert, useErrorHandler } from '../ui/ErrorAlert'; -import { parseKnowledgeBaseError, EnhancedError } from '../../services/knowledgeBaseErrorHandler'; - -interface ToolTestingPanelProps { - client: Client | null; - isOpen: boolean; - onClose: () => void; -} - -interface TerminalLine { - id: string; - content: string; - isTyping: boolean; - isCommand: boolean; - isError?: boolean; - isWarning?: boolean; -} - -export const ToolTestingPanel = ({ - client, - isOpen, - onClose -}: ToolTestingPanelProps) => { - const [selectedTool, setSelectedTool] = useState(null); - const [terminalOutput, setTerminalOutput] = useState([{ - id: '1', - content: '> Tool testing terminal ready', - isTyping: false, - isCommand: true - }]); - const [paramValues, setParamValues] = useState>({}); - const [isCopied, setIsCopied] = useState(false); - const [panelHeight, setPanelHeight] = useState(400); - const [isResizing, setIsResizing] = useState(false); - const [isMaximized, setIsMaximized] = useState(false); - const [isExecuting, setIsExecuting] = useState(false); - const { error, setError, clearError } = useErrorHandler(); - const terminalRef = useRef(null); - const resizeHandleRef = useRef(null); - const panelRef = useRef(null); - const previousHeightRef = useRef(400); - - // Reset selected tool when client changes - useEffect(() => { - if (client && client.tools.length > 0) { - setSelectedTool(client.tools[0]); - setParamValues({}); - } else { - setSelectedTool(null); - setParamValues({}); - } - }, [client]); - - // Auto-scroll terminal to bottom when output changes - useEffect(() => { - if (terminalRef.current) { - terminalRef.current.scrollTop = terminalRef.current.scrollHeight; - } - }, [terminalOutput]); - - // Handle resizing functionality - useEffect(() => { - const handleMouseMove = (e: MouseEvent) => { - if (isResizing && panelRef.current) { - const containerHeight = window.innerHeight; - const mouseY = e.clientY; - const newHeight = containerHeight - mouseY; - if (newHeight >= 200 && newHeight <= containerHeight * 0.8) { - setPanelHeight(newHeight); - } - } - }; - - const handleMouseUp = () => { - setIsResizing(false); - document.body.style.cursor = 'default'; - document.body.style.userSelect = 'auto'; - }; - - if (isResizing) { - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - document.body.style.cursor = 'ns-resize'; - document.body.style.userSelect = 'none'; - } - - return () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - }, [isResizing]); - - // Cleanup error state on component unmount - useEffect(() => { - return () => { - clearError(); // Clear any pending errors when component unmounts - }; - }, [clearError]); - - // Handle tool selection - const handleToolSelect = (tool: Tool) => { - setSelectedTool(tool); - setParamValues({}); - }; - - // Handle parameter value change - const handleParamChange = (paramName: string, value: string) => { - setParamValues(prev => ({ - ...prev, - [paramName]: value - })); - }; - - // Simulate typing animation for terminal output - const addTypingLine = (content: string, isCommand: boolean = false, isError: boolean = false, isWarning: boolean = false) => { - const newLineId = Date.now().toString() + Math.random().toString(36).substring(2); - - setTerminalOutput(prev => [...prev, { - id: newLineId, - content: '', - isTyping: true, - isCommand, - isError, - isWarning - }]); - - // Simulate typing animation - let currentText = ''; - const textArray = content.split(''); - const typeInterval = setInterval(() => { - if (textArray.length > 0) { - currentText += textArray.shift(); - setTerminalOutput(prev => prev.map(line => - line.id === newLineId ? { - ...line, - content: currentText - } : line - )); - } else { - clearInterval(typeInterval); - setTerminalOutput(prev => prev.map(line => - line.id === newLineId ? { - ...line, - isTyping: false - } : line - )); - } - }, 15); // Faster typing - - return newLineId; - }; - - // Add instant line (no typing effect) - const addInstantLine = (content: string, isCommand: boolean = false, isError: boolean = false, isWarning: boolean = false) => { - const newLineId = Date.now().toString() + Math.random().toString(36).substring(2); - - setTerminalOutput(prev => [...prev, { - id: newLineId, - content, - isTyping: false, - isCommand, - isError, - isWarning - }]); - - return newLineId; - }; - - // Convert parameter values to proper types - const convertParameterValues = (): Record => { - if (!selectedTool) return {}; - - const convertedParams: Record = {}; - - selectedTool.parameters.forEach(param => { - const value = paramValues[param.name]; - - if (value !== undefined && value !== '') { - try { - switch (param.type) { - case 'number': - convertedParams[param.name] = Number(value); - if (isNaN(convertedParams[param.name])) { - throw new Error(`Invalid number: ${value}`); - } - break; - case 'boolean': - convertedParams[param.name] = value.toLowerCase() === 'true' || value === '1'; - break; - case 'array': - // Try to parse as JSON array first, fallback to comma-separated - try { - convertedParams[param.name] = JSON.parse(value); - if (!Array.isArray(convertedParams[param.name])) { - throw new Error('Not an array'); - } - } catch { - convertedParams[param.name] = value.split(',').map(v => v.trim()).filter(v => v); - } - break; - default: - convertedParams[param.name] = value; - } - } catch (error) { - console.warn(`Parameter conversion error for ${param.name}:`, error); - convertedParams[param.name] = value; // Fallback to string - } - } - }); - - return convertedParams; - }; - - - - // Execute tool using universal MCP client service (works for ALL clients) - const executeTool = async () => { - if (!selectedTool || !client) return; - - try { - const convertedParams = convertParameterValues(); - - addTypingLine(`> Connecting to ${client.name} via MCP protocol...`); - - // Call the client tool via MCP service - const result = await mcpClientService.callClientTool({ - client_id: client.id, - tool_name: selectedTool.name, - arguments: convertedParams - }); - - setTimeout(() => addTypingLine('> Tool executed successfully'), 300); - - // Display the result - setTimeout(() => { - if (result) { - let resultText = ''; - - if (typeof result === 'object') { - if (result.content) { - // Handle MCP content response - if (Array.isArray(result.content)) { - resultText = result.content.map((item: any) => - item.text || JSON.stringify(item, null, 2) - ).join('\n'); - } else { - resultText = result.content.text || JSON.stringify(result.content, null, 2); - } - } else { - resultText = JSON.stringify(result, null, 2); - } - } else { - resultText = String(result); - } - - addInstantLine('> Result:'); - addInstantLine(resultText); - } else { - addTypingLine('> No result returned'); - } - - addTypingLine('> Completed successfully'); - setIsExecuting(false); - }, 600); - - } catch (error: any) { - console.error('MCP tool execution failed:', error); - - // Parse error through enhanced error handler for better user experience - const enhancedError = parseKnowledgeBaseError(error); - setError(enhancedError); - - setTimeout(() => { - addTypingLine(`> ERROR: Failed to execute tool on ${client.name}`, false, true); - addTypingLine(`> ${error.message || 'Unknown error occurred'}`, false, true); - - // Add special handling for OpenAI/RAG related errors - if (enhancedError.isOpenAIError && enhancedError.errorDetails) { - addTypingLine(`> Error Type: ${enhancedError.errorDetails.error_type}`, false, true); - if (enhancedError.errorDetails.tokens_used) { - addTypingLine(`> Tokens Used: ${enhancedError.errorDetails.tokens_used}`, false, true); - } - } - - addTypingLine('> Execution failed'); - setIsExecuting(false); - }, 300); - } - }; - - // Validate required parameters - const validateParameters = (): string | null => { - if (!selectedTool) return 'No tool selected'; - - for (const param of selectedTool.parameters) { - if (param.required && !paramValues[param.name]) { - return `Required parameter '${param.name}' is missing`; - } - } - - return null; - }; - - // Handle tool execution - const executeSelectedTool = () => { - if (!selectedTool || !client || isExecuting) return; - - // Clear any previous errors - clearError(); - - // Validate required parameters - const validationError = validateParameters(); - if (validationError) { - addTypingLine(`> ERROR: ${validationError}`, false, true); - return; - } - - setIsExecuting(true); - - // Add command to terminal - const params = selectedTool.parameters.map(p => { - const value = paramValues[p.name]; - return value ? `${p.name}=${value}` : undefined; - }).filter(Boolean).join(' '); - - const command = `> execute ${selectedTool.name} ${params}`; - addTypingLine(command, true); - - // Execute using universal client service for ALL clients - setTimeout(() => { - executeTool(); - }, 200); - }; - - // Handle copy terminal output - const copyTerminalOutput = () => { - const textContent = terminalOutput.map(line => line.content).join('\n'); - navigator.clipboard.writeText(textContent); - setIsCopied(true); - setTimeout(() => setIsCopied(false), 2000); - }; - - // Handle resize start - const handleResizeStart = (e: React.MouseEvent) => { - e.preventDefault(); - setIsResizing(true); - }; - - // Handle maximize/minimize - const toggleMaximize = () => { - if (isMaximized) { - setPanelHeight(previousHeightRef.current); - } else { - previousHeightRef.current = panelHeight; - setPanelHeight(window.innerHeight * 0.8); - } - setIsMaximized(!isMaximized); - }; - - // Clear terminal - const clearTerminal = () => { - setTerminalOutput([{ - id: Date.now().toString(), - content: '> Terminal cleared', - isTyping: false, - isCommand: true - }]); - }; - - if (!isOpen || !client) return null; - - return ( -
- {/* Resize handle at the top */} -
-
-
- - {/* Panel with neon effect */} -
-
- - {/* Header */} -
-

- - {client.name} - - {client.ip} - - - {client.tools.length} tools available - -

-
- - - -
-
- - {/* Content */} -
- {/* Error Alert for OpenAI/RAG errors */} - - - {client.tools.length === 0 ? ( -
-
- -

No Tools Available

-

- {client.status === 'offline' - ? 'Client is offline. Tools will be available when connected.' - : 'No tools discovered for this client.'} -

-
-
- ) : ( -
- {/* Left column: Tool selection and parameters */} -
- {/* Tool selection and execute button row */} -
-
- -
- -
- -
-
-
-
- -
-
- - {/* Tool description */} - {selectedTool && ( -

- {selectedTool.description} -

- )} - - {/* Parameters */} - {selectedTool && selectedTool.parameters.length > 0 && ( -
-

- Parameters -

-
- {selectedTool.parameters.map(param => ( -
- - handleParamChange(param.name, e.target.value)} - className="w-full px-3 py-2 text-sm bg-white/50 dark:bg-black/50 border border-gray-300 dark:border-gray-700 rounded-md focus:outline-none focus:ring-1 focus:ring-cyan-500 focus:border-cyan-500 transition-all duration-200" - placeholder={param.description || `Enter ${param.name}`} - /> - {param.description && ( -

- {param.description} -

- )} -
- ))} -
-
- )} -
- - {/* Right column: Terminal output */} -
-
-
-
- - - Terminal Output - -
- -
-
- {terminalOutput.map(line => ( -
- {line.content} - {line.isTyping && } -
- ))} -
-
-
-
- )} -
-
-
- ); -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/project-tasks/DataTab.tsx b/archon-ui-main/src/components/project-tasks/DataTab.tsx deleted file mode 100644 index 4b0cb54c..00000000 --- a/archon-ui-main/src/components/project-tasks/DataTab.tsx +++ /dev/null @@ -1,958 +0,0 @@ -import React, { useCallback, useState, useEffect, useMemo } from 'react'; -import '@xyflow/react/dist/style.css'; -import { ReactFlow, Node, Edge, Background, Controls, MarkerType, NodeChange, applyNodeChanges, EdgeChange, applyEdgeChanges, ConnectionLineType, addEdge, Connection, Handle, Position } from '@xyflow/react'; -import { Database, Info, Calendar, TrendingUp, Edit, Plus, X, Save, Trash2 } from 'lucide-react'; -import { projectService } from '../../services/projectService'; -import { taskUpdateSocketIO } from '../../services/socketIOService'; -import { useToast } from '../../contexts/ToastContext'; - -// Custom node types - will be defined inside the component to access state - -const createTableNode = (id: string, label: string, columns: string[], x: number, y: number): Node => ({ - id, - type: 'table', - data: { - label, - columns - }, - position: { - x, - y - } -}); - -// Default fallback nodes for basic database structure -const defaultNodes: Node[] = [ - createTableNode('users', 'Users', ['id (PK) - UUID', 'email - VARCHAR(255)', 'password - VARCHAR(255)', 'firstName - VARCHAR(100)', 'lastName - VARCHAR(100)', 'createdAt - TIMESTAMP', 'updatedAt - TIMESTAMP'], 150, 100), - createTableNode('projects', 'Projects', ['id (PK) - UUID', 'title - VARCHAR(255)', 'description - TEXT', 'status - VARCHAR(50)', 'userId (FK) - UUID', 'createdAt - TIMESTAMP', 'updatedAt - TIMESTAMP'], 500, 100) -]; - -const defaultEdges: Edge[] = [{ - id: 'projects-users', - source: 'users', - target: 'projects', - sourceHandle: 'Users-id', - targetHandle: 'Projects-userId', - animated: true, - style: { - stroke: '#d946ef' - }, - markerEnd: { - type: MarkerType.Arrow, - color: '#d946ef' - } -}]; - -// Data metadata card component for the new data structure -const DataCard = ({ data }: { data: any }) => { - const iconMap: { [key: string]: any } = { - 'ShoppingCart': Database, - 'Database': Database, - 'Info': Info, - 'Calendar': Calendar, - 'TrendingUp': TrendingUp - }; - - const IconComponent = iconMap[data.icon] || Database; - - const colorClasses = { - cyan: 'from-cyan-900/40 to-cyan-800/30 border-cyan-500/50 text-cyan-400', - blue: 'from-blue-900/40 to-blue-800/30 border-blue-500/50 text-blue-400', - purple: 'from-purple-900/40 to-purple-800/30 border-purple-500/50 text-purple-400', - pink: 'from-pink-900/40 to-pink-800/30 border-pink-500/50 text-pink-400' - }; - - const colorClass = colorClasses[data.color as keyof typeof colorClasses] || colorClasses.cyan; - - return ( -
-
- -
Project Data Overview
-
-
-
-
Description:
-
{data.description}
-
-
- Progress: -
-
-
-
- {data.progress}% -
-
-
- Last updated: {data.updated} -
-
-
- ); -}; - -interface DataTabProps { - project?: { - id: string; - title: string; - data?: any[]; - } | null; -} - -export const DataTab = ({ project }: DataTabProps) => { - const [nodes, setNodes] = useState([]); - const [edges, setEdges] = useState([]); - const [loading, setLoading] = useState(true); - const [viewMode, setViewMode] = useState<'metadata' | 'erd'>('metadata'); - const [editingNode, setEditingNode] = useState(null); - const [showEditModal, setShowEditModal] = useState(false); - const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); - const [isSaving, setIsSaving] = useState(false); - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); - const [nodeToDelete, setNodeToDelete] = useState(null); - - const { showToast } = useToast(); - - // Note: Removed aggressive WebSocket cleanup to prevent interference with normal connection lifecycle - - // Helper function to normalize nodes to ensure required properties - const normalizeNode = (node: any): Node => { - return { - id: node.id || `node-${Date.now()}-${Math.random()}`, - type: node.type || 'table', - position: { - x: node.position?.x || 100, - y: node.position?.y || 100 - }, - data: { - label: node.data?.label || 'Untitled', - columns: node.data?.columns || ['id (PK) - UUID'] - } - }; - }; - - useEffect(() => { - console.log('DataTab project data:', project?.data); - - // Determine view mode based on data structure - if (project?.data) { - if (Array.isArray(project.data) && project.data.length > 0) { - // Handle array format: [{"type": "erd", ...}] or [{"description": "...", "progress": 65}] - const firstItem = project.data[0]; - console.log('First data item (array):', firstItem); - - if (firstItem.description && typeof firstItem.progress === 'number') { - console.log('Setting metadata view'); - setViewMode('metadata'); - } else if (firstItem.type === 'erd' && firstItem.nodes && firstItem.edges) { - console.log('Setting ERD view with structured array data'); - setViewMode('erd'); - // Normalize nodes to ensure required properties - const normalizedNodes = firstItem.nodes.map(normalizeNode); - setNodes(normalizedNodes); - // Fix any ArrowClosed marker types in loaded edges - const sanitizedEdges = firstItem.edges.map((edge: any) => ({ - ...edge, - markerEnd: edge.markerEnd ? { - ...edge.markerEnd, - type: edge.markerEnd.type === 'ArrowClosed' ? MarkerType.Arrow : edge.markerEnd.type - } : undefined - })); - setEdges(sanitizedEdges); - } else { - console.log('Setting ERD view for array data'); - setViewMode('erd'); - // Normalize nodes to ensure required properties - const normalizedNodes = project.data.map(normalizeNode); - setNodes(normalizedNodes); - setEdges([]); - } - } else if (typeof project.data === 'object' && !Array.isArray(project.data) && - (project.data as any).type === 'erd' && - (project.data as any).nodes && - (project.data as any).edges) { - // Handle direct object format: {"type": "erd", "nodes": [...], "edges": [...]} - console.log('Setting ERD view with direct object data'); - setViewMode('erd'); - // Normalize nodes to ensure required properties - const normalizedNodes = (project.data as any).nodes.map(normalizeNode); - setNodes(normalizedNodes); - // Fix any ArrowClosed marker types in loaded edges - const sanitizedEdges = (project.data as any).edges.map((edge: any) => ({ - ...edge, - markerEnd: edge.markerEnd ? { - ...edge.markerEnd, - type: edge.markerEnd.type === 'ArrowClosed' ? MarkerType.Arrow : edge.markerEnd.type - } : undefined - })); - setEdges(sanitizedEdges); - } else { - console.log('Unknown data format, showing empty state'); - setViewMode('erd'); - setNodes([]); - setEdges([]); - } - } else { - console.log('No data, using empty state'); - setViewMode('erd'); - setNodes([]); - setEdges([]); - } - setLoading(false); - }, [project]); - - const onNodesChange = useCallback((changes: NodeChange[]) => { - setNodes(nds => applyNodeChanges(changes, nds)); - setHasUnsavedChanges(true); - }, []); - - const onEdgesChange = useCallback((changes: EdgeChange[]) => { - setEdges(eds => applyEdgeChanges(changes, eds)); - setHasUnsavedChanges(true); - }, []); - const onConnect = useCallback(async (connection: Connection) => { - const newEdgeProps = { - animated: true, - style: { - stroke: '#22d3ee' - }, - markerEnd: { - type: MarkerType.Arrow, - color: '#22d3ee' - }, - label: 'relates to', - labelStyle: { - fill: '#e94560', - fontWeight: 500 - }, - labelBgStyle: { - fill: 'rgba(0, 0, 0, 0.7)' - } - }; - - const newEdges = addEdge({ ...connection, ...newEdgeProps }, edges); - setEdges(newEdges); - - // Auto-save to database - await saveToDatabase(nodes, newEdges); - }, [nodes, edges, project?.id]); - - const handleNodeClick = useCallback((event: React.MouseEvent, node: Node) => { - setEditingNode(node); - setShowEditModal(true); - }, []); - - - - const addTableNode = async () => { - if (!project?.id) { - console.error('❌ No project ID available for adding table'); - return; - } - - console.log('🔄 Adding new table...'); - const newNodeId = `table-${Date.now()}`; - const newNode = createTableNode(newNodeId, `New Table ${nodes.length + 1}`, ['id (PK) - UUID', 'name - VARCHAR(255)', 'description - TEXT', 'createdAt - TIMESTAMP', 'updatedAt - TIMESTAMP'], 400, 300); - const newNodes = [...nodes, newNode]; - setNodes(newNodes); - - // Auto-save to database - try { - console.log('💾 Saving new table to database...'); - await saveToDatabase(newNodes, edges); - console.log('✅ New table saved successfully'); - } catch (error) { - console.error('❌ Failed to save new table:', error); - // Optionally revert the UI change if save failed - setNodes(nodes); - } - }; - - const saveToDatabase = async (nodesToSave = nodes, edgesToSave = edges) => { - if (!project?.id) { - console.error('No project ID available for saving'); - return; - } - - console.log('💾 saveToDatabase called with:', { - projectId: project.id, - nodeCount: nodesToSave.length, - edgeCount: edgesToSave.length - }); - - setIsSaving(true); - try { - const updatedData = { - type: 'erd', - nodes: nodesToSave, - edges: edgesToSave - }; - - console.log('🔄 Calling projectService.updateProject with data:', updatedData); - - const result = await projectService.updateProject(project.id, { - data: [updatedData] // Wrap in array to match UpdateProjectRequest type - }); - - console.log('✅ ERD data saved successfully, result:', result); - setHasUnsavedChanges(false); - } catch (error) { - console.error('❌ Failed to save ERD data:', error); - console.error('Error details:', error); - throw error; // Re-throw so calling function can handle it - } finally { - setIsSaving(false); - } - }; - - const saveNodeChanges = async (updatedNode: Node) => { - // Update local state first - const newNodes = nodes.map(node => - node.id === updatedNode.id ? updatedNode : node - ); - setNodes(newNodes); - - // Save to database - await saveToDatabase(newNodes, edges); - - setShowEditModal(false); - setEditingNode(null); - }; - - const handleManualSave = async () => { - await saveToDatabase(); - }; - - const handleDeleteNode = useCallback(async (event: React.MouseEvent, nodeId: string) => { - event.stopPropagation(); // Prevent triggering the edit modal - - if (!project?.id) { - console.error('❌ No project ID available for deleting table'); - return; - } - - // Show custom confirmation dialog - setNodeToDelete(nodeId); - setShowDeleteConfirm(true); - }, [project?.id]); - - const confirmDelete = useCallback(async () => { - if (!nodeToDelete) return; - - console.log('🗑️ Deleting table:', nodeToDelete); - - try { - // Remove node from UI - const newNodes = nodes.filter(node => node.id !== nodeToDelete); - - // Remove any edges connected to this node - const newEdges = edges.filter(edge => - edge.source !== nodeToDelete && edge.target !== nodeToDelete - ); - - setNodes(newNodes); - setEdges(newEdges); - - // Save to database - console.log('💾 Saving after table deletion...'); - await saveToDatabase(newNodes, newEdges); - console.log('✅ Table deleted successfully'); - showToast('Table deleted successfully', 'success'); - - // Close confirmation dialog - setShowDeleteConfirm(false); - setNodeToDelete(null); - } catch (error) { - console.error('❌ Failed to delete table:', error); - // Revert UI changes on error - setNodes(nodes); - setEdges(edges); - showToast('Failed to delete table', 'error'); - } - }, [nodeToDelete, nodes, edges, saveToDatabase]); - - const cancelDelete = useCallback(() => { - setShowDeleteConfirm(false); - setNodeToDelete(null); - }, []); - - // Memoize nodeTypes to prevent recreation on every render - const nodeTypes = useMemo(() => ({ - table: ({ data, id }: any) => ( -
-
-
- -
- {data.label} -
-
-
- - -
-
-
- {data.columns.map((col: string, i: number) => { - const isPK = col.includes('PK'); - const isFK = col.includes('FK'); - return ( -
- {col} - {isPK && ( - - )} - {isFK && ( - - )} -
- ); - })} -
-
- ) - }), [handleNodeClick, handleDeleteNode, nodes]); - - if (loading) { - return ( -
-
Loading data...
-
- ); - } - - return ( -
-
-
-
-
- - {viewMode === 'metadata' ? 'Data Overview' : 'Data Relationships'} - {viewMode === 'erd' && nodes.length > 0 && ` (${nodes.length} tables)`} - {viewMode === 'metadata' && Array.isArray(project?.data) && ` (${project.data.length} items)`} -
- {viewMode === 'metadata' && ( - - )} - {viewMode === 'erd' && ( -
- - {hasUnsavedChanges && ( - - )} - -
- )} -
- - {viewMode === 'metadata' ? ( -
- {Array.isArray(project?.data) && project.data.length > 0 ? ( -
- {project.data.map((item, index) => ( - - ))} -
- ) : ( -
- -

No metadata available

-

Switch to ERD view to see database schema

-
- )} -
- ) : ( -
- {/* Subtle neon glow at the top */} -
- {nodes.length === 0 ? ( -
- -

No data schema defined

-

Add tables to design your database

-
- ) : ( - - - - )} -
- )} - - {/* Delete Confirmation Modal */} - {showDeleteConfirm && ( - n.id === nodeToDelete)?.data.label as string || 'table'} - /> - )} - - {/* Edit Modal */} - {showEditModal && editingNode && ( - { - setShowEditModal(false); - setEditingNode(null); - }} - /> - )} -
-
- ); -}; - -// Delete Confirmation Modal Component -const DeleteConfirmModal = ({ - onConfirm, - onCancel, - tableName -}: { - onConfirm: () => void; - onCancel: () => void; - tableName: string; -}) => { - return ( -
-
- -
-
-
- -
-
-

- Delete Table -

-

- This action cannot be undone -

-
-
- -

- Are you sure you want to delete the "{tableName}" table? - This will also remove all related connections. -

- -
- - -
-
-
-
- ); -}; - -// Column interface for better type management -interface ColumnDefinition { - name: string; - dataType: string; - columnType: 'regular' | 'pk' | 'fk'; - referencedTable?: string; - referencedColumn?: string; -} - -// Edit Table Modal Component -const EditTableModal = ({ - node, - nodes, - edges, - onSave, - onUpdateEdges, - onClose -}: { - node: Node; - nodes: Node[]; - edges: Edge[]; - onSave: (node: Node) => void; - onUpdateEdges: (edges: Edge[]) => void; - onClose: () => void; -}) => { - const [tableName, setTableName] = useState(node.data.label as string); - const [columns, setColumns] = useState([]); - - // Parse existing columns into structured format - useEffect(() => { - const parsedColumns = (node.data.columns as string[]).map((colStr: string) => { - const parts = colStr.split(' - '); - const nameAndType = parts[0]; - const dataType = parts[1] || 'VARCHAR(255)'; - - let columnType: 'regular' | 'pk' | 'fk' = 'regular'; - let name = nameAndType; - const referencedTable = ''; - const referencedColumn = ''; - - if (nameAndType.includes('(PK)')) { - columnType = 'pk'; - name = nameAndType.replace(' (PK)', ''); - } else if (nameAndType.includes('(FK)')) { - columnType = 'fk'; - name = nameAndType.replace(' (FK)', ''); - } - - return { - name, - dataType, - columnType, - referencedTable, - referencedColumn - }; - }); - setColumns(parsedColumns); - }, [node.data.columns]); - - const addColumn = () => { - setColumns([...columns, { - name: 'newColumn', - dataType: 'VARCHAR(255)', - columnType: 'regular', - referencedTable: '', - referencedColumn: '' - }]); - }; - - const updateColumn = (index: number, field: keyof ColumnDefinition, value: string) => { - const newColumns = [...columns]; - newColumns[index] = { ...newColumns[index], [field]: value }; - setColumns(newColumns); - }; - - const removeColumn = (index: number) => { - setColumns(columns.filter((_, i) => i !== index)); - }; - - // Get available tables for FK references (exclude current table) - const getAvailableTables = () => { - return nodes.filter(n => n.id !== node.id).map(n => ({ - id: n.id, - label: n.data.label as string - })); - }; - - // Get available columns for a specific table - const getAvailableColumns = (tableId: string) => { - const targetNode = nodes.find(n => n.id === tableId); - if (!targetNode) return []; - - return (targetNode.data.columns as string[]) - .filter(col => col.includes('(PK)')) // Only allow referencing primary keys - .map(col => { - const name = col.split(' - ')[0].replace(' (PK)', ''); - return { name, label: name }; - }); - }; - - const handleSave = () => { - // Convert columns back to string format - const columnStrings = columns.map(col => { - let name = col.name; - if (col.columnType === 'pk') { - name += ' (PK)'; - } else if (col.columnType === 'fk') { - name += ' (FK)'; - } - return `${name} - ${col.dataType}`; - }); - - const updatedNode = { - ...node, - data: { - ...node.data, - label: tableName, - columns: columnStrings - } - }; - - // Create edges for FK relationships - const newEdges = [...edges]; - - // Remove existing edges from this table - const filteredEdges = newEdges.filter(edge => edge.source !== node.id); - - // Add new edges for FK columns - columns.forEach(col => { - if (col.columnType === 'fk' && col.referencedTable && col.referencedColumn) { - const edgeId = `${col.referencedTable}-${node.id}`; - const newEdge = { - id: edgeId, - source: col.referencedTable, - target: node.id, - sourceHandle: `${nodes.find(n => n.id === col.referencedTable)?.data.label}-${col.referencedColumn}`, - targetHandle: `${tableName}-${col.name}`, - animated: true, - style: { - stroke: '#d946ef' - }, - markerEnd: { - type: MarkerType.Arrow, - color: '#d946ef' - } - }; - filteredEdges.push(newEdge); - } - }); - - // Update edges state - onUpdateEdges(filteredEdges); - - onSave(updatedNode); - }; - - return ( -
-
-
-

- - Edit Table -

- -
- -
- {/* Table Name */} -
- - setTableName(e.target.value)} - className="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-cyan-500 focus:outline-none" - /> -
- - {/* Columns */} -
-
- - -
- - {/* Column Headers */} -
-
Column Name
-
Data Type
-
Type
-
References (FK only)
-
-
- -
- {columns.map((column, index) => ( -
- {/* Column Name */} - updateColumn(index, 'name', e.target.value)} - className="col-span-3 px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-cyan-500 focus:outline-none text-sm" - /> - - {/* Data Type */} - updateColumn(index, 'dataType', e.target.value)} - className="col-span-2 px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-cyan-500 focus:outline-none text-sm" - /> - - {/* Column Type */} - - - {/* FK Reference (only show for FK columns) */} - {column.columnType === 'fk' && ( - <> - - - - - )} - - {/* Spacer for non-FK columns */} - {column.columnType !== 'fk' &&
} - - {/* Remove Button */} - -
- ))} -
-
-
- - {/* Actions */} -
- - -
-
-
- ); -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/project-tasks/DocsTab.tsx b/archon-ui-main/src/components/project-tasks/DocsTab.tsx deleted file mode 100644 index 55aebebb..00000000 --- a/archon-ui-main/src/components/project-tasks/DocsTab.tsx +++ /dev/null @@ -1,1536 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Plus, X, Search, Upload, Link as LinkIcon, Check, Brain, Save, History, Eye, Edit3, Sparkles } from 'lucide-react'; -import { Button } from '../ui/Button'; -import { knowledgeBaseService, KnowledgeItem } from '../../services/knowledgeBaseService'; -import { projectService } from '../../services/projectService'; -import { useToast } from '../../contexts/ToastContext'; -import { Input } from '../ui/Input'; -import { Card } from '../ui/Card'; -import { Badge } from '../ui/Badge'; -import { Select } from '../ui/Select'; -import { CrawlProgressData, crawlProgressService } from '../../services/crawlProgressService'; -import { WebSocketState } from '../../services/socketIOService'; -import { MilkdownEditor } from './MilkdownEditor'; -import { VersionHistoryModal } from './VersionHistoryModal'; -import { PRPViewer } from '../prp'; -import { DocumentCard, NewDocumentCard } from './DocumentCard'; - - - - -interface ProjectDoc { - id: string; - title: string; - created_at: string; - updated_at: string; - // Content field stores markdown or structured data - content?: any; - document_type?: string; -} - -interface Task { - id: string; - title: string; - feature: string; - status: 'backlog' | 'in-progress' | 'review' | 'complete'; -} - -// Document Templates - Updated for proper MCP database storage -const DOCUMENT_TEMPLATES = { - 'prp_base': { - name: 'Feature PRP Template', - icon: '🚀', - document_type: 'prp', - content: { - document_type: 'prp', - title: 'New Feature Implementation', - version: '1.0', - author: 'User', - date: new Date().toISOString().split('T')[0], - status: 'draft', - - goal: 'Build a specific feature - replace with your goal', - - why: [ - 'Business value this feature provides', - 'User problem this solves', - 'How it integrates with existing functionality' - ], - - what: { - description: 'Detailed description of what users will see and experience', - success_criteria: [ - 'Measurable outcome 1 (e.g., response time < 200ms)', - 'User behavior outcome 2 (e.g., 90% task completion rate)', - 'Technical outcome 3 (e.g., zero data loss during operations)' - ], - user_stories: [ - 'As a [user type], I want to [action] so that [benefit]', - 'As a [user type], I need to [requirement] in order to [goal]' - ] - }, - - context: { - documentation: [ - { - source: 'https://docs.example.com/api', - why: 'API endpoints and data models needed' - }, - { - source: 'src/components/Example.tsx', - why: 'Existing pattern to follow for UI components' - } - ], - existing_code: [ - { - file: 'src/services/baseService.ts', - purpose: 'Service layer pattern to extend' - } - ], - gotchas: [ - 'Critical requirement or constraint to remember', - 'Common mistake to avoid during implementation' - ], - dependencies: [ - 'Package or service that must be available', - 'Another feature that must be completed first' - ] - }, - - implementation_blueprint: { - phase_1_foundation: { - description: 'Set up core infrastructure', - duration: '2-3 days', - tasks: [ - { - title: 'Create TypeScript interfaces', - details: 'Define all data types and API contracts', - files: ['src/types/newFeature.ts'] - }, - { - title: 'Set up database schema', - details: 'Create tables and relationships if needed', - files: ['migrations/add_feature_tables.sql'] - } - ] - }, - phase_2_implementation: { - description: 'Build core functionality', - duration: '1 week', - tasks: [ - { - title: 'Implement service layer', - details: 'Business logic and data access', - files: ['src/services/newFeatureService.ts'] - }, - { - title: 'Create API endpoints', - details: 'RESTful endpoints with proper validation', - files: ['src/api/newFeatureApi.ts'] - }, - { - title: 'Build UI components', - details: 'React components with TypeScript', - files: ['src/components/NewFeature.tsx'] - } - ] - }, - phase_3_integration: { - description: 'Connect everything and test', - duration: '2-3 days', - tasks: [ - { - title: 'Integrate frontend with backend', - details: 'Connect UI to API endpoints', - files: ['src/hooks/useNewFeature.ts'] - }, - { - title: 'Add comprehensive tests', - details: 'Unit, integration, and E2E tests', - files: ['tests/newFeature.test.ts'] - } - ] - } - }, - - validation: { - level_1_syntax: [ - 'npm run lint -- --fix', - 'npm run typecheck', - 'Ensure no TypeScript errors' - ], - level_2_unit_tests: [ - 'npm run test -- newFeature', - 'Verify all unit tests pass with >80% coverage' - ], - level_3_integration: [ - 'npm run test:integration', - 'Test API endpoints with proper data flow' - ], - level_4_end_to_end: [ - 'Start development server and test user flows', - 'Verify feature works as expected in browser', - 'Test error scenarios and edge cases' - ] - } - } - }, - 'prp_task': { - name: 'Task/Bug Fix PRP', - icon: '✅', - document_type: 'prp', - content: { - document_type: 'prp', - title: 'Task or Bug Fix', - version: '1.0', - author: 'User', - date: new Date().toISOString().split('T')[0], - status: 'draft', - - goal: 'Fix specific bug or complete targeted task', - - why: [ - 'Impact on users or system if not fixed', - 'How this fits into larger project goals', - 'Priority level and urgency' - ], - - what: { - description: 'Specific problem to solve and expected outcome', - current_behavior: 'What happens now (the problem)', - expected_behavior: 'What should happen instead', - acceptance_criteria: [ - 'Specific testable condition 1', - 'Specific testable condition 2', - 'No regressions in existing functionality' - ] - }, - - context: { - affected_files: [ - { - path: 'src/component.tsx', - reason: 'Contains the bug or needs the change' - }, - { - path: 'src/service.ts', - reason: 'Related logic that may need updates' - } - ], - root_cause: 'Analysis of why this issue exists', - related_issues: [ - 'Link to GitHub issue or ticket', - 'Related bugs or enhancement requests' - ], - dependencies: [ - 'Other tasks that must be completed first', - 'External services or APIs involved' - ] - }, - - implementation_steps: [ - { - step: 1, - action: 'Reproduce the issue', - details: 'Create test case that demonstrates the problem' - }, - { - step: 2, - action: 'Identify root cause', - details: 'Debug and trace the issue to its source' - }, - { - step: 3, - action: 'Implement fix', - details: 'Apply minimal change that resolves the issue' - }, - { - step: 4, - action: 'Test solution', - details: 'Verify fix works and doesn\'t break other functionality' - }, - { - step: 5, - action: 'Update documentation', - details: 'Update any relevant docs or comments' - } - ], - - validation: { - reproduction_test: [ - 'Steps to reproduce the original issue', - 'Verify the issue no longer occurs' - ], - regression_tests: [ - 'Run existing test suite to ensure no regressions', - 'Test related functionality manually' - ], - edge_cases: [ - 'Test boundary conditions', - 'Test error scenarios' - ] - } - } - }, - 'prp_planning': { - name: 'Architecture/Planning PRP', - icon: '📐', - document_type: 'prp', - content: { - document_type: 'prp', - title: 'System Architecture and Planning', - version: '1.0', - author: 'User', - date: new Date().toISOString().split('T')[0], - status: 'draft', - - goal: 'Design and plan system architecture for [specific system/feature]', - - why: [ - 'Strategic business objective driving this architecture', - 'Technical debt or scalability issues to address', - 'Future growth and maintainability requirements' - ], - - what: { - scope: 'System boundaries, affected components, and integration points', - deliverables: [ - 'Comprehensive architecture documentation', - 'Component specifications and interfaces', - 'Implementation roadmap and timeline', - 'Migration/deployment strategy' - ], - constraints: [ - 'Budget and timeline limitations', - 'Technical constraints and dependencies', - 'Regulatory or compliance requirements' - ] - }, - - current_state_analysis: { - strengths: [ - 'What works well in the current system', - 'Stable components that should be preserved', - 'Existing patterns worth maintaining' - ], - weaknesses: [ - 'Performance bottlenecks and limitations', - 'Maintenance and scaling challenges', - 'Security or reliability concerns' - ], - opportunities: [ - 'Modern technologies to leverage', - 'Process improvements to implement', - 'Business capabilities to enable' - ], - threats: [ - 'Risks during transition period', - 'Dependencies on legacy systems', - 'Resource and timeline constraints' - ] - }, - - proposed_architecture: { - overview: 'High-level description of the new architecture', - components: { - frontend: { - technology: 'React 18 with TypeScript', - patterns: 'Component composition with ShadCN UI', - state_management: 'React hooks with context for global state' - }, - backend: { - technology: 'FastAPI with async Python', - patterns: 'Service layer with repository pattern', - database: 'Supabase PostgreSQL with proper indexing' - }, - realtime: { - technology: 'Socket.IO for live updates', - patterns: 'Event-driven communication with proper error handling' - }, - infrastructure: { - deployment: 'Docker containers with orchestration', - monitoring: 'Comprehensive logging and metrics', - security: 'OAuth2 with proper encryption' - } - }, - data_flow: [ - 'User interaction → Frontend validation → API call', - 'Backend processing → Database operations → Response', - 'Real-time events → Socket.IO → UI updates' - ], - integration_points: [ - 'External APIs and their usage patterns', - 'Third-party services and data sources', - 'Legacy system interfaces' - ] - }, - - implementation_phases: { - phase_1_foundation: { - duration: '2-3 weeks', - objective: 'Core infrastructure and basic functionality', - deliverables: [ - 'Database schema and basic API endpoints', - 'Authentication and authorization system', - 'Core UI components and routing' - ], - success_criteria: [ - 'Basic user flows working end-to-end', - 'Core API responses under 200ms', - 'Authentication working with test users' - ] - }, - phase_2_features: { - duration: '3-4 weeks', - objective: 'Primary feature implementation', - deliverables: [ - 'Complete feature set with UI', - 'Real-time updates and notifications', - 'Data validation and error handling' - ], - success_criteria: [ - 'All major user stories implemented', - 'Real-time features working reliably', - 'Comprehensive error handling' - ] - }, - phase_3_optimization: { - duration: '1-2 weeks', - objective: 'Testing, optimization, and deployment', - deliverables: [ - 'Comprehensive test suite', - 'Performance optimization', - 'Production deployment' - ], - success_criteria: [ - 'Test coverage >80%', - 'Performance targets met', - 'Successful production deployment' - ] - } - }, - - success_metrics: { - performance: [ - 'API response time <200ms for 95% of requests', - 'UI load time <2 seconds', - 'Support 1000+ concurrent users' - ], - quality: [ - 'Test coverage >80%', - 'Zero critical security vulnerabilities', - 'Mean time to recovery <15 minutes' - ], - business: [ - 'User task completion rate >90%', - 'Feature adoption >60% within first month', - 'User satisfaction score >4.5/5' - ] - }, - - risks_and_mitigation: { - technical_risks: [ - { - risk: 'Integration complexity with legacy systems', - mitigation: 'Phased approach with fallback options' - }, - { - risk: 'Performance issues at scale', - mitigation: 'Load testing and optimization in early phases' - } - ], - business_risks: [ - { - risk: 'Timeline delays due to scope creep', - mitigation: 'Clear requirements and change control process' - } - ] - } - } - }, - - // Simple markdown templates for non-PRP documents - 'markdown_doc': { - name: 'Markdown Document', - icon: '📝', - document_type: 'markdown', - content: { - markdown: `# Document Title - -## Overview - -Provide a brief overview of this document... - -## Content - -Add your content here... - -## Next Steps - -- [ ] Action item 1 -- [ ] Action item 2` - } - }, - - 'meeting_notes': { - name: 'Meeting Notes', - icon: '📋', - document_type: 'meeting_notes', - content: { - meeting_date: new Date().toISOString().split('T')[0], - attendees: ['Person 1', 'Person 2'], - agenda: [ - 'Agenda item 1', - 'Agenda item 2' - ], - notes: 'Meeting discussion notes...', - action_items: [ - { - item: 'Action item 1', - owner: 'Person Name', - due_date: 'YYYY-MM-DD' - } - ], - next_meeting: 'YYYY-MM-DD' - } - } -}; - -/* ——————————————————————————————————————————— */ -/* Main component */ -/* ——————————————————————————————————————————— */ -export const DocsTab = ({ - tasks, - project -}: { - tasks: Task[]; - project?: { - id: string; - title: string; - created_at?: string; - updated_at?: string; - } | null; -}) => { - // Document state - const [documents, setDocuments] = useState([]); - const [selectedDocument, setSelectedDocument] = useState(null); - const [isEditing, setIsEditing] = useState(false); - const [isSaving, setIsSaving] = useState(false); - const [loading, setLoading] = useState(false); - const [showTemplateModal, setShowTemplateModal] = useState(false); - const [showVersionHistory, setShowVersionHistory] = useState(false); - const [viewMode, setViewMode] = useState<'beautiful' | 'markdown'>('beautiful'); - - // Dark mode detection - const [isDarkMode, setIsDarkMode] = useState(false); - - useEffect(() => { - const checkDarkMode = () => { - const htmlElement = document.documentElement; - const hasDarkClass = htmlElement.classList.contains('dark'); - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - setIsDarkMode(hasDarkClass || prefersDark); - }; - - checkDarkMode(); - - // Listen for changes - const observer = new MutationObserver(checkDarkMode); - observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); - - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - mediaQuery.addEventListener('change', checkDarkMode); - - return () => { - observer.disconnect(); - mediaQuery.removeEventListener('change', checkDarkMode); - }; - }, []); - - // Knowledge management state - const [showTechnicalModal, setShowTechnicalModal] = useState(false); - const [showBusinessModal, setShowBusinessModal] = useState(false); - const [selectedTechnicalSources, setSelectedTechnicalSources] = useState([]); - const [selectedBusinessSources, setSelectedBusinessSources] = useState([]); - const [showAddSourceModal, setShowAddSourceModal] = useState(false); - const [sourceType, setSourceType] = useState<'technical' | 'business'>('technical'); - const [knowledgeItems, setKnowledgeItems] = useState([]); - const [progressItems, setProgressItems] = useState([]); - const { showToast } = useToast(); - - // Load project documents from the project data - const loadProjectDocuments = async () => { - if (!project?.id || !project.docs) return; - - try { - setLoading(true); - - // Use the docs directly from the project data - const projectDocuments: ProjectDoc[] = project.docs.map((doc: any) => ({ - id: doc.id, - title: doc.title || 'Untitled Document', - created_at: doc.created_at, - updated_at: doc.updated_at, - content: doc.content, - document_type: doc.document_type || 'document' - })); - - setDocuments(projectDocuments); - - // Auto-select first document if available and no document is currently selected - if (projectDocuments.length > 0 && !selectedDocument) { - setSelectedDocument(projectDocuments[0]); - } - - console.log(`Loaded ${projectDocuments.length} documents from project data`); - } catch (error) { - console.error('Failed to load documents:', error); - showToast('Failed to load documents', 'error'); - } finally { - setLoading(false); - } - }; - - // Create new document from template - const createDocumentFromTemplate = async (templateKey: string) => { - if (!project?.id) return; - - const template = DOCUMENT_TEMPLATES[templateKey as keyof typeof DOCUMENT_TEMPLATES]; - if (!template) return; - - try { - setIsSaving(true); - - // Create the document in the database first - const newDocument = await projectService.createDocument(project.id, { - title: template.name, - content: template.content, - document_type: template.document_type, - tags: [] - }); - - // Add to documents list with the real document from the database - setDocuments(prev => [...prev, newDocument]); - setSelectedDocument(newDocument); - - console.log('Document created successfully:', newDocument); - showToast('Document created successfully', 'success'); - setShowTemplateModal(false); - } catch (error) { - console.error('Failed to create document:', error); - showToast( - error instanceof Error ? error.message : 'Failed to create document', - 'error' - ); - } finally { - setIsSaving(false); - } - }; - - // Save document changes - const saveDocument = async () => { - if (!selectedDocument || !project?.id) return; - - try { - setIsSaving(true); - - // Call backend API to persist changes - const updatedDocument = await projectService.updateDocument( - project.id, - selectedDocument.id, - { - title: selectedDocument.title, - content: selectedDocument.content, - tags: selectedDocument.tags, - author: selectedDocument.author - } - ); - - // Update local state with backend response - setDocuments(prev => prev.map(doc => - doc.id === selectedDocument.id ? updatedDocument : doc - )); - setSelectedDocument(updatedDocument); - - console.log('Document saved successfully:', updatedDocument); - showToast('Document saved successfully', 'success'); - setIsEditing(false); - } catch (error) { - console.error('Failed to save document:', error); - showToast( - error instanceof Error ? error.message : 'Failed to save document', - 'error' - ); - } finally { - setIsSaving(false); - } - }; - - // Note: Block editing functions removed - now handled by BlockNoteEditor internally - - // Load project data including linked sources - const loadProjectData = async () => { - if (!project?.id) return; - - try { - const response = await fetch(`/api/projects/${project.id}`); - if (!response.ok) throw new Error('Failed to load project data'); - - const projectData = await response.json(); - - // Initialize selected sources from saved project data - const technicalSourceIds = (projectData.technical_sources || []).map((source: any) => source.source_id); - const businessSourceIds = (projectData.business_sources || []).map((source: any) => source.source_id); - - setSelectedTechnicalSources(technicalSourceIds); - setSelectedBusinessSources(businessSourceIds); - - console.log('Loaded project sources:', { - technical: technicalSourceIds, - business: businessSourceIds - }); - } catch (error) { - console.error('Failed to load project data:', error); - showToast('Failed to load project sources', 'error'); - } - }; - - // Load knowledge items and documents on mount - useEffect(() => { - loadKnowledgeItems(); - loadProjectDocuments(); - loadProjectData(); // Load saved sources - - // Cleanup function to disconnect crawl progress service - return () => { - console.log('🧹 DocsTab: Disconnecting crawl progress service'); - crawlProgressService.disconnect(); - }; - }, [project?.id]); - - // Clear selected document when project changes - useEffect(() => { - setSelectedDocument(null); - }, [project?.id]); - - // Existing knowledge loading function - const loadKnowledgeItems = async (knowledgeType?: 'technical' | 'business') => { - try { - setLoading(true); - const response = await knowledgeBaseService.getKnowledgeItems({ - knowledge_type: knowledgeType, - page: 1, - per_page: 50 - }); - setKnowledgeItems(response.items); - } catch (error) { - console.error('Failed to load knowledge items:', error); - showToast('Failed to load knowledge items', 'error'); - setKnowledgeItems([]); - } finally { - setLoading(false); - } - }; - - // Knowledge management helper functions (simplified for brevity) - const transformToLegacyFormat = (items: KnowledgeItem[]) => { - return items.map(item => ({ - id: item.id, - title: item.title, - type: item.metadata.source_type || 'url', - lastUpdated: new Date(item.updated_at).toLocaleDateString() - })); - }; - - const technicalSources = transformToLegacyFormat( - knowledgeItems.filter(item => item.metadata.knowledge_type === 'technical') - ); - - const businessSources = transformToLegacyFormat( - knowledgeItems.filter(item => item.metadata.knowledge_type === 'business') - ); - - const toggleTechnicalSource = (id: string) => { - setSelectedTechnicalSources(prev => prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id]); - }; - const toggleBusinessSource = (id: string) => { - setSelectedBusinessSources(prev => prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id]); - }; - const saveTechnicalSources = async () => { - if (!project?.id) return; - - try { - await projectService.updateProject(project.id, { - technical_sources: selectedTechnicalSources - }); - showToast('Technical sources updated successfully', 'success'); - setShowTechnicalModal(false); - // Reload project data to reflect the changes - await loadProjectData(); - } catch (error) { - console.error('Failed to save technical sources:', error); - showToast('Failed to update technical sources', 'error'); - } - }; - - const saveBusinessSources = async () => { - if (!project?.id) return; - - try { - await projectService.updateProject(project.id, { - business_sources: selectedBusinessSources - }); - showToast('Business sources updated successfully', 'success'); - setShowBusinessModal(false); - // Reload project data to reflect the changes - await loadProjectData(); - } catch (error) { - console.error('Failed to save business sources:', error); - showToast('Failed to update business sources', 'error'); - } - }; - - const handleProgressComplete = (data: CrawlProgressData) => { - console.log('Crawl completed:', data); - setProgressItems(prev => prev.filter(item => item.progressId !== data.progressId)); - loadKnowledgeItems(); - showToast('Crawling completed successfully', 'success'); - }; - - const handleProgressError = (error: string) => { - console.error('Crawl error:', error); - showToast(`Crawling failed: ${error}`, 'error'); - }; - - const handleProgressUpdate = (data: CrawlProgressData) => { - setProgressItems(prev => - prev.map(item => - item.progressId === data.progressId ? data : item - ) - ); - }; - - const handleStartCrawl = async (progressId: string, initialData: Partial) => { - console.log(`Starting crawl tracking for: ${progressId}`); - - const newProgressItem: CrawlProgressData = { - progressId, - status: 'starting', - percentage: 0, - logs: ['Starting crawl...'], - ...initialData - }; - - setProgressItems(prev => [...prev, newProgressItem]); - - const progressCallback = (data: CrawlProgressData) => { - console.log(`📨 Progress callback called for ${progressId}:`, data); - - if (data.progressId === progressId) { - handleProgressUpdate(data); - - if (data.status === 'completed') { - handleProgressComplete(data); - } else if (data.status === 'error') { - handleProgressError(data.error || 'Crawling failed'); - } - } - }; - - try { - // Use the enhanced streamProgress method for better connection handling - await crawlProgressService.streamProgressEnhanced(progressId, { - onMessage: progressCallback, - onError: (error) => { - console.error(`❌ WebSocket error for ${progressId}:`, error); - handleProgressError(`Connection error: ${error.message}`); - } - }, { - autoReconnect: true, - reconnectDelay: 5000, - connectionTimeout: 10000 - }); - - console.log(`✅ WebSocket connected successfully for ${progressId}`); - } catch (error) { - console.error(`❌ Failed to establish WebSocket connection:`, error); - handleProgressError('Failed to connect to progress updates'); - } - }; - - const openAddSourceModal = (type: 'technical' | 'business') => { - setSourceType(type); - setShowAddSourceModal(true); - }; - - return ( -
-
- {/* Document Header */} -
-
-
-

- Project Docs -

-

{project?.title || 'No project selected'}

-
- - {/* View mode and action buttons */} -
- {selectedDocument && ( -
- {/* View mode toggle for all documents */} -
- - -
- - {isEditing && ( - - )} -
- )} - - -
-
-
- - {/* Document Cards Container */} -
-
- {documents.map(doc => ( - { - try { - // Call API to delete from database first - await projectService.deleteDocument(project.id, docId); - - // Then remove from local state - setDocuments(prev => prev.filter(d => d.id !== docId)); - if (selectedDocument?.id === docId) { - setSelectedDocument(documents.find(d => d.id !== docId) || null); - } - showToast('Document deleted', 'success'); - } catch (error) { - console.error('Failed to delete document:', error); - showToast('Failed to delete document', 'error'); - } - }} - isDarkMode={isDarkMode} - /> - ))} - - {/* Add New Document Card */} - setShowTemplateModal(true)} /> -
-
- - {/* Document Content */} - {loading ? ( -
-
Loading documents...
-
- ) : selectedDocument ? ( - // Show PRPViewer in beautiful mode for all documents - viewMode === 'beautiful' ? ( -
- -
- ) : ( - { - try { - setIsSaving(true); - - // Call backend API to persist changes - const savedDocument = await projectService.updateDocument( - project.id, - updatedDocument.id, - { - title: updatedDocument.title, - content: updatedDocument.content, - tags: updatedDocument.tags, - author: updatedDocument.author - } - ); - - // Update local state with backend response - setSelectedDocument(savedDocument); - setDocuments(prev => prev.map(doc => - doc.id === updatedDocument.id ? savedDocument : doc - )); - - console.log('Document saved via MilkdownEditor'); - showToast('Document saved successfully', 'success'); - } catch (error) { - console.error('Failed to save document:', error); - showToast( - error instanceof Error ? error.message : 'Failed to save document', - 'error' - ); - } finally { - setIsSaving(false); - } - }} - className="mb-8" - /> - ) - ) : ( -
- -

No documents found

-

Create a new document to get started

-
- )} - - {/* Knowledge Sections */} -
- technicalSources.find(source => source.id === id))} - onAddClick={() => setShowTechnicalModal(true)} - /> - businessSources.find(source => source.id === id))} - onAddClick={() => setShowBusinessModal(true)} - /> -
-
- - {/* Template Selection Modal */} - {showTemplateModal && ( - setShowTemplateModal(false)} - onSelectTemplate={createDocumentFromTemplate} - isCreating={isSaving} - /> - )} - - {/* Existing Modals (simplified for brevity) */} - {showTechnicalModal && ( - setShowTechnicalModal(false)} - onAddSource={() => openAddSourceModal('technical')} - /> - )} - - {showBusinessModal && ( - setShowBusinessModal(false)} - onAddSource={() => openAddSourceModal('business')} - /> - )} - - {showAddSourceModal && ( - setShowAddSourceModal(false)} - onSuccess={() => { - loadKnowledgeItems(); - setShowAddSourceModal(false); - }} - onStartCrawl={handleStartCrawl} - /> - )} - - {/* Version History Modal */} - {showVersionHistory && project && ( - setShowVersionHistory(false)} - onRestore={() => { - // Reload documents after restore - loadProjectDocuments(); - setShowVersionHistory(false); - }} - /> - )} -
- ); -}; - - -/* ——————————————————————————————————————————— */ -/* Helper components */ -/* ——————————————————————————————————————————— */ - -// ArchonEditor component removed - replaced with BlockNoteEditor - -// Template Modal Component -const TemplateModal: React.FC<{ - onClose: () => void; - onSelectTemplate: (templateKey: string) => void; - isCreating: boolean; -}> = ({ onClose, onSelectTemplate, isCreating }) => { - const templates = Object.entries(DOCUMENT_TEMPLATES); - - const getTemplateDescription = (key: string, template: any) => { - const descriptions: Record = { - 'prp_base': 'Comprehensive template for implementing new features with full context, validation loops, and structured implementation blueprint.', - 'prp_task': 'Focused template for specific tasks or bug fixes with clear steps and validation criteria.', - 'prp_planning': 'Strategic template for architecture planning and system design with risk analysis and success metrics.', - 'markdown_doc': 'Simple markdown document for general documentation and notes.', - 'meeting_notes': 'Structured template for meeting notes with attendees, agenda, and action items.' - }; - return descriptions[key] || 'Document template'; - }; - - return ( -
-
- -
-
-

- Choose a Template -

- -
- -
- {templates.map(([key, template]) => ( - - ))} -
- - {isCreating && ( -
-
- Creating document... -
- )} -
-
-
- ); -}; - -const KnowledgeSection: React.FC<{ - title: string; - color: 'blue' | 'purple' | 'pink' | 'orange'; - sources: any[]; - onAddClick: () => void; -}> = ({ - title, - color, - sources = [], - onAddClick -}) => { - const colorMap = { - blue: { - bg: 'bg-blue-500/10', - border: 'border-blue-500/30', - text: 'text-blue-600 dark:text-blue-400', - buttonBg: 'bg-blue-500/20', - buttonHover: 'hover:bg-blue-500/30', - buttonBorder: 'border-blue-500/40', - buttonShadow: 'hover:shadow-[0_0_15px_rgba(59,130,246,0.3)]' - }, - purple: { - bg: 'bg-purple-500/10', - border: 'border-purple-500/30', - text: 'text-purple-600 dark:text-purple-400', - buttonBg: 'bg-purple-500/20', - buttonHover: 'hover:bg-purple-500/30', - buttonBorder: 'border-purple-500/40', - buttonShadow: 'hover:shadow-[0_0_15px_rgba(168,85,247,0.3)]' - }, - pink: { - bg: 'bg-pink-500/10', - border: 'border-pink-500/30', - text: 'text-pink-600 dark:text-pink-400', - buttonBg: 'bg-pink-500/20', - buttonHover: 'hover:bg-pink-500/30', - buttonBorder: 'border-pink-500/40', - buttonShadow: 'hover:shadow-[0_0_15px_rgba(236,72,153,0.3)]' - }, - orange: { - bg: 'bg-orange-500/10', - border: 'border-orange-500/30', - text: 'text-orange-600 dark:text-orange-400', - buttonBg: 'bg-orange-500/20', - buttonHover: 'hover:bg-orange-500/30', - buttonBorder: 'border-orange-500/40', - buttonShadow: 'hover:shadow-[0_0_15px_rgba(249,115,22,0.3)]' - } - }; - return
-
-

- - {title} -

- -
-
-
- {sources && sources.length > 0 ?
- {sources.map(source => source &&
- {source.type === 'url' ? : } -
-
- {source.title} -
-
- Updated {source.lastUpdated} -
-
-
)} -
:
-

No knowledge sources added yet

-

- Click "Add Sources" to select relevant documents -

-
} -
-
; -}; - -const SourceSelectionModal: React.FC<{ - title: string; - sources: any[]; - selectedSources: string[]; - onToggleSource: (id: string) => void; - onSave: () => void; - onClose: () => void; - onAddSource: () => void; -}> = ({ - title, - sources, - selectedSources, - onToggleSource, - onSave, - onClose, - onAddSource -}) => { - const [searchQuery, setSearchQuery] = useState(''); - // Filter sources based on search query - const filteredSources = sources.filter(source => source.title.toLowerCase().includes(searchQuery.toLowerCase())); - return
-
-
-
-

- {title} -

- -
- {/* Search and Add Source */} -
-
- - setSearchQuery(e.target.value)} placeholder="Search sources..." className="w-full bg-white/50 dark:bg-black/70 border border-gray-300 dark:border-gray-700 text-gray-900 dark:text-white rounded-md py-2 pl-10 pr-3 focus:outline-none focus:border-blue-400 focus:shadow-[0_0_10px_rgba(59,130,246,0.2)] transition-all duration-300" /> -
- -
- {/* Sources List */} -
- {filteredSources.length > 0 ?
- {filteredSources.map(source =>
onToggleSource(source.id)} className={`flex items-center gap-3 p-3 rounded-md cursor-pointer transition-all duration-200 - ${selectedSources.includes(source.id) ? 'bg-blue-100/80 dark:bg-blue-900/30 border border-blue-300 dark:border-blue-500/50' : 'bg-white/50 dark:bg-black/30 border border-gray-200 dark:border-gray-800 hover:border-gray-300 dark:hover:border-gray-700'}`}> -
- {selectedSources.includes(source.id) && } -
- {source.type === 'url' ? : } -
-
- {source.title} -
-
- Updated {source.lastUpdated} -
-
-
)} -
:
- No sources found matching your search -
} -
- {/* Action Buttons */} -
- - -
-
-
-
; -}; - -interface AddKnowledgeModalProps { - sourceType: 'technical' | 'business'; - onClose: () => void; - onSuccess: () => void; - onStartCrawl: (progressId: string, initialData: Partial) => void; -} - -const AddKnowledgeModal = ({ - sourceType, - onClose, - onSuccess, - onStartCrawl -}: AddKnowledgeModalProps) => { - const [method, setMethod] = useState<'url' | 'file'>('url'); - const [url, setUrl] = useState(''); - const [updateFrequency, setUpdateFrequency] = useState('7'); - const [tags, setTags] = useState([]); - const [newTag, setNewTag] = useState(''); - const [selectedFile, setSelectedFile] = useState(null); - const [loading, setLoading] = useState(false); - const { showToast } = useToast(); - - const handleSubmit = async () => { - try { - setLoading(true); - - if (method === 'url') { - if (!url.trim()) { - showToast('Please enter a URL', 'error'); - return; - } - - const result = await knowledgeBaseService.crawlUrl({ - url: url.trim(), - knowledge_type: sourceType, - tags, - update_frequency: parseInt(updateFrequency) - }); - - // Check if result contains a progressId for streaming - if ((result as any).progressId) { - // Start progress tracking - onStartCrawl((result as any).progressId, { - currentUrl: url.trim(), - totalPages: 0, - processedPages: 0 - }); - - showToast('Crawling started - tracking progress', 'success'); - onClose(); // Close modal immediately - } else { - // Fallback for non-streaming response - showToast((result as any).message || 'Crawling started', 'success'); - onSuccess(); - } - } else { - if (!selectedFile) { - showToast('Please select a file', 'error'); - return; - } - - const result = await knowledgeBaseService.uploadDocument(selectedFile, { - knowledge_type: sourceType, - tags - }); - - showToast((result as any).message || 'Document uploaded successfully', 'success'); - onSuccess(); - } - } catch (error) { - console.error('Failed to add knowledge:', error); - showToast('Failed to add knowledge source', 'error'); - } finally { - setLoading(false); - } - }; - - return
- -

- Add {sourceType === 'technical' ? 'Technical' : 'Business'} Knowledge Source -

- - {/* Source Type Selection */} -
- - -
- - {/* URL Input */} - {method === 'url' &&
- setUrl(e.target.value)} placeholder="https://..." accentColor="blue" /> -
} - - {/* File Upload */} - {method === 'file' &&
- - setSelectedFile(e.target.files?.[0] || null)} - className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-purple-50 file:text-purple-700 hover:file:bg-purple-100" - /> -

- Supports PDF, MD, DOC up to 10MB -

-
} - - {/* Update Frequency */} - {method === 'url' &&
- setNewTag(e.target.value)} onKeyDown={e => { - if (e.key === 'Enter' && newTag.trim()) { - setTags([...tags, newTag.trim()]); - setNewTag(''); - } - }} placeholder="Add tags..." accentColor="purple" /> -
- - {/* Action Buttons */} -
- - -
-
-
; -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/project-tasks/DocumentCard.tsx b/archon-ui-main/src/components/project-tasks/DocumentCard.tsx deleted file mode 100644 index e6ec5b9b..00000000 --- a/archon-ui-main/src/components/project-tasks/DocumentCard.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React, { useState } from 'react'; -import { Rocket, Code, Briefcase, Users, FileText, X, Plus, Clipboard } from 'lucide-react'; -import { useToast } from '../../contexts/ToastContext'; - -export interface ProjectDoc { - id: string; - title: string; - content: any; - document_type?: string; - updated_at: string; - created_at?: string; -} - -interface DocumentCardProps { - document: ProjectDoc; - isActive: boolean; - onSelect: (doc: ProjectDoc) => void; - onDelete: (docId: string) => void; - isDarkMode: boolean; -} - -export const DocumentCard: React.FC = ({ - document, - isActive, - onSelect, - onDelete, - isDarkMode -}) => { - const [showDelete, setShowDelete] = useState(false); - const { showToast } = useToast(); - - const getDocumentIcon = (type?: string) => { - switch (type) { - case 'prp': return ; - case 'technical': return ; - case 'business': return ; - case 'meeting_notes': return ; - default: return ; - } - }; - - const getTypeColor = (type?: string) => { - switch (type) { - case 'prp': return 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/30'; - case 'technical': return 'bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/30'; - case 'business': return 'bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/30'; - case 'meeting_notes': return 'bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/30'; - default: return 'bg-gray-500/10 text-gray-600 dark:text-gray-400 border-gray-500/30'; - } - }; - - const handleCopyId = (e: React.MouseEvent) => { - e.stopPropagation(); - navigator.clipboard.writeText(document.id); - showToast('Document ID copied to clipboard', 'success'); - - // Visual feedback - const button = e.currentTarget; - const originalHTML = button.innerHTML; - button.innerHTML = '
Copied
'; - setTimeout(() => { - button.innerHTML = originalHTML; - }, 2000); - }; - - return ( -
onSelect(document)} - onMouseEnter={() => setShowDelete(true)} - onMouseLeave={() => setShowDelete(false)} - > - {/* Document Type Badge */} -
- {getDocumentIcon(document.document_type)} - {document.document_type || 'document'} -
- - {/* Title */} -

- {document.title} -

- - {/* Metadata */} -

- {new Date(document.updated_at || document.created_at || Date.now()).toLocaleDateString()} -

- - {/* ID Display Section - Always visible for active, hover for others */} -
- - {document.id.slice(0, 8)}... - - -
- - {/* Delete Button */} - {showDelete && !isActive && ( - - )} -
- ); -}; - -// New Document Card Component -interface NewDocumentCardProps { - onClick: () => void; -} - -export const NewDocumentCard: React.FC = ({ onClick }) => { - return ( -
- - New Document -
- ); -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/project-tasks/DraggableTaskCard.tsx b/archon-ui-main/src/components/project-tasks/DraggableTaskCard.tsx deleted file mode 100644 index a610030f..00000000 --- a/archon-ui-main/src/components/project-tasks/DraggableTaskCard.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import React, { useRef, useState } from 'react'; -import { useDrag, useDrop } from 'react-dnd'; -import { Edit, Trash2, RefreshCw, Tag, User, Bot, Clipboard } from 'lucide-react'; -import { Task } from './TaskTableView'; -import { ItemTypes, getAssigneeIcon, getAssigneeGlow, getOrderColor, getOrderGlow } from '../../lib/task-utils'; - -export interface DraggableTaskCardProps { - task: Task; - index: number; - onView: () => void; - onComplete: () => void; - onDelete: (task: Task) => void; - onTaskReorder: (taskId: string, targetIndex: number, status: Task['status']) => void; - tasksInStatus: Task[]; - allTasks?: Task[]; - hoveredTaskId?: string | null; - onTaskHover?: (taskId: string | null) => void; -} - -export const DraggableTaskCard = ({ - task, - index, - onView, - onDelete, - onTaskReorder, - allTasks = [], - hoveredTaskId, - onTaskHover, -}: DraggableTaskCardProps) => { - - const [{ isDragging }, drag] = useDrag({ - type: ItemTypes.TASK, - item: { id: task.id, status: task.status, index }, - collect: (monitor) => ({ - isDragging: !!monitor.isDragging() - }) - }); - - const [, drop] = useDrop({ - accept: ItemTypes.TASK, - hover: (draggedItem: { id: string; status: Task['status']; index: number }, monitor) => { - if (!monitor.isOver({ shallow: true })) return; - if (draggedItem.id === task.id) return; - if (draggedItem.status !== task.status) return; - - const draggedIndex = draggedItem.index; - const hoveredIndex = index; - - if (draggedIndex === hoveredIndex) return; - - console.log('BOARD HOVER: Moving task', draggedItem.id, 'from index', draggedIndex, 'to', hoveredIndex, 'in status', task.status); - - // Move the task immediately for visual feedback (same pattern as table view) - onTaskReorder(draggedItem.id, hoveredIndex, task.status); - - // Update the dragged item's index to prevent re-triggering - draggedItem.index = hoveredIndex; - } - }); - - const [isFlipped, setIsFlipped] = useState(false); - - const toggleFlip = (e: React.MouseEvent) => { - e.stopPropagation(); - setIsFlipped(!isFlipped); - }; - - // Calculate hover effects for parent-child relationships - const getRelatedTaskIds = () => { - const relatedIds = new Set(); - - return relatedIds; - }; - - const relatedTaskIds = getRelatedTaskIds(); - const isHighlighted = hoveredTaskId ? relatedTaskIds.has(hoveredTaskId) || hoveredTaskId === task.id : false; - - const handleMouseEnter = () => { - onTaskHover?.(task.id); - }; - - const handleMouseLeave = () => { - onTaskHover?.(null); - }; - - - // Card styling - using CSS-based height animation for better scrolling - - // Card styling constants - const cardScale = 'scale-100'; - const cardOpacity = 'opacity-100'; - - // Subtle highlight effect for related tasks - applied to the card, not parent - const highlightGlow = isHighlighted - ? 'border-cyan-400/50 shadow-[0_0_8px_rgba(34,211,238,0.2)]' - : ''; - - // Simplified hover effect - just a glowing border - const hoverEffectClasses = 'group-hover:border-cyan-400/70 dark:group-hover:border-cyan-500/50 group-hover:shadow-[0_0_15px_rgba(34,211,238,0.4)] dark:group-hover:shadow-[0_0_15px_rgba(34,211,238,0.6)]'; - - // Base card styles with proper rounded corners - const cardBaseStyles = 'bg-gradient-to-b from-white/80 to-white/60 dark:from-white/10 dark:to-black/30 border border-gray-200 dark:border-gray-700 rounded-lg'; - - // Transition settings - const transitionStyles = 'transition-all duration-200 ease-in-out'; - - return ( -
drag(drop(node))} - style={{ - perspective: '1000px', - transformStyle: 'preserve-3d' - }} - className={`flip-card w-full min-h-[140px] cursor-move relative ${cardScale} ${cardOpacity} ${isDragging ? 'opacity-50 scale-90' : ''} ${transitionStyles} group`} - onMouseEnter={handleMouseEnter} - onMouseLeave={handleMouseLeave} - > -
- {/* Front side with subtle hover effect */} -
- {/* Priority indicator */} -
- - {/* Content container with fixed padding - exactly matching back side structure */} -
-
-
- - {task.feature} -
- - {/* Task order display */} -
- {task.task_order} -
- - {/* Action buttons group */} -
- - - -
-
- -

- {task.title} -

- - {/* Spacer to push assignee section to bottom */} -
- -
-
-
- {getAssigneeIcon(task.assignee?.name || 'User')} -
- {task.assignee?.name || 'User'} -
- -
-
-
- - {/* Back side */} - {/* Back side with same hover effect */} -
- {/* Priority indicator */} -
- - {/* Content container with fixed padding */} -
-
-

- {task.title} -

- -
- - {/* Description container with absolute positioning inside parent bounds */} -
-
-

{task.description}

-
-
-
-
-
-
- ); -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/project-tasks/EditTaskModal.tsx b/archon-ui-main/src/components/project-tasks/EditTaskModal.tsx deleted file mode 100644 index 4a7f14a7..00000000 --- a/archon-ui-main/src/components/project-tasks/EditTaskModal.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import React, { memo, useCallback, useMemo, useState, useEffect, useRef } from 'react'; -import { X } from 'lucide-react'; -import { Button } from '../ui/Button'; -import { ArchonLoadingSpinner } from '../animations/Animations'; -import { DebouncedInput, FeatureInput } from './TaskInputComponents'; -import type { Task } from './TaskTableView'; - -interface EditTaskModalProps { - isModalOpen: boolean; - editingTask: Task | null; - projectFeatures: any[]; - isLoadingFeatures: boolean; - isSavingTask: boolean; - onClose: () => void; - onSave: (task: Task) => Promise; - getTasksForPrioritySelection: (status: Task['status']) => Array<{value: number, label: string}>; -} - -const ASSIGNEE_OPTIONS = ['User', 'Archon', 'AI IDE Agent'] as const; - -// Removed debounce utility - now using DebouncedInput component - -export const EditTaskModal = memo(({ - isModalOpen, - editingTask, - projectFeatures, - isLoadingFeatures, - isSavingTask, - onClose, - onSave, - getTasksForPrioritySelection -}: EditTaskModalProps) => { - const [localTask, setLocalTask] = useState(null); - - // Diagnostic: Track render count - const renderCount = useRef(0); - - useEffect(() => { - renderCount.current++; - console.log(`[EditTaskModal] Render #${renderCount.current}`, { - localTask: localTask?.title, - isModalOpen, - timestamp: Date.now() - }); - }); - - // Sync local state with editingTask when it changes - useEffect(() => { - if (editingTask) { - setLocalTask(editingTask); - } - }, [editingTask]); - - const priorityOptions = useMemo(() => { - console.log(`[EditTaskModal] Recalculating priorityOptions for status: ${localTask?.status || 'backlog'}`); - return getTasksForPrioritySelection(localTask?.status || 'backlog'); - }, [localTask?.status, getTasksForPrioritySelection]); - - // Memoized handlers for input changes - const handleTitleChange = useCallback((value: string) => { - console.log('[EditTaskModal] Title changed via DebouncedInput:', value); - setLocalTask(prev => prev ? { ...prev, title: value } : null); - }, []); - - const handleDescriptionChange = useCallback((value: string) => { - console.log('[EditTaskModal] Description changed via DebouncedInput:', value); - setLocalTask(prev => prev ? { ...prev, description: value } : null); - }, []); - - const handleFeatureChange = useCallback((value: string) => { - console.log('[EditTaskModal] Feature changed via FeatureInput:', value); - setLocalTask(prev => prev ? { ...prev, feature: value } : null); - }, []); - - const handleStatusChange = useCallback((e: React.ChangeEvent) => { - const newStatus = e.target.value as Task['status']; - const newOrder = getTasksForPrioritySelection(newStatus)[0]?.value || 1; - setLocalTask(prev => prev ? { ...prev, status: newStatus, task_order: newOrder } : null); - }, [getTasksForPrioritySelection]); - - const handlePriorityChange = useCallback((e: React.ChangeEvent) => { - setLocalTask(prev => prev ? { ...prev, task_order: parseInt(e.target.value) } : null); - }, []); - - const handleAssigneeChange = useCallback((e: React.ChangeEvent) => { - setLocalTask(prev => prev ? { - ...prev, - assignee: { name: e.target.value as 'User' | 'Archon' | 'AI IDE Agent', avatar: '' } - } : null); - }, []); - - const handleSave = useCallback(() => { - if (localTask) { - onSave(localTask); - } - }, [localTask, onSave]); - - const handleClose = useCallback(() => { - onClose(); - }, [onClose]); - - if (!isModalOpen) return null; - - return ( -
-
-
-
-

- {editingTask?.id ? 'Edit Task' : 'New Task'} -

- -
- -
-
- - -
- -
- - -
- -
-
- - -
- -
- - -
-
- -
-
- - -
- -
- - -
-
-
- - -
- - -
-
-
-
- ); -}, (prevProps, nextProps) => { - // Custom comparison function to prevent unnecessary re-renders - // Only re-render if these specific props change - const isEqual = ( - prevProps.isModalOpen === nextProps.isModalOpen && - prevProps.editingTask?.id === nextProps.editingTask?.id && - prevProps.editingTask?.title === nextProps.editingTask?.title && - prevProps.editingTask?.description === nextProps.editingTask?.description && - prevProps.editingTask?.status === nextProps.editingTask?.status && - prevProps.editingTask?.assignee?.name === nextProps.editingTask?.assignee?.name && - prevProps.editingTask?.feature === nextProps.editingTask?.feature && - prevProps.editingTask?.task_order === nextProps.editingTask?.task_order && - prevProps.isSavingTask === nextProps.isSavingTask && - prevProps.isLoadingFeatures === nextProps.isLoadingFeatures && - prevProps.projectFeatures === nextProps.projectFeatures // Reference equality check - ); - - if (!isEqual) { - console.log('[EditTaskModal] Props changed, re-rendering'); - } - - return isEqual; -}); - -EditTaskModal.displayName = 'EditTaskModal'; \ No newline at end of file diff --git a/archon-ui-main/src/components/project-tasks/FeaturesTab.tsx b/archon-ui-main/src/components/project-tasks/FeaturesTab.tsx deleted file mode 100644 index 10add1b4..00000000 --- a/archon-ui-main/src/components/project-tasks/FeaturesTab.tsx +++ /dev/null @@ -1,814 +0,0 @@ -import { useCallback, useState, useEffect, useMemo } from 'react' -import '@xyflow/react/dist/style.css' -import { - ReactFlow, - Node, - Edge, - Controls, - MarkerType, - NodeProps, - Handle, - Position, - NodeChange, - applyNodeChanges, - EdgeChange, - applyEdgeChanges, - Connection, - addEdge, -} from '@xyflow/react' -import { Layout, Component as ComponentIcon, X, Trash2, Edit, Save } from 'lucide-react' -import { projectService } from '../../services/projectService' -import { useToast } from '../../contexts/ToastContext' - -// Define custom node types following React Flow v12 pattern -type PageNodeData = { - label: string; - type: string; - route: string; - components: number; -}; - -type ServiceNodeData = { - label: string; - type: string; -}; - -// Define union type for all custom nodes -type CustomNodeTypes = Node | Node; - -// Custom node components -const PageNode = ({ data }: NodeProps) => { - const pageData = data as PageNodeData; - return ( -
- -
-
- -
{pageData.label}
-
-
{pageData.type}
-
-
Route: {pageData.route}
-
Components: {pageData.components}
-
-
- -
- ); -}; - -const ServiceNode = ({ data }: NodeProps) => { - const serviceData = data as ServiceNodeData; - return ( -
- -
-
- -
{serviceData.label}
-
-
{serviceData.type}
-
- -
- ); -}; - -const nodeTypes = { - page: PageNode, - service: ServiceNode, -} - -// Default/fallback nodes for when project has no features data -const defaultNodes: Node[] = [ - { - id: 'start', - type: 'page', - data: { - label: 'Start App', - type: 'Entry Point', - route: '/', - components: 3, - }, - position: { - x: 400, - y: 0, - }, - }, - { - id: 'home', - type: 'page', - data: { - label: 'Homepage', - type: 'Main View', - route: '/home', - components: 6, - }, - position: { - x: 400, - y: 150, - }, - }, -]; - -// Default/fallback edges -const defaultEdges: Edge[] = [ - { - id: 'start-home', - source: 'start', - target: 'home', - animated: true, - style: { - stroke: '#22d3ee', - }, - markerEnd: { - type: MarkerType.ArrowClosed, - color: '#22d3ee', - }, - }, -]; - -interface FeaturesTabProps { - project?: { - id: string; - title: string; - features?: any[]; - } | null; -} - -export const FeaturesTab = ({ project }: FeaturesTabProps) => { - const [nodes, setNodes] = useState([]) - const [edges, setEdges] = useState([]) - const [loading, setLoading] = useState(true) - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) - const [nodeToDelete, setNodeToDelete] = useState(null) - const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false) - const [editingNode, setEditingNode] = useState(null) - const [showEditModal, setShowEditModal] = useState(false) - const [isSaving, setIsSaving] = useState(false) - - const { showToast } = useToast() - - // Load features from project or show empty state - useEffect(() => { - if (project?.features && Array.isArray(project.features) && project.features.length > 0) { - // Ensure all nodes have required properties with defaults - const normalizedNodes = project.features.map((node: any, index: number) => ({ - ...node, - // Ensure position exists with sensible defaults - position: node.position || { - x: 250 + (index % 3) * 250, // Spread horizontally - y: 200 + Math.floor(index / 3) * 150 // Stack vertically - }, - // Ensure type exists (fallback based on data structure) - type: node.type || (node.data?.route ? 'page' : 'service'), - // Ensure data exists - data: node.data || { label: 'Unknown', type: 'Unknown Component' } - })); - - setNodes(normalizedNodes) - // Generate edges based on the flow (simplified logic) - const generatedEdges = generateEdgesFromNodes(normalizedNodes) - setEdges(generatedEdges) - } else { - // Show empty state - no nodes or edges - setNodes([]) - setEdges([]) - } - setLoading(false) - }, [project]) - - // Helper function to generate edges based on node positioning and types - const generateEdgesFromNodes = (nodes: Node[]): Edge[] => { - const edges: Edge[] = [] - - // Sort nodes by y position to create a logical flow (with safety check for position) - const sortedNodes = [...nodes].sort((a, b) => { - const aY = a.position?.y || 0; - const bY = b.position?.y || 0; - return aY - bY; - }) - - for (let i = 0; i < sortedNodes.length - 1; i++) { - const currentNode = sortedNodes[i] - const nextNode = sortedNodes[i + 1] - - // Connect sequential nodes with appropriate styling - const edgeStyle = currentNode.type === 'service' ? '#d946ef' : '#22d3ee' - - edges.push({ - id: `${currentNode.id}-${nextNode.id}`, - source: currentNode.id, - target: nextNode.id, - animated: true, - style: { - stroke: edgeStyle, - }, - markerEnd: { - type: MarkerType.ArrowClosed, - color: edgeStyle, - }, - }) - } - - return edges - } - - const onNodesChange = useCallback( - (changes: NodeChange[]) => { - setNodes((nds) => applyNodeChanges(changes, nds)) - setHasUnsavedChanges(true) - }, - [], - ) - - const onEdgesChange = useCallback( - (changes: EdgeChange[]) => { - setEdges((eds) => applyEdgeChanges(changes, eds)) - setHasUnsavedChanges(true) - }, - [], - ) - - const onConnect = useCallback( - (connection: Connection) => { - const sourceNode = nodes.find((node) => node.id === connection.source) - // Set edge color based on source node type - const edgeStyle = - sourceNode?.type === 'service' - ? { - stroke: '#d946ef', - } - : // Fuchsia for service nodes - { - stroke: '#22d3ee', - } // Cyan for page nodes - setEdges((eds) => - addEdge( - { - ...connection, - animated: true, - style: edgeStyle, - markerEnd: { - type: MarkerType.ArrowClosed, - color: edgeStyle.stroke, - }, - }, - eds, - ), - ) - setHasUnsavedChanges(true) - }, - [nodes], - ) - - const saveToDatabase = async (nodesToSave = nodes, edgesToSave = edges) => { - if (!project?.id) { - console.error('❌ No project ID available for saving features'); - return; - } - - setIsSaving(true); - try { - console.log('💾 Saving features to database...'); - await projectService.updateProject(project.id, { - features: nodesToSave - }); - console.log('✅ Features saved successfully'); - setHasUnsavedChanges(false); - } catch (error) { - console.error('❌ Failed to save features:', error); - throw error; - } finally { - setIsSaving(false); - } - }; - - const handleManualSave = async () => { - await saveToDatabase(); - }; - - const addPageNode = async () => { - const newNode: Node = { - id: `page-${Date.now()}`, - type: 'page', - data: { - label: `New Page`, - type: 'Page Component', - route: '/new-page', - components: 0, - }, - position: { - x: 250, - y: 200, - }, - } - const newNodes = [...nodes, newNode]; - setNodes(newNodes); - setHasUnsavedChanges(true); - - // Auto-save when adding - try { - await saveToDatabase(newNodes, edges); - } catch (error) { - // Revert on error - setNodes(nodes); - } - } - - const addServiceNode = async () => { - const newNode: Node = { - id: `service-${Date.now()}`, - type: 'service', - data: { - label: 'New Service', - type: 'Service Component', - }, - position: { - x: 250, - y: 200, - }, - } - const newNodes = [...nodes, newNode]; - setNodes(newNodes); - setHasUnsavedChanges(true); - - // Auto-save when adding - try { - await saveToDatabase(newNodes, edges); - } catch (error) { - // Revert on error - setNodes(nodes); - } - } - - const handleDeleteNode = useCallback(async (event: React.MouseEvent, nodeId: string) => { - event.stopPropagation(); - - if (!project?.id) { - console.error('❌ No project ID available for deleting node'); - return; - } - - // Show custom confirmation dialog - setNodeToDelete(nodeId); - setShowDeleteConfirm(true); - }, [project?.id]); - - const confirmDelete = useCallback(async () => { - if (!nodeToDelete) return; - - console.log('🗑️ Deleting node:', nodeToDelete); - - try { - // Remove node from UI - const newNodes = nodes.filter(node => node.id !== nodeToDelete); - - // Remove any edges connected to this node - const newEdges = edges.filter(edge => - edge.source !== nodeToDelete && edge.target !== nodeToDelete - ); - - setNodes(newNodes); - setEdges(newEdges); - - // Save to database - await saveToDatabase(newNodes, newEdges); - showToast('Node deleted successfully', 'success'); - - // Close confirmation dialog - setShowDeleteConfirm(false); - setNodeToDelete(null); - } catch (error) { - console.error('❌ Failed to delete node:', error); - // Revert UI changes on error - setNodes(nodes); - setEdges(edges); - showToast('Failed to delete node', 'error'); - } - }, [nodeToDelete, nodes, edges]); - - const cancelDelete = useCallback(() => { - setShowDeleteConfirm(false); - setNodeToDelete(null); - }, []); - - const handleNodeClick = useCallback((event: React.MouseEvent, node: Node) => { - setEditingNode(node); - setShowEditModal(true); - }, []); - - const saveNodeChanges = async (updatedNode: Node) => { - // Update local state first - const newNodes = nodes.map(node => - node.id === updatedNode.id ? updatedNode : node - ); - setNodes(newNodes); - - // Save to database - await saveToDatabase(newNodes, edges); - - setShowEditModal(false); - setEditingNode(null); - }; - - // Memoize node types with delete and edit functionality - const nodeTypes = useMemo(() => ({ - page: ({ data, id }: NodeProps) => { - const pageData = data as any; - return ( -
- -
{ - const actualNode = nodes.find(node => node.id === id); - if (actualNode) { - handleNodeClick(e, actualNode); - } - }} - > -
-
- -
{pageData.label}
-
-
- - -
-
-
{pageData.type}
-
-
Route: {pageData.route}
-
Components: {pageData.components}
-
-
- -
- ); - }, - service: ({ data, id }: NodeProps) => { - const serviceData = data as any; - return ( -
- -
{ - const actualNode = nodes.find(node => node.id === id); - if (actualNode) { - handleNodeClick(e, actualNode); - } - }} - > -
-
- -
{serviceData.label}
-
-
- - -
-
-
{serviceData.type}
-
- -
- ); - } - }), [handleNodeClick, handleDeleteNode, nodes]); - - if (loading) { - return ( -
-
Loading features...
-
- ) - } - - return ( -
-
-
-
-
- - Feature Planner {project?.features ? `(${project.features.length} features)` : '(Default)'} -
-
- {hasUnsavedChanges && ( - - )} - - -
-
-
- {/* Subtle neon glow at the top */} -
- {nodes.length === 0 ? ( -
- -

No features defined

-

Add pages and services to get started

-
- ) : ( - - - - )} -
- - {/* Delete Confirmation Modal */} - {showDeleteConfirm && ( - n.id === nodeToDelete)?.data.label as string || 'node'} - /> - )} - - {/* Edit Modal */} - {showEditModal && editingNode && ( - { - setShowEditModal(false); - setEditingNode(null); - }} - /> - )} -
-
- ) -} - -// Delete Confirmation Modal Component -const DeleteConfirmModal = ({ - onConfirm, - onCancel, - nodeName -}: { - onConfirm: () => void; - onCancel: () => void; - nodeName: string; -}) => { - return ( -
-
- -
-
-
- -
-
-

- Delete Node -

-

- This action cannot be undone -

-
-
- -

- Are you sure you want to delete "{nodeName}"? - This will also remove all related connections. -

- -
- - -
-
-
-
- ); -}; - -// Edit Feature Modal Component -const EditFeatureModal = ({ - node, - onSave, - onClose -}: { - node: Node; - onSave: (node: Node) => void; - onClose: () => void; -}) => { - const [name, setName] = useState(node.data.label as string); - const [route, setRoute] = useState((node.data as any).route || ''); - const [components, setComponents] = useState((node.data as any).components || 0); - - const isPageNode = node.type === 'page'; - - const handleSave = () => { - const updatedNode = { - ...node, - data: { - ...node.data, - label: name, - ...(isPageNode && { route, components }) - } - }; - onSave(updatedNode); - }; - - return ( -
-
-
-

- {isPageNode ? : } - Edit {isPageNode ? 'Page' : 'Service'} -

- -
- -
-
- - setName(e.target.value)} - className="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-cyan-500 focus:outline-none" - /> -
- - {isPageNode && ( - <> -
- - setRoute(e.target.value)} - className="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-cyan-500 focus:outline-none" - placeholder="/example-page" - /> -
- -
- - setComponents(parseInt(e.target.value) || 0)} - className="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-cyan-500 focus:outline-none" - min="0" - /> -
- - )} -
- -
- - -
-
-
- ); -}; diff --git a/archon-ui-main/src/components/project-tasks/MilkdownEditor.css b/archon-ui-main/src/components/project-tasks/MilkdownEditor.css deleted file mode 100644 index c9c272d7..00000000 --- a/archon-ui-main/src/components/project-tasks/MilkdownEditor.css +++ /dev/null @@ -1,277 +0,0 @@ -/* Milkdown Editor Custom Styles - Archon Theme */ - -/* Main editor container */ -.milkdown-crepe-editor { - background: rgba(255, 255, 255, 0.5); - backdrop-filter: blur(10px); - border: 1px solid rgba(59, 130, 246, 0.3); - border-radius: 12px; - padding: 1.5rem; - position: relative; - overflow: hidden; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); -} - -/* Gradient border effect */ -.milkdown-crepe-editor::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - background: linear-gradient(90deg, #3b82f6, #a855f7); - opacity: 0.8; -} - -/* Dark mode container */ -.dark .milkdown-crepe-editor { - background: rgba(0, 0, 0, 0.3); - backdrop-filter: blur(20px); - border-color: rgba(59, 130, 246, 0.5); - box-shadow: 0 0 20px rgba(59, 130, 246, 0.1); -} - -/* Remove default Crepe theme styling */ -.milkdown-crepe-editor .milkdown { - background: transparent !important; - border: none !important; - box-shadow: none !important; -} - -/* Editor content area */ -.milkdown-crepe-editor .ProseMirror { - font-family: Inter, system-ui, -apple-system, sans-serif; - min-height: 400px; - max-width: 100%; - padding: 1rem; - background: transparent; - color: #1f2937; - line-height: 1.6; -} - -.dark .milkdown-crepe-editor .ProseMirror { - color: #f9fafb; -} - -/* Remove dark mode filter - use proper theming instead */ -.milkdown-theme-dark { - filter: none; -} - -/* Typography */ -.milkdown-crepe-editor h1 { - font-size: 2rem; - font-weight: 700; - margin-bottom: 1rem; - color: #111827; -} - -.dark .milkdown-crepe-editor h1 { - color: #f9fafb; -} - -.milkdown-crepe-editor h2 { - font-size: 1.5rem; - font-weight: 600; - margin-top: 1.5rem; - margin-bottom: 0.75rem; - color: #374151; -} - -.dark .milkdown-crepe-editor h2 { - color: #e5e7eb; -} - -.milkdown-crepe-editor h3 { - font-size: 1.25rem; - font-weight: 600; - margin-top: 1rem; - margin-bottom: 0.5rem; - color: #4b5563; -} - -.dark .milkdown-crepe-editor h3 { - color: #d1d5db; -} - -/* Links */ -.milkdown-crepe-editor a { - color: #3b82f6; - text-decoration: none; - transition: color 0.2s; -} - -.milkdown-crepe-editor a:hover { - color: #2563eb; - text-decoration: underline; -} - -.dark .milkdown-crepe-editor a { - color: #60a5fa; -} - -.dark .milkdown-crepe-editor a:hover { - color: #93bbfc; -} - -/* Code blocks */ -.milkdown-crepe-editor pre { - background: rgba(0, 0, 0, 0.05); - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 8px; - padding: 1rem; - overflow-x: auto; - font-family: 'JetBrains Mono', 'Fira Code', monospace; -} - -.dark .milkdown-crepe-editor pre { - background: rgba(255, 255, 255, 0.05); - border-color: rgba(255, 255, 255, 0.1); -} - -.milkdown-crepe-editor code { - background: rgba(59, 130, 246, 0.1); - padding: 0.125rem 0.375rem; - border-radius: 4px; - font-size: 0.875rem; - font-family: 'JetBrains Mono', 'Fira Code', monospace; -} - -.dark .milkdown-crepe-editor code { - background: rgba(59, 130, 246, 0.2); -} - -/* Lists */ -.milkdown-crepe-editor ul, -.milkdown-crepe-editor ol { - padding-left: 1.5rem; - margin: 0.5rem 0; -} - -.milkdown-crepe-editor li { - margin: 0.25rem 0; -} - -/* Blockquotes */ -.milkdown-crepe-editor blockquote { - border-left: 4px solid #3b82f6; - padding-left: 1rem; - margin: 1rem 0; - color: #6b7280; - font-style: italic; -} - -.dark .milkdown-crepe-editor blockquote { - color: #9ca3af; - border-left-color: #60a5fa; -} - -/* Tables */ -.milkdown-crepe-editor table { - border-collapse: collapse; - width: 100%; - margin: 1rem 0; -} - -.milkdown-crepe-editor th, -.milkdown-crepe-editor td { - border: 1px solid rgba(0, 0, 0, 0.1); - padding: 0.5rem; - text-align: left; -} - -.dark .milkdown-crepe-editor th, -.dark .milkdown-crepe-editor td { - border-color: rgba(255, 255, 255, 0.1); -} - -.milkdown-crepe-editor th { - background: rgba(59, 130, 246, 0.05); - font-weight: 600; -} - -.dark .milkdown-crepe-editor th { - background: rgba(59, 130, 246, 0.1); -} - -/* Toolbar styling */ -.milkdown-crepe-editor .milkdown-toolbar { - background: transparent; - border: none; - padding: 0; - margin-bottom: 1rem; -} - -/* Toolbar buttons */ -.milkdown-crepe-editor .toolbar-item { - background: rgba(255, 255, 255, 0.8); - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 6px; - padding: 0.375rem 0.75rem; - margin: 0 0.25rem; - cursor: pointer; - transition: all 0.2s; - color: #374151; -} - -.dark .milkdown-crepe-editor .toolbar-item { - background: rgba(255, 255, 255, 0.1); - border-color: rgba(255, 255, 255, 0.2); - color: #e5e7eb; -} - -.milkdown-crepe-editor .toolbar-item:hover { - background: #3b82f6; - border-color: #3b82f6; - color: white; - transform: translateY(-1px); - box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3); -} - -/* Selection */ -.milkdown-crepe-editor .ProseMirror ::selection { - background: rgba(59, 130, 246, 0.3); -} - -.dark .milkdown-crepe-editor .ProseMirror ::selection { - background: rgba(96, 165, 250, 0.3); -} - -/* Focus state */ -.milkdown-crepe-editor .ProseMirror:focus { - outline: none; -} - -/* Placeholder */ -.milkdown-crepe-editor .ProseMirror.is-empty::before { - content: 'Start writing...'; - color: #9ca3af; - position: absolute; - pointer-events: none; -} - -/* Horizontal rule */ -.milkdown-crepe-editor hr { - border: none; - height: 1px; - background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.5), transparent); - margin: 2rem 0; -} - -/* Save button animation */ -@keyframes pulse-glow { - 0% { - box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); - } - 70% { - box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); - } - 100% { - box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); - } -} - -.save-button-pulse { - animation: pulse-glow 2s infinite; -} \ No newline at end of file diff --git a/archon-ui-main/src/components/project-tasks/MilkdownEditor.tsx b/archon-ui-main/src/components/project-tasks/MilkdownEditor.tsx deleted file mode 100644 index 6f945ccb..00000000 --- a/archon-ui-main/src/components/project-tasks/MilkdownEditor.tsx +++ /dev/null @@ -1,555 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Crepe, CrepeFeature } from '@milkdown/crepe'; -import '@milkdown/crepe/theme/common/style.css'; -import '@milkdown/crepe/theme/frame.css'; -import '@milkdown/crepe/theme/frame-dark.css'; -import './MilkdownEditor.css'; -import { Save, Undo } from 'lucide-react'; - -interface MilkdownEditorProps { - document: { - id: string; - title: string; - content?: any; - created_at: string; - updated_at: string; - }; - onSave: (document: any) => void; - className?: string; - isDarkMode?: boolean; -} - -export const MilkdownEditor: React.FC = ({ - document: doc, - onSave, - className = '', - isDarkMode = false, -}) => { - const editorRef = useRef(null); - const crepeRef = useRef(null); - const [isLoading, setIsLoading] = useState(false); - const [hasChanges, setHasChanges] = useState(false); - const [isReverted, setIsReverted] = useState(false); - const [originalContent, setOriginalContent] = useState(''); - const [currentContent, setCurrentContent] = useState(''); - - // Convert document content to markdown string - const getMarkdownContent = () => { - if (typeof doc.content === 'string') { - return doc.content; - } - - if (doc.content && typeof doc.content === 'object') { - // If content has a markdown field, use it - if (doc.content.markdown) { - return doc.content.markdown; - } - - // Check if this is a PRP document - if (doc.content.document_type === 'prp' || doc.document_type === 'prp') { - return convertPRPToMarkdown(doc.content); - } - - // Otherwise, convert the content object to a readable markdown format - let markdown = `# ${doc.title}\n\n`; - - Object.entries(doc.content).forEach(([key, value]) => { - const sectionTitle = key.replace(/_/g, ' ').charAt(0).toUpperCase() + key.replace(/_/g, ' ').slice(1); - markdown += `## ${sectionTitle}\n\n`; - - if (Array.isArray(value)) { - value.forEach(item => { - markdown += `- ${item}\n`; - }); - markdown += '\n'; - } else if (typeof value === 'object' && value !== null) { - if (value.description) { - markdown += `${value.description}\n\n`; - } else { - Object.entries(value).forEach(([subKey, subValue]) => { - markdown += `**${subKey}:** ${subValue}\n\n`; - }); - } - } else { - markdown += `${value}\n\n`; - } - }); - - return markdown; - } - - return `# ${doc.title}\n\nStart writing...`; - }; - - // Helper function to format values for markdown - // Enhanced formatValue to handle complex nested structures - const formatValue = (value: any, indent = '', depth = 0): string => { - if (value === null || value === undefined) { - return ''; - } - - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - return String(value); - } - - if (Array.isArray(value)) { - if (value.length === 0) return ''; - - // Check if it's a simple array (strings/numbers) - const isSimple = value.every(item => - typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean' - ); - - if (isSimple) { - return value.map(item => `${indent}- ${item}`).join('\n') + '\n'; - } - - // Complex array with objects - return value.map((item, index) => { - if (typeof item === 'object' && item !== null) { - const itemLines = formatValue(item, indent + ' ', depth + 1).split('\n'); - const firstLine = itemLines[0]; - const restLines = itemLines.slice(1).join('\n'); - - if (itemLines.length === 1 || (itemLines.length === 2 && !itemLines[1])) { - // Single line item - return `${indent}- ${firstLine}`; - } else { - // Multi-line item - return `${indent}-\n${indent} ${firstLine}${restLines ? '\n' + restLines : ''}`; - } - } - return `${indent}- ${formatValue(item, indent + ' ', depth + 1)}`; - }).join('\n') + '\n'; - } - - if (typeof value === 'object' && value !== null) { - const entries = Object.entries(value); - if (entries.length === 0) return ''; - - // Check if it's a simple object (all values are primitives) - const isSimple = entries.every(([_, val]) => - typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' - ); - - if (isSimple && entries.length <= 3 && depth > 0) { - // Inline simple objects - const pairs = entries.map(([k, v]) => `${formatKey(k)}: ${v}`); - return pairs.join(', '); - } - - let result = ''; - entries.forEach(([key, val], index) => { - const formattedKey = formatKey(key); - - if (val === null || val === undefined) { - return; // Skip null/undefined - } - - if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') { - result += `${indent}**${formattedKey}:** ${val}\n`; - } else if (Array.isArray(val)) { - result += `${indent}**${formattedKey}:**\n${formatValue(val, indent, depth + 1)}`; - } else if (typeof val === 'object') { - // Use appropriate heading level based on depth - const headingLevel = Math.min(depth + 3, 6); - const heading = '#'.repeat(headingLevel); - result += `${indent}${heading} ${formattedKey}\n\n${formatValue(val, indent, depth + 1)}`; - } - - // Add spacing between top-level sections - if (depth === 0 && index < entries.length - 1) { - result += '\n'; - } - }); - - return result; - } - - return String(value); - }; - - // Helper to format keys nicely - const formatKey = (key: string): string => { - return key - .replace(/_/g, ' ') - .replace(/([a-z])([A-Z])/g, '$1 $2') - .split(' ') - .filter(word => word.length > 0) - .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) - .join(' '); - }; - - // Convert PRP document structure to readable markdown - fully dynamic - const convertPRPToMarkdown = (content: any): string => { - // Handle raw string content - if (typeof content === 'string') { - return content; - } - - // Handle null/undefined - if (!content || typeof content !== 'object') { - return `# ${doc.title}\n\nNo content available.`; - } - - // Start with title - let markdown = `# ${content.title || doc.title || 'Untitled Document'}\n\n`; - - // Group metadata fields - const metadataFields = ['version', 'author', 'date', 'status', 'document_type', 'created_at', 'updated_at']; - const metadata = metadataFields.filter(field => content[field]); - - if (metadata.length > 0) { - markdown += `## Metadata\n\n`; - metadata.forEach(field => { - const value = content[field]; - const label = formatKey(field); - markdown += `- **${label}:** ${value}\n`; - }); - markdown += '\n'; - } - - // Process all other fields dynamically - const skipFields = ['title', ...metadataFields, 'id', '_id', 'project_id']; - - // Sort fields by priority (known important fields first) - const priorityFields = [ - 'goal', 'goals', 'objective', 'objectives', - 'why', 'rationale', 'background', - 'what', 'description', 'overview', - 'context', 'background_context', - 'user_personas', 'personas', 'users', 'stakeholders', - 'user_flows', 'flows', 'journeys', 'workflows', - 'requirements', 'functional_requirements', 'non_functional_requirements', - 'success_metrics', 'metrics', 'kpis', 'success_criteria', - 'timeline', 'roadmap', 'milestones', 'phases', - 'implementation_plan', 'implementation_roadmap', 'plan', - 'technical_requirements', 'technical_implementation', 'architecture', - 'validation_gates', 'testing_strategy', 'quality_gates', - 'risks', 'risk_assessment', 'mitigation_strategies' - ]; - - // Create ordered list of fields - const orderedFields = []; - const remainingFields = []; - - Object.keys(content).forEach(key => { - if (skipFields.includes(key)) return; - - const lowerKey = key.toLowerCase(); - const priorityIndex = priorityFields.findIndex(pf => - lowerKey === pf || lowerKey.includes(pf) || pf.includes(lowerKey) - ); - - if (priorityIndex !== -1) { - orderedFields.push({ key, priority: priorityIndex }); - } else { - remainingFields.push(key); - } - }); - - // Sort by priority - orderedFields.sort((a, b) => a.priority - b.priority); - - // Process fields in order - const allFields = [...orderedFields.map(f => f.key), ...remainingFields]; - - allFields.forEach(key => { - const value = content[key]; - if (value === null || value === undefined) return; - - const sectionTitle = formatKey(key); - markdown += `## ${sectionTitle}\n\n`; - - // Handle different value types - if (typeof value === 'string') { - markdown += `${value}\n\n`; - } else if (typeof value === 'number' || typeof value === 'boolean') { - markdown += `${value}\n\n`; - } else if (Array.isArray(value)) { - markdown += formatValue(value) + '\n'; - } else if (typeof value === 'object') { - markdown += formatValue(value) + '\n'; - } - }); - - return markdown.trim(); - }; - - // Initialize editor - useEffect(() => { - if (!editorRef.current || crepeRef.current) return; - - const initialContent = getMarkdownContent(); - setOriginalContent(initialContent); - setCurrentContent(initialContent); - - // Add theme class to root element - if (isDarkMode) { - editorRef.current.classList.add('milkdown-theme-dark'); - } - - const crepe = new Crepe({ - root: editorRef.current, - defaultValue: initialContent, - features: { - [CrepeFeature.HeaderMeta]: true, - [CrepeFeature.LinkTooltip]: true, - [CrepeFeature.ImageBlock]: true, - [CrepeFeature.BlockEdit]: true, - [CrepeFeature.ListItem]: true, - [CrepeFeature.CodeBlock]: true, - [CrepeFeature.Table]: true, - [CrepeFeature.Toolbar]: true, - }, - }); - - crepe.create().then(() => { - console.log('Milkdown editor created'); - - // Set up content change tracking - const editorElement = editorRef.current?.querySelector('.ProseMirror'); - if (editorElement) { - // Listen for input events on the editor - const handleInput = () => { - // Get current markdown content - const markdown = crepe.getMarkdown(); - console.log('Editor content changed via input:', markdown.substring(0, 50) + '...'); - setCurrentContent(markdown); - - // Compare trimmed content to avoid whitespace issues - const hasUnsavedChanges = markdown.trim() !== originalContent.trim(); - setHasChanges(hasUnsavedChanges); - setIsReverted(false); - }; - - // Listen to multiple events to catch all changes - editorElement.addEventListener('input', handleInput); - editorElement.addEventListener('keyup', handleInput); - editorElement.addEventListener('paste', handleInput); - editorElement.addEventListener('cut', handleInput); - - // Store the handlers for cleanup - (editorElement as any)._milkdownHandlers = { - input: handleInput, - keyup: handleInput, - paste: handleInput, - cut: handleInput - }; - } - }).catch((error) => { - console.error('Failed to create Milkdown editor:', error); - }); - - crepeRef.current = crepe; - - return () => { - // Clean up event listeners - const editorElement = editorRef.current?.querySelector('.ProseMirror'); - if (editorElement && (editorElement as any)._milkdownHandlers) { - const handlers = (editorElement as any)._milkdownHandlers; - editorElement.removeEventListener('input', handlers.input); - editorElement.removeEventListener('keyup', handlers.keyup); - editorElement.removeEventListener('paste', handlers.paste); - editorElement.removeEventListener('cut', handlers.cut); - delete (editorElement as any)._milkdownHandlers; - } - - if (crepeRef.current) { - crepeRef.current.destroy(); - crepeRef.current = null; - } - }; - }, [doc.id, originalContent]); - - // Update theme class when isDarkMode changes - useEffect(() => { - if (editorRef.current) { - if (isDarkMode) { - editorRef.current.classList.add('milkdown-theme-dark'); - } else { - editorRef.current.classList.remove('milkdown-theme-dark'); - } - } - }, [isDarkMode]); - - // Add keyboard shortcut for saving - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if ((e.metaKey || e.ctrlKey) && e.key === 's') { - e.preventDefault(); - if (hasChanges && !isLoading) { - handleSave(); - } - } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => { - window.removeEventListener('keydown', handleKeyDown); - }; - }, [hasChanges, isLoading, currentContent]); - - // Handle manual save - const handleSave = async () => { - if (!hasChanges || isLoading) return; - - try { - setIsLoading(true); - console.log('Saving document with content:', currentContent.substring(0, 100) + '...'); - - // Create updated document with markdown content stored in content field - const updatedDocument = { - ...doc, - content: { - markdown: currentContent, - // Preserve any other content fields - ...(typeof doc.content === 'object' && doc.content !== null ? doc.content : {}) - }, - updated_at: new Date().toISOString(), - }; - - await onSave(updatedDocument); - - // Update state after successful save - setHasChanges(false); - setIsReverted(false); - setOriginalContent(currentContent); - console.log('Document saved successfully'); - } catch (error) { - console.error('Error saving document:', error); - // You might want to show an error toast here - } finally { - setIsLoading(false); - } - }; - - // Handle undo changes - const handleUndo = () => { - if (crepeRef.current && editorRef.current) { - // Destroy and recreate editor with original content - crepeRef.current.destroy(); - - const crepe = new Crepe({ - root: editorRef.current, - defaultValue: originalContent, - features: { - [CrepeFeature.HeaderMeta]: true, - [CrepeFeature.LinkTooltip]: true, - [CrepeFeature.ImageBlock]: true, - [CrepeFeature.BlockEdit]: true, - [CrepeFeature.ListItem]: true, - [CrepeFeature.CodeBlock]: true, - [CrepeFeature.Table]: true, - [CrepeFeature.Toolbar]: true, - }, - }); - - crepe.create().then(() => { - console.log('Milkdown editor reverted to original content'); - - // Set up content change tracking for the new editor instance - const editorElement = editorRef.current?.querySelector('.ProseMirror'); - if (editorElement) { - const handleInput = () => { - const markdown = crepe.getMarkdown(); - console.log('Editor content changed after undo:', markdown.substring(0, 50) + '...'); - setCurrentContent(markdown); - const hasUnsavedChanges = markdown.trim() !== originalContent.trim(); - setHasChanges(hasUnsavedChanges); - setIsReverted(false); - }; - - editorElement.addEventListener('input', handleInput); - editorElement.addEventListener('keyup', handleInput); - editorElement.addEventListener('paste', handleInput); - editorElement.addEventListener('cut', handleInput); - - (editorElement as any)._milkdownHandlers = { - input: handleInput, - keyup: handleInput, - paste: handleInput, - cut: handleInput - }; - } - - setCurrentContent(originalContent); - setHasChanges(false); - setIsReverted(true); - }).catch((error) => { - console.error('Failed to revert Milkdown editor:', error); - }); - - crepeRef.current = crepe; - } - }; - - return ( -
-
-
-

- {doc.title} -

-
- {isLoading ? ( - -
- Saving... -
- ) : isReverted ? ( - -
- Reverted -
- ) : hasChanges ? ( - -
- Unsaved changes -
- ) : ( - -
- All changes saved -
- )} -
-
-
- {hasChanges && ( - - )} - -
-
- -
-
- ); -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/project-tasks/Tabs.tsx b/archon-ui-main/src/components/project-tasks/Tabs.tsx deleted file mode 100644 index fd66d55c..00000000 --- a/archon-ui-main/src/components/project-tasks/Tabs.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useMemo, useState, createContext, useContext } from 'react'; -interface TabsProps { - defaultValue: string; - value?: string; - onValueChange?: (value: string) => void; - children: React.ReactNode; - className?: string; -} -const TabsContext = createContext<{ - value: string; - onValueChange: (value: string) => void; -}>({ - value: '', - onValueChange: () => {} -}); -export const Tabs = ({ - defaultValue, - value, - onValueChange, - children, - className = '' -}: TabsProps) => { - const [internalValue, setInternalValue] = useState(defaultValue); - const activeValue = value !== undefined ? value : internalValue; - const contextValue = useMemo(() => ({ - value: activeValue, - onValueChange: (newValue: string) => { - setInternalValue(newValue); - onValueChange?.(newValue); - } - }), [activeValue, onValueChange]); - return -
{children}
-
; -}; -interface TabsListProps { - children: React.ReactNode; - className?: string; -} -export const TabsList = ({ - children, - className = '' -}: TabsListProps) => { - return
- {/* Subtle neon glow effect */} -
- {children} -
; -}; -interface TabsTriggerProps { - value: string; - children: React.ReactNode; - className?: string; - onClick?: () => void; - color?: 'blue' | 'purple' | 'pink' | 'orange' | 'cyan' | 'green'; -} -export const TabsTrigger = ({ - value, - children, - className = '', - onClick, - color = 'blue' -}: TabsTriggerProps) => { - const { - value: activeValue, - onValueChange - } = useContext(TabsContext); - const isActive = activeValue === value; - const handleClick = () => { - onValueChange(value); - onClick?.(); - }; - const colorMap = { - blue: { - text: 'text-blue-600 dark:text-blue-400', - glow: 'bg-blue-500 shadow-[0_0_10px_2px_rgba(59,130,246,0.4)] dark:shadow-[0_0_20px_5px_rgba(59,130,246,0.7)]', - hover: 'hover:text-blue-500 dark:hover:text-blue-400/70' - }, - purple: { - text: 'text-purple-600 dark:text-purple-400', - glow: 'bg-purple-500 shadow-[0_0_10px_2px_rgba(168,85,247,0.4)] dark:shadow-[0_0_20px_5px_rgba(168,85,247,0.7)]', - hover: 'hover:text-purple-500 dark:hover:text-purple-400/70' - }, - pink: { - text: 'text-pink-600 dark:text-pink-400', - glow: 'bg-pink-500 shadow-[0_0_10px_2px_rgba(236,72,153,0.4)] dark:shadow-[0_0_20px_5px_rgba(236,72,153,0.7)]', - hover: 'hover:text-pink-500 dark:hover:text-pink-400/70' - }, - orange: { - text: 'text-orange-600 dark:text-orange-400', - glow: 'bg-orange-500 shadow-[0_0_10px_2px_rgba(249,115,22,0.4)] dark:shadow-[0_0_20px_5px_rgba(249,115,22,0.7)]', - hover: 'hover:text-orange-500 dark:hover:text-orange-400/70' - }, - cyan: { - text: 'text-cyan-600 dark:text-cyan-400', - glow: 'bg-cyan-500 shadow-[0_0_10px_2px_rgba(34,211,238,0.4)] dark:shadow-[0_0_20px_5px_rgba(34,211,238,0.7)]', - hover: 'hover:text-cyan-500 dark:hover:text-cyan-400/70' - }, - green: { - text: 'text-emerald-600 dark:text-emerald-400', - glow: 'bg-emerald-500 shadow-[0_0_10px_2px_rgba(16,185,129,0.4)] dark:shadow-[0_0_20px_5px_rgba(16,185,129,0.7)]', - hover: 'hover:text-emerald-500 dark:hover:text-emerald-400/70' - } - }; - return ; -}; -interface TabsContentProps { - value: string; - children: React.ReactNode; - className?: string; -} -export const TabsContent = ({ - value, - children, - className = '' -}: TabsContentProps) => { - const { - value: activeValue - } = useContext(TabsContext); - // Simplified TabsContent - we're handling animations in the parent component now - if (activeValue !== value) return null; - return
- {children} -
; -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/project-tasks/TaskBoardView.tsx b/archon-ui-main/src/components/project-tasks/TaskBoardView.tsx deleted file mode 100644 index 0e37c988..00000000 --- a/archon-ui-main/src/components/project-tasks/TaskBoardView.tsx +++ /dev/null @@ -1,397 +0,0 @@ -import React, { useRef, useState, useCallback } from 'react'; -import { useDrag, useDrop } from 'react-dnd'; -import { useToast } from '../../contexts/ToastContext'; -import { DeleteConfirmModal } from '../../pages/ProjectPage'; -import { CheckSquare, Square, Trash2, ArrowRight } from 'lucide-react'; -import { projectService } from '../../services/projectService'; -import { Task } from './TaskTableView'; // Import Task interface -import { ItemTypes, getAssigneeIcon, getAssigneeGlow, getOrderColor, getOrderGlow } from '../../lib/task-utils'; -import { DraggableTaskCard, DraggableTaskCardProps } from './DraggableTaskCard'; // Import the new component and its props - -interface TaskBoardViewProps { - tasks: Task[]; - onTaskView: (task: Task) => void; - onTaskComplete: (taskId: string) => void; - onTaskDelete: (task: Task) => void; - onTaskMove: (taskId: string, newStatus: Task['status']) => void; - onTaskReorder: (taskId: string, targetIndex: number, status: Task['status']) => void; -} - -interface ColumnDropZoneProps { - status: Task['status']; - title: string; - tasks: Task[]; - onTaskMove: (taskId: string, newStatus: Task['status']) => void; - onTaskView: (task: Task) => void; - onTaskComplete: (taskId: string) => void; - onTaskDelete: (task: Task) => void; - onTaskReorder: (taskId: string, targetIndex: number, status: Task['status']) => void; - allTasks: Task[]; - hoveredTaskId: string | null; - onTaskHover: (taskId: string | null) => void; - selectedTasks: Set; - onTaskSelect: (taskId: string) => void; -} - -const ColumnDropZone = ({ - status, - title, - tasks, - onTaskMove, - onTaskView, - onTaskComplete, - onTaskDelete, - onTaskReorder, - allTasks, - hoveredTaskId, - onTaskHover, - selectedTasks, - onTaskSelect -}: ColumnDropZoneProps) => { - const ref = useRef(null); - - const [{ isOver }, drop] = useDrop({ - accept: ItemTypes.TASK, - drop: (item: { id: string; status: string }) => { - if (item.status !== status) { - // Moving to different status - use length of current column as new order - onTaskMove(item.id, status); - } - }, - collect: (monitor) => ({ - isOver: !!monitor.isOver() - }) - }); - - drop(ref); - - // Get column header color based on status - const getColumnColor = () => { - switch (status) { - case 'backlog': - return 'text-gray-600 dark:text-gray-400'; - case 'in-progress': - return 'text-blue-600 dark:text-blue-400'; - case 'review': - return 'text-purple-600 dark:text-purple-400'; - case 'complete': - return 'text-green-600 dark:text-green-400'; - } - }; - - // Get column header glow based on status - const getColumnGlow = () => { - switch (status) { - case 'backlog': - return 'bg-gray-500/30'; - case 'in-progress': - return 'bg-blue-500/30 shadow-[0_0_10px_2px_rgba(59,130,246,0.2)]'; - case 'review': - return 'bg-purple-500/30 shadow-[0_0_10px_2px_rgba(168,85,247,0.2)]'; - case 'complete': - return 'bg-green-500/30 shadow-[0_0_10px_2px_rgba(16,185,129,0.2)]'; - } - }; - - // Just use the tasks as-is since they're already parent tasks only - const organizedTasks = tasks; - - return ( -
-
-

{title}

- {/* Column header divider with glow */} -
-
- -
- {organizedTasks.map((task, index) => ( - onTaskView(task)} - onComplete={() => onTaskComplete(task.id)} - onDelete={onTaskDelete} - onTaskReorder={onTaskReorder} - tasksInStatus={organizedTasks} - allTasks={allTasks} - hoveredTaskId={hoveredTaskId} - onTaskHover={onTaskHover} - /> - ))} -
-
- ); -}; - -export const TaskBoardView = ({ - tasks, - onTaskView, - onTaskComplete, - onTaskDelete, - onTaskMove, - onTaskReorder -}: TaskBoardViewProps) => { - const [hoveredTaskId, setHoveredTaskId] = useState(null); - const [selectedTasks, setSelectedTasks] = useState>(new Set()); - - // State for delete confirmation modal - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); - const [taskToDelete, setTaskToDelete] = useState(null); - - const { showToast } = useToast(); - - // Multi-select handlers - const toggleTaskSelection = useCallback((taskId: string) => { - setSelectedTasks(prev => { - const newSelection = new Set(prev); - if (newSelection.has(taskId)) { - newSelection.delete(taskId); - } else { - newSelection.add(taskId); - } - return newSelection; - }); - }, []); - - const selectAllTasks = useCallback(() => { - setSelectedTasks(new Set(tasks.map(task => task.id))); - }, [tasks]); - - const clearSelection = useCallback(() => { - setSelectedTasks(new Set()); - }, []); - - // Mass delete handler - const handleMassDelete = useCallback(async () => { - if (selectedTasks.size === 0) return; - - const tasksToDelete = tasks.filter(task => selectedTasks.has(task.id)); - - try { - // Delete all selected tasks - await Promise.all( - tasksToDelete.map(task => projectService.deleteTask(task.id)) - ); - - // Clear selection - clearSelection(); - - showToast(`${tasksToDelete.length} tasks deleted successfully`, 'success'); - } catch (error) { - console.error('Failed to delete tasks:', error); - showToast('Failed to delete some tasks', 'error'); - } - }, [selectedTasks, tasks, clearSelection, showToast]); - - // Mass status change handler - const handleMassStatusChange = useCallback(async (newStatus: Task['status']) => { - if (selectedTasks.size === 0) return; - - const tasksToUpdate = tasks.filter(task => selectedTasks.has(task.id)); - - try { - // Update all selected tasks - await Promise.all( - tasksToUpdate.map(task => - projectService.updateTask(task.id, { - status: mapUIStatusToDBStatus(newStatus) - }) - ) - ); - - // Clear selection - clearSelection(); - - showToast(`${tasksToUpdate.length} tasks moved to ${newStatus}`, 'success'); - } catch (error) { - console.error('Failed to update tasks:', error); - showToast('Failed to update some tasks', 'error'); - } - }, [selectedTasks, tasks, clearSelection, showToast]); - - // Helper function to map UI status to DB status (reuse from TasksTab) - const mapUIStatusToDBStatus = (uiStatus: Task['status']) => { - switch (uiStatus) { - case 'backlog': return 'todo'; - case 'in-progress': return 'doing'; - case 'review': return 'review'; - case 'complete': return 'done'; - default: return 'todo'; - } - }; - - // Handle task deletion (opens confirmation modal) - const handleDeleteTask = useCallback((task: Task) => { - setTaskToDelete(task); - setShowDeleteConfirm(true); - }, [setTaskToDelete, setShowDeleteConfirm]); - - // Confirm deletion and execute - const confirmDeleteTask = useCallback(async () => { - if (!taskToDelete) return; - - try { - await projectService.deleteTask(taskToDelete.id); - // Notify parent to update tasks - onTaskDelete(taskToDelete); - showToast(`Task "${taskToDelete.title}" deleted successfully`, 'success'); - } catch (error) { - console.error('Failed to delete task:', error); - showToast(error instanceof Error ? error.message : 'Failed to delete task', 'error'); - } finally { - setShowDeleteConfirm(false); - setTaskToDelete(null); - } - }, [taskToDelete, onTaskDelete, showToast, setShowDeleteConfirm, setTaskToDelete, projectService]); - - // Cancel deletion - const cancelDeleteTask = useCallback(() => { - setShowDeleteConfirm(false); - setTaskToDelete(null); - }, [setShowDeleteConfirm, setTaskToDelete]); - - // Simple task filtering for board view - const getTasksByStatus = (status: Task['status']) => { - return tasks - .filter(task => task.status === status) - .sort((a, b) => a.task_order - b.task_order); - }; - - return ( -
- {/* Multi-select toolbar */} - {selectedTasks.size > 0 && ( -
-
- - {selectedTasks.size} task{selectedTasks.size !== 1 ? 's' : ''} selected - -
- -
- {/* Status change dropdown */} - - - {/* Mass delete button */} - - - {/* Clear selection */} - -
-
- )} - - {/* Board Columns */} -
- {/* Backlog Column */} - - - {/* In Progress Column */} - - - {/* Review Column */} - - - {/* Complete Column */} - -
- - {/* Delete Confirmation Modal for Tasks */} - {showDeleteConfirm && taskToDelete && ( - - )} -
- ); -}; \ No newline at end of file diff --git a/archon-ui-main/src/components/project-tasks/TaskInputComponents.tsx b/archon-ui-main/src/components/project-tasks/TaskInputComponents.tsx deleted file mode 100644 index 75924c33..00000000 --- a/archon-ui-main/src/components/project-tasks/TaskInputComponents.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import React, { memo, useState, useEffect, useCallback, useRef } from 'react'; - -interface DebouncedInputProps { - value: string; - onChange: (value: string) => void; - placeholder?: string; - className?: string; - type?: 'text' | 'textarea'; - rows?: number; -} - -// Memoized input component that manages its own state -export const DebouncedInput = memo(({ - value, - onChange, - placeholder, - className, - type = 'text', - rows = 5 -}: DebouncedInputProps) => { - const [localValue, setLocalValue] = useState(value); - const timeoutRef = useRef(); - const isFirstRender = useRef(true); - - // Sync with external value only on mount or when externally changed - useEffect(() => { - if (isFirstRender.current) { - isFirstRender.current = false; - return; - } - // Only update if the external value is different from local - if (value !== localValue) { - setLocalValue(value); - } - }, [value]); - - const handleChange = useCallback((e: React.ChangeEvent) => { - const newValue = e.target.value; - setLocalValue(newValue); - - // Clear existing timeout - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - // Set new timeout for debounced update - timeoutRef.current = setTimeout(() => { - onChange(newValue); - }, 300); - }, [onChange]); - - // Cleanup timeout on unmount - useEffect(() => { - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - }; - }, []); - - if (type === 'textarea') { - return ( -