diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index b9123ef..76de801 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -74,9 +74,11 @@ flowchart TD TopoHelpers["helpers.py"] end - NavigatorSrv["Codebase Navigator Service (navigator.py)\n3-Pane Tree, AST Outline & Impact Engine"] + NavigatorSrv["Codebase Navigator Service (navigator.py)\nHero Tree, AST Outline & Impact Engine"] + OmniSearchSrv["Omni-Search Command Palette (omni_search.py)\nFuzzy Repo, File & Symbol Ranking"] + FileReaderSrv["Source & Doc Reader (file_reader.py)\nFull Code & Markdown Content Delivery"] LocalStorageSrv["Local Storage Service (local_storage.py)\nPath Traversal Defense & File Trees"] - GitMgr["Universal Shallow Git Ingestion (git_manager.py)"] + GitMgr["Universal Shallow Git Ingestion (git_manager.py)\nEphemeral & Persistent Clones"] Embeddings["FastEmbed Engine (embeddings.py)\nDense (384d) + Sparse BM25"] Search["Hybrid & RRF Search (search.py)"] Poller["Auto-Sync Poller Daemon (poller.py)"] @@ -94,6 +96,7 @@ flowchart TD Chroma["ChromaDB (Embedded/Remote)"] end LocalStorageDir[("Managed Local Storage\n(DATA_DIR/storage)")] + PersistentRepoDir[("Persistent Shallow Clones\n(DATA_DIR/repos/{repo_name})")] end Claude -->|Authorization: Bearer cc_... or JWT| SSE @@ -352,26 +355,19 @@ flowchart TD --- -### 13. High-Performance 3-Pane Codebase Navigator (`app/services/navigator.py` & `app/api/routers/navigator.py`) -- **Architectural Motivation**: Replaces legacy 2D graph canvases with a structured, ultra-fast 3-pane navigation paradigm designed for instant codebase comprehension, symbol discovery, and architectural impact analysis. -- **Three-Pane Layout Topology**: - - **Pane 1: Files & Modules (`NavigatorTree.tsx`)**: - - Queries `GET /admin/api/navigator/tree?repo=...`. - - Recursively organizes `indexed_files` into hierarchical directory trees with aggregate metrics (total symbol counts, detected API routes per folder/file). - - Features instant search filtering (auto-expanding ancestor directories), Expand All / Collapse All controls, and active file highlighting. - - **Pane 2: Symbols & Routes (`NavigatorOutline.tsx`)**: - - Queries `GET /admin/api/navigator/file-outline?filepath=...&repo=...`. - - Retrieves syntax-aware AST symbols from `ast_symbols` along with associated REST routes from `api_routes`. - - Provides category chip filtering (`All`, `Functions`, `Classes`, `Routes`) and real-time symbol search. - - Displays symbol kinds (function, class, struct, interface), start/end line numbers, and route method badges (`POST`, `GET`, etc.). - - **Pane 3: Code Intelligence & Impact Inspector (`NavigatorInspector.tsx`)**: - - Queries `GET /admin/api/navigator/symbol-impact?symbol_id=...` or `?name=...&filepath=...`. - - Aggregates multi-source intelligence from `ast_symbols`, `ast_relationships`, and `api_routes`. - - **4-Metric Impact Summary**: Count of incoming callers, outgoing callees, imported modules, and language scope. - - **API Route Mapping Card**: Method badge (`POST`, `GET`, `PUT`, `DELETE`), path pattern (`/v1/chat/completions`), and framework tag (`FastAPI`, `Express`, `Gin`, etc.). - - **Signature & Docstrings**: Syntax-highlighted code block and extracted documentation. - - **Interactive Relationship Cards**: Clickable caller cards with filename, symbol name, and line numbers. Clicking a caller triggers bidirectional navigation (updates tree selection, switches file outline, and activates the caller symbol). - - **Copy Permalink**: Generates and copies clean permalinks with file paths and line ranges. +### 13. High-Performance Codebase Navigator & Omni-Search (`app/services/navigator.py`, `app/services/omni_search.py`, `app/services/file_reader.py`) +- **Architectural Motivation**: Provides an ultra-fast, IDE-grade codebase comprehension and exploration system with split hero layout, instant command palette search, syntax-highlighted code viewing, and deep AST symbol intelligence. +- **Hero Split-View Layout Topology**: + - **Left Sidebar: Dual-Tab Directory Tree & Symbol Outline**: + - **Files Tab (`NavigatorTree.tsx`)**: Queries `GET /admin/api/navigator/tree?repo=...`. Recursively structures `indexed_files` into hierarchical trees with aggregate symbol counts and API routes. Features instant search filtering, Expand All / Collapse All controls, active file highlighting, and per-repository directory expansion persistence via `sessionStorage`. + - **Symbols Tab (`NavigatorOutline.tsx`)**: Queries `GET /admin/api/navigator/file-outline?filepath=...&repo=...`. Displays AST symbol declarations from `ast_symbols` and REST routes from `api_routes` with category filtering (`All`, `Functions`, `Classes`, `Routes`), signature previews, and search. + - **Center Hero Viewport: Code Viewer, Doc Reader & Intelligence**: + - **Syntax-Aware Source Code Viewer (`NavigatorCodeViewer.tsx`)**: Queries `GET /admin/api/navigator/file-content?filepath=...&repo=...` through `file_reader.py`. Features Prism-powered syntax highlighting across 8+ languages (C#, Python, JavaScript/TypeScript, C++, Go, Rust, SQL, COBOL, JSON, YAML, etc.), line numbering, target line range highlights (`targetStartLine`-`targetEndLine`), permalink copying, and collapsible caller impact drawer. + - **Documentation & Markdown Reader (`NavigatorDocReader.tsx`)**: Renders markdown files with GitHub Flavored Markdown (GFM), automatic table normalization (bridging blank lines and auto-inserting missing separator rows), and interactive Mermaid diagram rendering (`flowchart`, `sequenceDiagram`, `classDiagram`, `erDiagram`, etc.). + - **Code Intelligence & Impact Inspector (`NavigatorInspector.tsx`)**: Queries `GET /admin/api/navigator/symbol-impact?symbol_id=...`. Immediately accessible and clickable upon file selection without requiring manual outline navigation. Aggregates caller/callee counts, API route mappings, method signatures, docstrings, and cross-file jump navigation. + - **Omni-Search Command Palette (`NavigatorOmniSearch.tsx` / `Ctrl+K`)**: + - Queries `GET /admin/api/navigator/omni-search?query=...&repo=...`. + - Real-time fuzzy ranked matching across repository aliases, file paths, and AST symbols with hotkey triggers, badge indicators, and instant keyboard selection. - **Multi-Density Layout Engine & Persistent UX**: - `Balanced`: Default balanced layout optimized for standard desktop viewports. - `Compact`: High-density IDE layout reducing font sizes and padding for large file trees and complex outlines. @@ -400,6 +396,7 @@ erDiagram datetime last_synced int enabled int auto_sync + int keep_shallow "0 = Ephemeral (delete on sync), 1 = Retain shallow clone on disk" string webhook_secret datetime added_at } diff --git a/DEVELOPER_DOCS.md b/DEVELOPER_DOCS.md index 8ba5131..db9bd87 100644 --- a/DEVELOPER_DOCS.md +++ b/DEVELOPER_DOCS.md @@ -90,10 +90,10 @@ cd contextcortex ### Backend Tests (Python Pytest & Coverage) ```bash -# Run all backend unit and integration tests (277 tests) +# Run all backend unit and integration tests (522+ tests) pytest -v -# Run backend tests with code coverage report (88% coverage baseline) +# Run backend tests with code coverage report (88%+ coverage baseline) pytest -v --cov=app --cov-report=term-missing ``` @@ -101,13 +101,13 @@ pytest -v --cov=app --cov-report=term-missing ```bash cd frontend -# Run unit and component tests via Vitest (82 tests) +# Run unit and component tests via Vitest (315 tests across 32 test files) npm test -# Run component tests with code coverage (87% line coverage) +# Run component tests with code coverage npm run test:coverage -# Run end-to-end user journey tests via Playwright (26 user journeys) +# Run end-to-end user journey tests via Playwright npx playwright test ``` @@ -138,6 +138,7 @@ npm run docs:preview | :--- | :--- | :--- | | `DATABASE_URL` | SQLAlchemy connection string (e.g. `postgresql+psycopg://...` or `sqlite:///...`) | `sqlite:////app/data/index_cache.db` | | `LOCAL_STORAGE_PATH` | Storage directory for managed local storage file uploads | `/app/data/storage` | +| `PERSISTENT_REPOS_DIR` | Storage directory for retained shallow git clones (`keep_shallow`) | `/app/data/repos` | | `AUTH_ENABLED` | Enable MCP 2026-07-28 OAuth 2.1 & API Key RBAC | `false` | | `VECTOR_STORE_PROVIDER` | Vector database backend (`pgvector`, `qdrant`, or `chroma`) | `qdrant` | | `VECTOR_STORE_MODE` | Vector store mode (`embedded` or `remote`) | `embedded` | @@ -179,16 +180,18 @@ contextcortex/ │ ├── indexing/ # Git/local syncers, file processor, state notifications │ ├── topology/ # Graph topology builder, node details, BFS helpers │ ├── vector_store/ # pgvector, Qdrant, and ChromaDB pluggable vector store implementations -│ ├── navigator.py # High-performance 3-pane codebase tree, outline & impact intelligence +│ ├── navigator.py # High-performance codebase tree, outline & impact intelligence +│ ├── omni_search.py # Unified fuzzy command palette search for files, repos, and symbols +│ ├── file_reader.py # Safe full-file source and markdown content resolution from disk │ ├── local_storage.py# Safe path resolution, disk file persistence, and tree inspection -│ ├── git_manager.py # Ephemeral shallow git clone, token masking, permalinks +│ ├── git_manager.py # Shallow git clone (ephemeral & persistent), token masking, permalinks │ ├── embeddings.py # FastEmbed dense (384d) & sparse BM25 multi-vector engine │ ├── search.py # Hybrid search & Reciprocal Rank Fusion (RRF) reranker │ ├── poller.py # Background scheduled repo SHA poller daemon │ ├── adr.py # MADR / Nygard format ADR ingestion and lifecycle │ ├── architecture.py# Codebase entry point, language distribution synthesis │ └── logger.py # In-memory 500-event ring buffer diagnostic logger -├── tests/ # Backend pytest test suite (430+ tests, 88% coverage) +├── tests/ # Backend pytest test suite (522+ tests, 88%+ coverage) │ ├── backend/ # Unit and integration test modules │ ├── test_navigator_router.py # REST navigator endpoints tests │ ├── test_navigator_service.py # Tree, outline, and impact service tests diff --git a/Dockerfile b/Dockerfile index 54ee121..2c8b8ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ git \ libpq-dev \ curl \ + ripgrep \ && rm -rf /var/lib/apt/lists/* COPY requirements.txt . diff --git a/README.md b/README.md index 2f945bd..e99ca25 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ A high-performance, multi-repo Model Context Protocol (MCP) server providing **s | ![Overview](docs/assets/desktop_overview.png) | ![Git Repos](docs/assets/desktop_git-repos.png) | | **Codebase Navigator** | **Search & Inspector** | | ![Codebase Navigator](docs/assets/desktop_codebase-navigator.png) | ![Search](docs/assets/desktop_search-inspector.png) | -| **Local Paths** | **Settings** | -| ![Local Paths](docs/assets/desktop_local-paths.png) | ![Settings](docs/assets/desktop_settings.png) | -| **Diagnostics & Logs** | | -| ![Diagnostics](docs/assets/desktop_diagnostics.png) | | +| **Files & Storage** | **Settings** | +| ![Files & Storage](docs/assets/desktop_local-paths.png) | ![Settings](docs/assets/desktop_settings.png) | +| **Diagnostics & Logs** | **Add Repository Modal** | +| ![Diagnostics](docs/assets/desktop_diagnostics.png) | ![Add Repository Modal](docs/assets/desktop_add-repo-modal.png) | @@ -59,12 +59,13 @@ A high-performance, multi-repo Model Context Protocol (MCP) server providing **s - **Qdrant**: High-scale vector engine supporting dense + sparse BM25 multi-vectors with Reciprocal Rank Fusion (RRF) in both embedded disk and remote server modes. - **ChromaDB**: Lightweight, zero-dependency embedded disk or remote vector store with automatic fallback. - **Dynamic Backend Switching**: Test and switch vector backends live from the Settings UI or REST API without server restarts. -- **Universal Git Provider Support**: +- **Universal Git Provider Support & Persistent Shallow Retention**: - Ingest repositories from **any source where Git lives**: **GitHub**, **GitLab (Cloud, Enterprise & Self-Hosted)**, **Gitea & Forgejo**, **Bitbucket (Cloud & Server)**, and **Generic Git HTTP/HTTPS**. + - **Configurable Shallow Copy Retention (`keep_shallow`)**: Choose between **ephemeral shallow clones** (immediate deletion after indexing to save storage) or **persistent shallow clones** (`--depth 1` retained on disk under `/app/data/repos/{repo_name}` on the cache volume). Persistent copies power instant full source code inspection in Code Navigator and accelerated incremental delta syncing (`git fetch --depth 1` + `git reset --hard`). + - **Full-Screen React Portals for Modals**: Add Repository modal, Webhook setup modal, and Sync Drawer render cleanly via React Portals (`createPortal(..., document.body)`), eliminating containment clipping from glassmorphism cards. - **Provider-Aware Permalinks**: Automatically generates exact deep-links for code results (`/blob/`, `/-/blob/`, `/src/branch/`, `/src/commit/`, `/src/#lines-`). - **Custom Git Host Credential Vault**: Register per-host tokens and authentication types for private internal domains (e.g. `gitlab.company.internal` or `http://git.lan:3000`). - **AST-Aware Code Chunking (Tree-sitter)**: Understands syntax structures across Python, TypeScript/JavaScript, Go, Rust, C#, C++, Java, Ruby, PHP, and more. Chunks along class, method, and function boundaries with exact line numbers and symbol names. -- **Ephemeral Repository Ingestion**: Authenticated shallow clones (`--depth 1`) extract AST symbols and hybrid vectors and **immediately remove the cloned repository from disk** to conserve storage. - **Multi-Tier Git Authentication Hierarchy**: 1. Per-repository override token & optional username. 2. Domain-level Custom Git Host Vault (`git_host_credentials`). @@ -80,20 +81,21 @@ A high-performance, multi-repo Model Context Protocol (MCP) server providing **s - Granular multi-dimensional filtering by `source_type` (`all`, `git`, `monitored_path`, `local_storage`), `repo_name`, `path_prefix`, and `file_extension`. - Flexible granularity (`summary` for totals and status; `detailed` for hierarchical file trees). - **Fast Deterministic Symbol Lookup**: Built-in symbol table (`ast_symbols`) powers instantaneous symbol searches (`find_symbol`) and file outlines (`get_file_outline`) without token bloat. -- **High-Performance 3-Pane Codebase Navigator**: - - **Pane 1 (Files & Modules)**: Virtualized folder & file tree hierarchy with symbol/route counts, instant filtering, and one-click expand/collapse. - - **Pane 2 (Symbols & Routes)**: Language-aware AST symbol declarations with category chip filtering (`All`, `Functions`, `Classes`, `Routes`), signature previews, and search. - - **Pane 3 (Code Intelligence & Impact)**: Deep architectural intelligence displaying incoming callers, outgoing callees, imported modules, REST API route mappings (`POST`, `GET`, etc.), signature code blocks, docstrings, and one-click caller navigation jump. - - **Integrated Document & Markdown Reader**: Formatted Markdown and raw source table viewer for reading full documentation (`.md`, `.txt`) directly within Pane 3. +- **High-Performance Codebase Navigator & Omni-Search**: + - **Omni-Search Command Palette (`Ctrl+K` / `Cmd+K`)**: Unified command palette searching across repositories, file paths, and AST symbols with live fuzzy scoring and keyboard navigation. + - **Dual-Pane Sidebar (Files & Symbols)**: Tabbed sidebar separating file directory hierarchy and AST symbol outline. Preserves folder expansion state per-repository across browser sessions (`sessionStorage`). + - **Hero Viewport (Full Source Code & Documentation)**: + - **Syntax-Highlighted Source Code Viewer**: High-fidelity syntax highlighting across 8+ languages (C#, Python, JavaScript/TypeScript, C++, Go, Rust, SQL, COBOL, JSON, YAML, etc.) with line numbers, highlighted target symbol line ranges, permalink copy, and collapsible caller impact drawer. + - **Markdown Document Reader**: Automatic table auto-normalization (bridging gaps and synthesizing separator rows for loose Markdown) and interactive Mermaid diagram rendering. + - **Instant Intelligence & Impact Tab**: Caller/callee analysis, route mappings, AST signatures, and cross-file jump navigation, immediately clickable and populated upon file selection without requiring manual outline navigation. - **Customizable Layout Density & Mobile Responsiveness**: Persisted `Compact` (IDE density), `Balanced` (default), and `Spacious` (cards) modes with responsive vertical stacking and zero horizontal overflow across devices. - **Diagnostic Logging & Observability**: In-memory ring buffer (500 events) capturing server warnings, errors, indexing lifecycle events, and expandable stack traces with a REST API (`/admin/api/logs`). - **Multi-Theme Engine & Modern Tabbed Web Dashboard (`/admin/`)**: - **Appearance & Theme Settings**: Instant zero-latency switching between 4 distinct dark and light themes (**Deep Ocean**, **Midnight Blue**, **Lavender Haze**, and **Amber Warmth**) with live palette swatches and browser persistence. - **Overview**: Real-time stats, vector counts, AST symbols, model specs, topic tag cloud, and manual full reindexing trigger. - - **Codebase Navigator**: High-performance 3-pane architectural file tree, AST symbol outline, and code impact/route inspector. - - **Git Repositories**: Register repos across GitHub, GitLab, Gitea, Bitbucket, or Generic Git, trigger shallow clone syncs, inspect commit SHAs, and manage sources. - - **Local Paths**: Monitor local workspaces and notes vaults with recursive directory scanning and filesystem browser modal. - - **Local Storage**: Managed file explorer, direct file upload modal with folder categorization, and file preview/replacement. + - **Codebase Navigator**: Hero split layout with Omni-Search palette, folder tree persistence, syntax-highlighted code viewer, Mermaid documentation reader, and instant intelligence inspection. + - **Git Repositories**: Register repos across GitHub, GitLab, Gitea, Bitbucket, or Generic Git, configure shallow copy retention, trigger syncs, inspect commit SHAs, and manage sources. + - **Files & Storage**: Unified explorer managing monitored local directory vaults and managed local file storage with upload modal and file preview. - **Ingestion Catalog**: Unified multi-source explorer with source type filters, repository lookup, and file listings. - **Search & Inspector**: Interactive live hybrid search tester with RRF score previews, target type toggle (Code vs Docs), and syntax highlighted results. - **Settings**: Vector Database manager (pgvector, Qdrant, & ChromaDB switcher & connection tester), LiteLLM Model Discovery with dynamic categorized model dropdowns (Embeddings, Vision OCR, and Chat models), multi-provider token cards, GitHub rate limit monitor, and interactive Custom Git Host Credential Vault table/modal. diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index eda4c5e..017c2ed 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -2,7 +2,7 @@ > **Note:** This document is automatically generated and verified against the live test suite by `scripts/generate_requirements.py` and `tests/backend/test_requirements_sync.py`. -**Test Verification Baseline:** **993 Automated Tests** (656 Pytest Backend + 287 Vitest Frontend + 50 Playwright E2E). +**Test Verification Baseline:** **1003 Automated Tests** (658 Pytest Backend + 295 Vitest Frontend + 50 Playwright E2E). --- @@ -393,6 +393,9 @@ classDiagram ### 6.1 Backend Python Tests +#### `tests/backend/test_ai_gateway_settings.py` (1 tests) +- `test_ai_gateway_settings_lifecycle` - _Verify OpenAI-compatible AI Gateway configuration lifecycle and masking._ + #### `tests/backend/test_api_route_discovery.py` (6 tests) - `test_path_normalization_and_matching` - `test_fastapi_route_parsing` @@ -642,7 +645,7 @@ classDiagram - `test_process_file_content_with_custom_provider` - `test_sync_single_git_repo_triggers_notification` -#### `tests/backend/test_navigator_router.py` (9 tests) +#### `tests/backend/test_navigator_router.py` (10 tests) - `test_db` - `test_api_get_navigator_tree_all` - `test_api_get_navigator_tree_specific_repo` @@ -652,6 +655,7 @@ classDiagram - `test_api_get_symbol_impact_success` - `test_api_get_symbol_impact_not_found` - `test_api_get_omni_search_symbols_and_files` +- `test_navigator_tree_has_no_empty_folder_root` - _Verify get_navigator_tree sanitizes URI schemes and never produces empty name root folders._ #### `tests/backend/test_navigator_service.py` (11 tests) - `test_db` @@ -1186,6 +1190,11 @@ and leaves the prior indexed state intact without data loss._ ### 6.2 Frontend Vitest Tests (`frontend/src/tests/`) +#### `AIGatewaySettings.test.tsx` (3 tests) +- renders masked api key and exposes change key input when clicked +- submits updated settings with new key +- discovers models and populates dropdown selections + #### `App.test.tsx` (6 tests) - renders header, status indicators, and default Overview tab - switches between tabs on navigation click @@ -1195,7 +1204,7 @@ and leaves the prior indexed state intact without data loss._ - renders ChromaDB provider and unhealthy status badge in header #### `CodeNavigator.test.tsx` (5 tests) -- renders toolbar, 3-pane layout, and fetches initial tree data +- renders toolbar, hero layout, and fetches initial tree data - handles density mode switching and persists to localStorage - loads file outline on file selection and symbol impact on symbol selection - supports caller click-through navigation jumping to caller file and symbol @@ -1243,11 +1252,12 @@ and leaves the prior indexed state intact without data loss._ - opens and closes RepoSyncDrawer when Live Logs button is clicked - displays loading state initially and then renders loaded repositories seamlessly -#### `IngestionCatalogViewer.test.tsx` (4 tests) +#### `IngestionCatalogViewer.test.tsx` (5 tests) - renders summary catalog with git repos, monitored paths, and local storage stats - switches source type filters (git, monitored_path, local_storage) - toggles detail level to detailed and renders ingested files list - applies search and extension filters +- renders 4 distinct aligned stat cards with formatted badges and no emojis #### `LocalPathManager.test.tsx` (8 tests) - renders configured paths correctly @@ -1271,6 +1281,12 @@ and leaves the prior indexed state intact without data loss._ - displays PDF notice and hides text textarea when upload path is a PDF - prevents direct text replacement of PDF files in replace modal +#### `NavigatorBreadcrumbs.test.tsx` (4 tests) +- renders repo badge and clickable path segments +- triggers back and forward history buttons +- disables history buttons when navigation history bounds are reached +- toggles sidebar on sidebar button click + #### `NavigatorCodeViewer.test.tsx` (6 tests) - renders line numbers and code lines - highlights target line range and calls scrollIntoView diff --git a/app/api/routers/files.py b/app/api/routers/files.py index 601ee3c..a366523 100644 --- a/app/api/routers/files.py +++ b/app/api/routers/files.py @@ -26,10 +26,17 @@ async def api_read_file( end_line: Optional[int] = Query(None, description="1-based ending line number"), ): try: + raw_path = path.strip() if path else "" + if not raw_path or "\x00" in raw_path or any(part == ".." for part in raw_path.replace("\\", "/").split("/")): + return JSONResponse(status_code=400, content={"error": "Path traversal or invalid path detected."}) + raw_repo = repo.strip() if repo else None + if raw_repo and ("\x00" in raw_repo or any(part == ".." for part in raw_repo.replace("\\", "/").split("/"))): + return JSONResponse(status_code=400, content={"error": "Path traversal or invalid repo detected."}) + reader = get_file_reader_service() res = reader.read_file( - path=path, - repo=repo, + path=raw_path, + repo=raw_repo, start_line=start_line, end_line=end_line ) @@ -37,6 +44,8 @@ async def api_read_file( except ForbiddenError as fe: logger.warning(f"Forbidden access reading file '{path}': {fe}") return JSONResponse(status_code=403, content={"error": "Access denied. Path is outside allowed repositories."}) + except IsADirectoryError: + return JSONResponse(status_code=400, content={"error": f"Target path '{path}' is a directory, not a file."}) except FileNotFoundError as fne: logger.warning(f"File not found reading '{path}': {fne}") return JSONResponse(status_code=404, content={"error": "File not found."}) @@ -54,8 +63,12 @@ async def api_summarize_file(payload: FileSummarizePayload): raw_path = payload.path.strip() if payload.path else "" if not raw_path or "\x00" in raw_path or any(part == ".." for part in raw_path.replace("\\", "/").split("/")): return JSONResponse(status_code=400, content={"error": "Path traversal or invalid path detected."}) + raw_repo = payload.repo.strip() if payload.repo else None + if raw_repo and ("\x00" in raw_repo or any(part == ".." for part in raw_repo.replace("\\", "/").split("/"))): + return JSONResponse(status_code=400, content={"error": "Path traversal or invalid repo detected."}) + reader = get_file_reader_service() - safe_path, _ = reader.resolve_safe_path(raw_path, repo=payload.repo) + safe_path, _ = reader.resolve_safe_path(raw_path, repo=raw_repo) summarizer = get_summarizer_service() summary_text = summarizer.get_or_create_summary( filepath=safe_path, diff --git a/app/api/routers/repositories.py b/app/api/routers/repositories.py index 623d054..cc32a6d 100644 --- a/app/api/routers/repositories.py +++ b/app/api/routers/repositories.py @@ -9,11 +9,12 @@ from fastapi import APIRouter, Request from fastapi.responses import JSONResponse, StreamingResponse -from app.models.schemas import RepoConfig, LocalPathConfig, AutoSyncToggleRequest, SearchRequest +from app.models.schemas import RepoConfig, LocalPathConfig, AutoSyncToggleRequest, KeepShallowToggleRequest, SearchRequest import app.services.database as db_service import app.services.vector_store as vs_service import app.services.indexing as idx_service import app.services.search as search_service +import app.services.git_manager as gm_service from app.services.indexing.git_progress import progress_tracker logger = logging.getLogger("contextcortex.api") @@ -26,7 +27,7 @@ async def api_get_repos(): with db_service.get_db_connection() as conn: rows = conn.execute(""" SELECT id, name, url, branch, commit_sha, status, last_error, last_synced, - auth_token, provider, auth_user, auto_sync, webhook_secret + auth_token, provider, auth_user, auto_sync, keep_shallow, webhook_secret FROM git_repositories ORDER BY id DESC """).fetchall() @@ -53,10 +54,10 @@ async def api_add_repo(repo: RepoConfig): with db_service.get_db_connection() as conn: cursor = conn.cursor() cursor.execute( - """INSERT INTO git_repositories (name, url, branch, auth_token, provider, auth_user, auto_sync, webhook_secret) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", + """INSERT INTO git_repositories (name, url, branch, auth_token, provider, auth_user, auto_sync, keep_shallow, webhook_secret) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", (repo.name.strip(), repo.url.strip(), repo.branch or "main", repo.auth_token, repo.provider or "github", repo.auth_user, - 1 if repo.auto_sync else 0, repo.webhook_secret) + 1 if repo.auto_sync else 0, 1 if repo.keep_shallow else 0, repo.webhook_secret) ) conn.commit() repo_id = cursor.lastrowid @@ -84,6 +85,21 @@ async def api_toggle_repo_auto_sync(repo_id: int, payload: AutoSyncToggleRequest logger.error(f"Error toggling auto-sync for repo {repo_id}: {e}") return JSONResponse(status_code=500, content={"error": "Failed to toggle auto-sync for repository."}) +@router.patch("/admin/api/repos/{repo_id}/keep-shallow") +async def api_toggle_repo_keep_shallow(repo_id: int, payload: KeepShallowToggleRequest): + try: + with db_service.get_db_connection() as conn: + cursor = conn.cursor() + val = 1 if payload.keep_shallow else 0 + res = cursor.execute("UPDATE git_repositories SET keep_shallow = ? WHERE id = ?", (val, repo_id)) + conn.commit() + if res.rowcount == 0: + return JSONResponse(status_code=404, content={"error": f"Repository with ID {repo_id} not found"}) + return {"status": "success", "id": repo_id, "repo_id": repo_id, "keep_shallow": payload.keep_shallow} + except Exception as e: + logger.error(f"Error toggling keep-shallow for repo {repo_id}: {e}") + return JSONResponse(status_code=500, content={"error": "Failed to toggle keep-shallow for repository."}) + @router.post("/admin/api/repos/{repo_id}/sync") @router.post("/admin/api/repos/sync/{repo_id}") async def api_sync_repo(repo_id: int): @@ -180,6 +196,11 @@ async def api_delete_repo(repo_id: int): except Exception as e: logger.error(f"Error removing points from vector database for {name}: {e}") + try: + gm_service.cleanup_persistent_repo(name) + except Exception as e: + logger.warning(f"Error cleaning persistent clone directory for {name}: {e}") + return {"status": "success", "name": name, "deleted": name} except Exception as e: logger.error(f"Error deleting repo {repo_id}: {e}") diff --git a/app/api/routers/settings.py b/app/api/routers/settings.py index 47a24ee..77b2fa2 100644 --- a/app/api/routers/settings.py +++ b/app/api/routers/settings.py @@ -13,7 +13,7 @@ TokenRequest, HostCredentialRequest, VectorStoreTestRequest, VectorStoreSwitchRequest, AutoSyncSettingsRequest, EmbeddingSettingsRequest, - FileSettingsRequest + FileSettingsRequest, AIGatewaySettingsRequest ) import app.services.database as db_service import app.services.git_manager as gm_service @@ -386,6 +386,34 @@ async def api_discover_models(url: Optional[str] = None, api_key: Optional[str] logger.error(f"Error discovering models: {e}") return JSONResponse(status_code=500, content={"status": "error", "error": "Failed to discover models."}) +@router.get("/admin/api/settings/ai-gateway") +async def api_get_ai_gateway_settings(): + try: + cfg = db_service.get_ai_gateway_config() + return cfg + except Exception as e: + logger.error(f"Error reading AI gateway settings: {e}") + return JSONResponse(status_code=500, content={"error": "Failed to read AI gateway settings."}) + +@router.post("/admin/api/settings/ai-gateway") +async def api_save_ai_gateway_settings(payload: AIGatewaySettingsRequest): + try: + updated = db_service.set_ai_gateway_config( + url=payload.url, + api_key=payload.api_key, + chat_model=payload.chat_model, + vision_ocr_model=payload.vision_ocr_model, + embedding_model=payload.embedding_model, + ) + return { + "status": "success", + "message": "AI & Model Gateway settings saved successfully.", + "config": updated + } + except Exception as e: + logger.error(f"Error saving AI gateway settings: {e}") + return JSONResponse(status_code=500, content={"status": "error", "error": "Failed to save AI gateway settings."}) + @router.get("/admin/api/settings/embedding") async def api_get_embedding_settings(): try: diff --git a/app/models/schemas.py b/app/models/schemas.py index 2422f0f..c1fb443 100644 --- a/app/models/schemas.py +++ b/app/models/schemas.py @@ -16,6 +16,7 @@ class RepoConfig(BaseModel): auth_user: Optional[str] = None enabled: bool = True auto_sync: bool = True + keep_shallow: bool = False webhook_secret: Optional[str] = None class LocalPathConfig(BaseModel): @@ -166,6 +167,9 @@ class VectorStoreConfigRequest(BaseModel): class AutoSyncToggleRequest(BaseModel): auto_sync: bool +class KeepShallowToggleRequest(BaseModel): + keep_shallow: bool + class AutoSyncSettingsRequest(BaseModel): interval_mins: int global_webhook_secret: Optional[str] = None @@ -239,4 +243,13 @@ class NodeDetailsResponse(BaseModel): metadata: Optional[Dict[str, Any]] = None +class AIGatewaySettingsRequest(BaseModel): + url: Optional[str] = Field(None, description="OpenAI-compatible / LiteLLM API endpoint URL") + api_key: Optional[str] = Field(None, description="API key / Bearer token (optional, only updated if provided)") + chat_model: Optional[str] = Field(None, description="Default chat completion model") + vision_ocr_model: Optional[str] = Field(None, description="Default vision OCR model") + embedding_model: Optional[str] = Field(None, description="Default embedding model") + + + diff --git a/app/services/database/__init__.py b/app/services/database/__init__.py index ddb0b7b..20b6735 100644 --- a/app/services/database/__init__.py +++ b/app/services/database/__init__.py @@ -38,6 +38,8 @@ set_embedding_db_config, get_vision_ocr_model, get_chat_model, + get_ai_gateway_config, + set_ai_gateway_config, get_file_settings, set_file_settings, ensure_file_summaries_columns, @@ -108,6 +110,8 @@ "set_embedding_db_config", "get_vision_ocr_model", "get_chat_model", + "get_ai_gateway_config", + "set_ai_gateway_config", "get_file_settings", "set_file_settings", "ensure_file_summaries_columns", diff --git a/app/services/database/connection.py b/app/services/database/connection.py index e2a22bd..92c7c8d 100644 --- a/app/services/database/connection.py +++ b/app/services/database/connection.py @@ -301,7 +301,7 @@ def set_embedding_db_config( def get_vision_ocr_model() -> str: """Returns stored vision OCR model or fallback to env/default.""" - stored = get_metadata("vision_ocr_model") or get_metadata("embedding_vision_ocr_model") + stored = get_metadata("openai_vision_model") or get_metadata("vision_ocr_model") or get_metadata("embedding_vision_ocr_model") if stored and stored.strip(): return stored.strip() return (os.getenv("VISION_OCR_MODEL") or "gemini-2.5-flash").strip() @@ -309,15 +309,105 @@ def get_vision_ocr_model() -> str: def get_chat_model() -> str: """Returns stored chat model or fallback to env/default.""" - stored = get_metadata("chat_model") or get_metadata("embedding_chat_model") + stored = get_metadata("openai_chat_model") or get_metadata("chat_model") or get_metadata("embedding_chat_model") if stored and stored.strip(): return stored.strip() return (os.getenv("CHAT_MODEL") or "gemini-2.5-flash").strip() +def get_ai_gateway_config() -> Dict[str, Any]: + """ + Returns OpenAI-compatible AI Gateway configuration from database. + Seeds from environment variables if database metadata is uninitialized. + """ + url = get_metadata("openai_api_url") or get_metadata("embedding_litellm_url") + api_key = get_metadata("openai_api_key") or get_metadata("embedding_litellm_api_key") + chat_model = get_metadata("openai_chat_model") or get_metadata("chat_model") or get_metadata("embedding_chat_model") + vision_model = get_metadata("openai_vision_model") or get_metadata("vision_ocr_model") or get_metadata("embedding_vision_ocr_model") + embedding_model = get_metadata("openai_embedding_model") or get_metadata("embedding_dense_model") + + # Env seed fallback + if not url: + url = os.getenv("LITELLM_URL", "http://litellm:4000/v1").strip() + if not api_key: + api_key = os.getenv("LITELLM_API_KEY", "").strip() + if not chat_model: + chat_model = os.getenv("CHAT_MODEL", "gemini-2.5-flash").strip() + if not vision_model: + vision_model = os.getenv("VISION_OCR_MODEL", "gemini-2.5-flash").strip() + if not embedding_model: + embedding_model = os.getenv("EMBEDDING_MODEL", "BAAI/bge-small-en-v1.5").strip() + + masked = "" + if api_key: + if len(api_key) <= 8: + masked = "••••••••" + else: + prefix = api_key[:3] + suffix = api_key[-4:] + masked = f"{prefix}••••••••{suffix}" + + return { + "url": url, + "has_api_key": bool(api_key), + "masked_api_key": masked, + "chat_model": chat_model, + "vision_ocr_model": vision_model, + "embedding_model": embedding_model, + } + + +def set_ai_gateway_config( + url: Optional[str] = None, + api_key: Optional[str] = None, + chat_model: Optional[str] = None, + vision_ocr_model: Optional[str] = None, + embedding_model: Optional[str] = None, +) -> Dict[str, Any]: + """ + Persists OpenAI-compatible AI Gateway configuration in SQLite system_metadata. + """ + if url is not None: + set_metadata("openai_api_url", url.strip()) + set_metadata("embedding_litellm_url", url.strip()) + if api_key is not None: + cleaned_key = api_key.strip() + set_metadata("openai_api_key", cleaned_key) + set_metadata("embedding_litellm_api_key", cleaned_key) + if chat_model is not None: + set_metadata("openai_chat_model", chat_model.strip()) + set_metadata("chat_model", chat_model.strip()) + set_metadata("embedding_chat_model", chat_model.strip()) + if vision_ocr_model is not None: + set_metadata("openai_vision_model", vision_ocr_model.strip()) + set_metadata("vision_ocr_model", vision_ocr_model.strip()) + set_metadata("embedding_vision_ocr_model", vision_ocr_model.strip()) + if embedding_model is not None: + set_metadata("openai_embedding_model", embedding_model.strip()) + + return get_ai_gateway_config() + + +def ensure_git_repositories_columns(conn=None): + """Ensures keep_shallow column exists on git_repositories table.""" + try: + raw_conn = get_db_connection() + try: + cols = [r["name"] for r in raw_conn.execute("PRAGMA table_info(git_repositories)").fetchall()] + if cols and "keep_shallow" not in cols: + raw_conn.execute("ALTER TABLE git_repositories ADD COLUMN keep_shallow INTEGER DEFAULT 0") + raw_conn.commit() + logger.info("Migrated git_repositories: added keep_shallow column") + finally: + raw_conn.close() + except Exception as e: + logger.debug(f"Migration error for git_repositories: {e}") + + def ensure_file_summaries_columns(conn=None): - """Ensures summary_text column exists on file_summaries table.""" + """Ensures summary_text column exists on file_summaries table and git_repositories has keep_shallow.""" try: + ensure_git_repositories_columns(conn) raw_conn = get_db_connection() try: cols = [r["name"] for r in raw_conn.execute("PRAGMA table_info(file_summaries)").fetchall()] diff --git a/app/services/database/schema.py b/app/services/database/schema.py index 24571b9..2f72163 100644 --- a/app/services/database/schema.py +++ b/app/services/database/schema.py @@ -49,6 +49,7 @@ Column("auth_user", Text, nullable=True), Column("enabled", Integer, default=1, server_default="1"), Column("auto_sync", Integer, default=1, server_default="1"), + Column("keep_shallow", Integer, default=0, server_default="0"), Column("webhook_secret", Text, nullable=True), Column("status", Text, default="pending", server_default="pending"), Column("last_error", Text, nullable=True), diff --git a/app/services/file_reader.py b/app/services/file_reader.py index d0c8d84..69e58cd 100644 --- a/app/services/file_reader.py +++ b/app/services/file_reader.py @@ -73,6 +73,26 @@ def _get_authorized_indexed_paths(self) -> List[Dict[str, Any]]: logger.warning(f"Failed to fetch authorized indexed_paths: {e}") return [] + def _get_authorized_persistent_repos(self) -> Dict[str, str]: + try: + from app.services.git_manager import PERSISTENT_REPOS_DIR + base_dir = os.path.normpath(os.path.abspath(PERSISTENT_REPOS_DIR)) + base_prefix = base_dir if base_dir.endswith(os.sep) else base_dir + os.sep + with get_db_connection() as conn: + rows = conn.execute("SELECT name FROM git_repositories").fetchall() + repos: Dict[str, str] = {} + for r in rows: + name = str(r["name"]).strip() + safe_name = os.path.basename(name) + if safe_name and safe_name not in (".", ".."): + target = os.path.normpath(os.path.abspath(os.path.join(base_dir, safe_name))) + if target.startswith(base_prefix) or target == base_dir: + repos[name] = target + return repos + except Exception as e: + logger.warning(f"Failed to fetch authorized persistent repos: {e}") + return {} + def resolve_safe_path(self, path: str, repo: Optional[str] = None) -> Tuple[str, str]: """Resolves target path safely within authorized watched paths or local storage. @@ -105,6 +125,21 @@ def resolve_safe_path(self, path: str, repo: Optional[str] = None) -> Tuple[str, # 2. repo specified matching indexed_paths or git_repositories if effective_repo: + # Check persistent shallow clone if available + try: + persistent_repos = self._get_authorized_persistent_repos() + if effective_repo in persistent_repos: + repo_disk_dir = persistent_repos[effective_repo] + rel_clean = path.split("://", 1)[1] if "://" in path else path.lstrip("/") + cand_repo = os.path.normpath(os.path.abspath(os.path.join(repo_disk_dir, rel_clean))) + if cand_repo.startswith(repo_disk_dir): + if os.path.lexists(cand_repo): + if not self._is_within_root(cand_repo, repo_disk_dir): + raise ValueError("Path outside authorized roots") + return cand_repo, "persistent_repo" + except Exception as e: + logger.debug(f"Persistent repo lookup check failed: {e}") + matching_paths = [ip for ip in indexed_paths if ip.get("repo") == effective_repo] if os.path.isabs(path): @@ -207,6 +242,82 @@ def is_binary_file(self, abs_path: str) -> bool: chunk = f.read(8192) return b"\x00" in chunk + def _read_from_vector_store( + self, + path: str, + repo: Optional[str] = None, + start_line: Optional[int] = None, + end_line: Optional[int] = None, + max_lines: Optional[int] = None, + ) -> Optional[Dict[str, Any]]: + """Reconstructs file content from vector store chunks when file is not on disk.""" + try: + from app.services.vector_store import get_vector_store + from qdrant_client.http import models as qmodels + store = get_vector_store() + if not hasattr(store, "client") or not store.client: + return None + + clean_path = path.replace("\\", "/").strip("/") + rel_path = clean_path.split("://", 1)[1] if "://" in clean_path else clean_path + if repo and rel_path.startswith(f"{repo}/"): + rel_path = rel_path[len(repo) + 1:] + + must_conditions = [] + if repo and repo not in ("__all__", "all"): + must_conditions.append(qmodels.FieldCondition(key="repo", match=qmodels.MatchValue(value=repo))) + + should_conditions = [ + qmodels.FieldCondition(key="rel_path", match=qmodels.MatchValue(value=rel_path)), + qmodels.FieldCondition(key="path", match=qmodels.MatchValue(value=path)), + qmodels.FieldCondition(key="rel_path", match=qmodels.MatchValue(value=path)), + ] + filter_obj = qmodels.Filter( + must=must_conditions if must_conditions else None, + should=should_conditions + ) + + records, _ = store.client.scroll( + collection_name=store.collection_name, + scroll_filter=filter_obj, + limit=250, + with_payload=True, + with_vectors=False + ) + if not records: + return None + + sorted_chunks = sorted(records, key=lambda p: (p.payload.get("start_line", 0), p.payload.get("end_line", 0))) + content_pieces = [] + for p in sorted_chunks: + text = p.payload.get("content") or p.payload.get("text") or "" + if text.strip(): + content_pieces.append(text) + + full_text = "\n\n".join(content_pieces) + lines = full_text.splitlines() + total_lines = len(lines) + s_line = start_line if (start_line is not None and start_line >= 1) else 1 + e_line = end_line if end_line is not None else total_lines + + start_idx = max(0, s_line - 1) + end_idx = min(total_lines, e_line) + sliced = "\n".join(lines[start_idx:end_idx]) + + return { + "filepath": path, + "content": sliced, + "start_line": s_line, + "end_line": end_idx, + "total_lines": total_lines, + "size_bytes": len(full_text.encode("utf-8")), + "truncated": False, + "source": "vector_store", + } + except Exception as e: + logger.warning(f"Error reading from vector store fallback for {path}: {e}") + return None + def read_file( self, path: str, @@ -216,10 +327,20 @@ def read_file( max_lines: Optional[int] = None, ) -> Dict[str, Any]: """Reads a file with safe path resolution, binary checking, and line slicing.""" - abs_path, source_type = self.resolve_safe_path(path, repo=repo) - - if not os.path.exists(abs_path): + try: + abs_path, source_type = self.resolve_safe_path(path, repo=repo) + if not abs_path.startswith("/"): + raise ValueError(f"Target path is not absolute: {path}") + if not os.path.exists(abs_path): + raise FileNotFoundError(f"File not found on disk: {path}") + except (ValueError, FileNotFoundError): + vec_res = self._read_from_vector_store( + path=path, repo=repo, start_line=start_line, end_line=end_line, max_lines=max_lines + ) + if vec_res: + return vec_res raise FileNotFoundError(f"File not found: {path}") + if os.path.isdir(abs_path): raise IsADirectoryError(f"Target path is a directory: {path}") diff --git a/app/services/git_manager.py b/app/services/git_manager.py index 68deef0..29e2d9e 100644 --- a/app/services/git_manager.py +++ b/app/services/git_manager.py @@ -11,6 +11,30 @@ logger = logging.getLogger("contextcortex.git") TMP_BASE_DIR = os.getenv("TMP_REPOS_DIR", "/tmp/rag_repos") +DATA_DIR = os.getenv( + "DATA_DIR", + "/app/data" if os.path.exists("/app/data") else os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "data")) +) +PERSISTENT_REPOS_DIR = os.getenv("PERSISTENT_REPOS_DIR", os.path.join(DATA_DIR, "repos")) + +def get_persistent_repo_dir(repo_name: str) -> str: + """Returns absolute path for persistent shallow repository clone.""" + if not repo_name or not isinstance(repo_name, str): + raise ValueError("Invalid repo_name") + safe_name = os.path.basename(repo_name.strip()) + if not safe_name or safe_name in (".", ".."): + raise ValueError("Invalid repo_name") + base_dir = os.path.realpath(PERSISTENT_REPOS_DIR) + resolved = os.path.realpath(os.path.join(base_dir, safe_name)) + base_prefix = base_dir if base_dir.endswith(os.sep) else base_dir + os.sep + if not (resolved.startswith(base_prefix) or resolved == base_dir): + raise ValueError(f"Path traversal detected in repo_name: {repo_name}") + return resolved + +def cleanup_persistent_repo(repo_name: str) -> None: + """Safely removes persistent shallow clone for a deleted repo.""" + target = get_persistent_repo_dir(repo_name) + cleanup_repo_dir(target) def get_env_token() -> Optional[str]: """Retrieve GitHub token from environment variables.""" @@ -157,16 +181,22 @@ def shallow_clone_repo( token: Optional[str] = None, username: Optional[str] = None, provider: Optional[str] = None, - repo_id: Optional[str] = None + repo_id: Optional[str] = None, + target_dir: Optional[str] = None ) -> CloneResult: """ - Shallow clones a git repository to a temporary directory. + Shallow clones a git repository to a temporary or persistent directory. Returns: CloneResult """ from app.services.database import get_effective_git_token norm_url = normalize_git_url(git_url) - os.makedirs(TMP_BASE_DIR, exist_ok=True) - repo_dir = tempfile.mkdtemp(prefix=f"repo_{repo_id or 'temp'}_", dir=TMP_BASE_DIR) + + if target_dir: + os.makedirs(os.path.dirname(os.path.abspath(target_dir)), exist_ok=True) + repo_dir = os.path.abspath(target_dir) + else: + os.makedirs(TMP_BASE_DIR, exist_ok=True) + repo_dir = tempfile.mkdtemp(prefix=f"repo_{repo_id or 'temp'}_", dir=TMP_BASE_DIR) if not token and not username: eff_token, eff_user, _ = get_effective_git_token(norm_url, provider=provider) @@ -178,6 +208,20 @@ def shallow_clone_repo( logger.info(f"Cloning {safe_url} (branch: {branch}) shallowly to {repo_dir}...") try: + # Check if already a git repo in target_dir (incremental shallow update) + if target_dir and os.path.exists(os.path.join(repo_dir, ".git")): + logger.info(f"Existing shallow clone found in {repo_dir}; fetching latest refs...") + fetch_cmd = ["git", "fetch", "--depth", "1", "origin", branch] + f_res = subprocess.run(fetch_cmd, cwd=repo_dir, capture_output=True, text=True, timeout=60, env=GIT_ENV) + if f_res.returncode == 0: + subprocess.run(["git", "reset", "--hard", f"origin/{branch}"], cwd=repo_dir, capture_output=True, text=True, timeout=30, env=GIT_ENV) + sha_res = subprocess.run(["git", "rev-parse", "HEAD"], cwd=repo_dir, capture_output=True, text=True, timeout=10, env=GIT_ENV) + commit_sha = sha_res.stdout.strip() if sha_res.returncode == 0 else "unknown" + return CloneResult(temp_dir=repo_dir, commit_sha=commit_sha, error=None) + else: + logger.warning(f"Incremental fetch failed in {repo_dir}; falling back to fresh clone: {f_res.stderr.strip()}") + cleanup_repo_dir(repo_dir) + # Shallow clone single branch cmd = [ "git", "clone", @@ -194,7 +238,8 @@ def shallow_clone_repo( res = subprocess.run(cmd_fallback, capture_output=True, text=True, timeout=120, env=GIT_ENV) if res.returncode != 0: err_msg = res.stderr.strip() or res.stdout.strip() or "Git clone failed" - cleanup_repo_dir(repo_dir) + if not target_dir: + cleanup_repo_dir(repo_dir) return CloneResult(temp_dir=None, commit_sha=None, error=f"Clone failed: {err_msg}") # Get Commit SHA @@ -203,7 +248,8 @@ def shallow_clone_repo( return CloneResult(temp_dir=repo_dir, commit_sha=commit_sha, error=None) except Exception as e: - cleanup_repo_dir(repo_dir) + if not target_dir: + cleanup_repo_dir(repo_dir) return CloneResult(temp_dir=None, commit_sha=None, error=f"Exception during git clone: {str(e)}") def cleanup_repo_dir(repo_dir: Optional[str]): diff --git a/app/services/indexing/git_syncer.py b/app/services/indexing/git_syncer.py index dd41a78..a899de2 100644 --- a/app/services/indexing/git_syncer.py +++ b/app/services/indexing/git_syncer.py @@ -96,6 +96,7 @@ def sync_single_git_repo(repo_id: int): """Ephemeral shallow clone, delta calculation, incremental vector upsert, and immediate disk cleanup.""" repo_name = f"repo-{repo_id}" temp_dir = None + keep_shallow = False try: with db_service.get_db_connection() as conn: repo_row = conn.execute("SELECT * FROM git_repositories WHERE id = ?", (repo_id,)).fetchone() @@ -108,6 +109,7 @@ def sync_single_git_repo(repo_id: int): per_repo_token = repo_row["auth_token"] per_repo_user = repo_row["auth_user"] if "auth_user" in repo_row.keys() else None provider = repo_row["provider"] if "provider" in repo_row.keys() else None + keep_shallow = bool(repo_row["keep_shallow"]) if "keep_shallow" in repo_row.keys() and repo_row["keep_shallow"] else False progress_tracker.get_or_create_job(repo_id, repo_name) @@ -144,8 +146,10 @@ def sync_single_git_repo(repo_id: int): conn.execute("UPDATE git_repositories SET status = 'syncing' WHERE id = ?", (repo_id,)) conn.commit() + target_dir = gm_service.get_persistent_repo_dir(repo_name) if keep_shallow else None + clone_mode_label = f"persistent storage ({target_dir})" if keep_shallow else "ephemeral directory" progress_tracker.update_step(repo_id, 2, "Shallow Cloning Repository", pct=25) - progress_tracker.log(repo_id, "INFO", f"Cloning branch '{branch}' to ephemeral directory...") + progress_tracker.log(repo_id, "INFO", f"Cloning branch '{branch}' to {clone_mode_label}...") clone_start = time.time() clone_res = gm_service.shallow_clone_repo( @@ -154,7 +158,8 @@ def sync_single_git_repo(repo_id: int): token=effective_token, username=effective_user, provider=provider, - repo_id=str(repo_id) + repo_id=str(repo_id), + target_dir=target_dir ) temp_dir = clone_res.temp_dir commit_sha = clone_res.commit_sha @@ -412,8 +417,11 @@ def sync_single_git_repo(repo_id: int): except Exception: pass finally: - # Crucial: Ephemeral disk cleanup! - gm_service.cleanup_repo_dir(temp_dir) + # Clean up ephemeral clone, or retain persistent shallow copy + if not keep_shallow and temp_dir: + gm_service.cleanup_repo_dir(temp_dir) + elif keep_shallow and temp_dir: + logger.info(f"Retained persistent shallow copy for repo '{repo_name}' at {temp_dir}") def run_full_indexing(): diff --git a/app/services/navigator.py b/app/services/navigator.py index 168f159..b1266d7 100644 --- a/app/services/navigator.py +++ b/app/services/navigator.py @@ -5,8 +5,17 @@ logger = logging.getLogger("contextcortex.navigator") -def _clean_path(p: str) -> str: - return p.replace("\\", "/").strip("/") +def _clean_path(p: str, repo: Optional[str] = None) -> str: + cleaned = p.replace("\\", "/") + if "://" in cleaned: + cleaned = cleaned.split("://", 1)[1] + if repo and cleaned.startswith(f"{repo}:"): + cleaned = cleaned[len(repo) + 1:] + return cleaned.strip("/") + +def _path_parts(filepath: str, repo: Optional[str] = None) -> List[str]: + cleaned = _clean_path(filepath, repo=repo) + return [part for part in cleaned.split("/") if part.strip()] def get_navigator_tree(repo: str) -> Optional[Dict[str, Any]]: with get_db_connection() as conn: @@ -33,7 +42,7 @@ def get_navigator_tree(repo: str) -> Optional[Dict[str, Any]]: f"SELECT filepath, count(*) as cnt FROM ast_symbols{where} GROUP BY filepath", params ).fetchall(): - sym_counts[_clean_path(row["filepath"])] = row["cnt"] + sym_counts[_clean_path(row["filepath"], repo=repo)] = row["cnt"] # Fetch route counts per file route_counts = {} @@ -41,28 +50,75 @@ def get_navigator_tree(repo: str) -> Optional[Dict[str, Any]]: f"SELECT filepath, count(*) as cnt FROM api_routes{where} GROUP BY filepath", params ).fetchall(): - route_counts[_clean_path(row["filepath"])] = row["cnt"] + route_counts[_clean_path(row["filepath"], repo=repo)] = row["cnt"] # Build hierarchical tree - root = {"children": {}} - for f in file_rows: - raw_path = _clean_path(f["filepath"]) - parts = raw_path.split("/") - curr = root - for i, part in enumerate(parts): - is_last = (i == len(parts) - 1) - if part not in curr["children"]: - curr["children"][part] = { - "id": f"{'file' if is_last else 'dir'}:{'/'.join(parts[:i+1])}", - "name": part, - "is_dir": not is_last, - "path": "/".join(parts[:i+1]), - "children": {} if not is_last else None, - "language": f["language"] if is_last else None, - "symbol_count": sym_counts.get(raw_path, 0) if is_last else 0, - "route_count": route_counts.get(raw_path, 0) if is_last else 0, - } - curr = curr["children"][part] + if repo == "__all__": + # Group files by repo source, each gets its own root node + sources: Dict[str, list] = {} + for f in file_rows: + sources.setdefault(f["repo"], []).append(f) + + root_children = {} + for source_name, source_files in sorted(sources.items()): + source_root = {"children": {}} + for f in source_files: + raw_path = _clean_path(f["filepath"], repo=f["repo"]) + parts = _path_parts(f["filepath"], repo=f["repo"]) + if not parts: + continue + curr = source_root + for i, part in enumerate(parts): + is_last = (i == len(parts) - 1) + rel_path = f"{source_name}/{'/'.join(parts[:i+1])}" + if part not in curr["children"]: + curr["children"][part] = { + "id": f"{'file' if is_last else 'dir'}:{rel_path}", + "name": part, + "is_dir": not is_last, + "path": rel_path, + "abs_path": f["filepath"] if is_last else None, + "children": {} if not is_last else None, + "language": f["language"] if is_last else None, + "symbol_count": sym_counts.get(raw_path, 0) if is_last else 0, + "route_count": route_counts.get(raw_path, 0) if is_last else 0, + } + curr = curr["children"][part] + root_children[source_name] = { + "id": f"source:{source_name}", + "name": source_name, + "is_dir": True, + "path": source_name, + "children": source_root["children"], + "language": None, + "symbol_count": 0, + "route_count": 0, + } + root = {"children": root_children} + else: + root = {"children": {}} + for f in file_rows: + raw_path = _clean_path(f["filepath"], repo=f["repo"]) + parts = _path_parts(f["filepath"], repo=f["repo"]) + if not parts: + continue + curr = root + for i, part in enumerate(parts): + is_last = (i == len(parts) - 1) + rel_path = "/".join(parts[:i+1]) + if part not in curr["children"]: + curr["children"][part] = { + "id": f"{'file' if is_last else 'dir'}:{rel_path}", + "name": part, + "is_dir": not is_last, + "path": rel_path, + "abs_path": f["filepath"] if is_last else None, + "children": {} if not is_last else None, + "language": f["language"] if is_last else None, + "symbol_count": sym_counts.get(raw_path, 0) if is_last else 0, + "route_count": route_counts.get(raw_path, 0) if is_last else 0, + } + curr = curr["children"][part] def _format_node(n): node = { @@ -70,6 +126,7 @@ def _format_node(n): "name": n["name"], "is_dir": n["is_dir"], "path": n["path"], + "abs_path": n.get("abs_path"), "language": n["language"], "symbol_count": n["symbol_count"], "route_count": n["route_count"], diff --git a/docs/assets/desktop_add-repo-modal.png b/docs/assets/desktop_add-repo-modal.png new file mode 100644 index 0000000..1b1a5d6 Binary files /dev/null and b/docs/assets/desktop_add-repo-modal.png differ diff --git a/docs/assets/desktop_codebase-navigator.png b/docs/assets/desktop_codebase-navigator.png index 0b64072..2cb74b4 100644 Binary files a/docs/assets/desktop_codebase-navigator.png and b/docs/assets/desktop_codebase-navigator.png differ diff --git a/docs/assets/desktop_diagnostics.png b/docs/assets/desktop_diagnostics.png index a82cf0b..b20f0d4 100644 Binary files a/docs/assets/desktop_diagnostics.png and b/docs/assets/desktop_diagnostics.png differ diff --git a/docs/assets/desktop_git-repos.png b/docs/assets/desktop_git-repos.png index 32058f2..7230f59 100644 Binary files a/docs/assets/desktop_git-repos.png and b/docs/assets/desktop_git-repos.png differ diff --git a/docs/assets/desktop_local-paths.png b/docs/assets/desktop_local-paths.png index 407d2aa..673f517 100644 Binary files a/docs/assets/desktop_local-paths.png and b/docs/assets/desktop_local-paths.png differ diff --git a/docs/assets/desktop_overview.png b/docs/assets/desktop_overview.png index baa1ed6..afccbc4 100644 Binary files a/docs/assets/desktop_overview.png and b/docs/assets/desktop_overview.png differ diff --git a/docs/assets/desktop_search-inspector.png b/docs/assets/desktop_search-inspector.png index 78fa215..49b722b 100644 Binary files a/docs/assets/desktop_search-inspector.png and b/docs/assets/desktop_search-inspector.png differ diff --git a/docs/assets/desktop_settings.png b/docs/assets/desktop_settings.png index b67181b..f0951fe 100644 Binary files a/docs/assets/desktop_settings.png and b/docs/assets/desktop_settings.png differ diff --git a/docs/guide/index.md b/docs/guide/index.md index b57553f..d8af6c0 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -19,11 +19,11 @@ ContextCortex solves this problem. It parses source files into Abstract Syntax T - **Multi-Vector Retrieval**: ContextCortex connects to Qdrant, ChromaDB, and PostgreSQL pgvector. It combines dense semantic vectors with BM25 lexical keywords using Reciprocal Rank Fusion (RRF). - **Universal Git Ingestion**: - The system indexes repositories from GitHub, GitLab, Gitea, Forgejo, Bitbucket, and generic Git hosts. It uses shallow clones and removes files after indexing to save disk space. + The system indexes repositories from GitHub, GitLab, Gitea, Forgejo, Bitbucket, and generic Git hosts. It supports shallow clones and optional persistent copy retention for instant code viewing. - **Managed Local Storage**: Users can upload files and PDF documents directly to the system. The system indexes text immediately and extracts text from images with optical character recognition (OCR). -- **3-Pane Codebase Navigator**: - A web interface provides file trees, symbol outlines, and caller-callee relationship graphs. +- **Codebase Navigator & Omni-Search**: + A split interface provides file trees, symbol outlines, full code viewing, and caller-callee relationship graphs. - **Security and Access Control**: The server supports OAuth 2.1 (RFC 9728) and database-backed API keys. It enforces role-based access control with three privilege levels: Viewer, Editor, and Admin. diff --git a/docs/guide/user-guide/git-repositories.md b/docs/guide/user-guide/git-repositories.md index e458366..94fd1f4 100644 --- a/docs/guide/user-guide/git-repositories.md +++ b/docs/guide/user-guide/git-repositories.md @@ -23,24 +23,31 @@ Follow these steps to connect a Git repository to ContextCortex: 6. **Authentication**: - Provide an optional personal access token (PAT), or - Leave empty to automatically inherit saved domain credentials from the [Git Host Credential Vault](/guide/user-guide/settings). -7. Click **Save Repository**. +7. **Keep Shallow Copy on Disk** *(New in v2.16.0)*: + - Check **Keep shallow copy on disk** to retain a shallow clone (`--depth 1`) in persistent storage under `/app/data/repos/{repo_name}`. + - When enabled, Code Navigator can instantly load unchunked source files for full inspection, and subsequent syncs use fast incremental `git fetch --depth 1` instead of re-cloning from scratch. + - You can also toggle shallow retention on or off at any time via the **Shallow Copy** badge in the repository list table. +8. Click **Add & Start Sync**. --- ## Synchronizing Repositories -ContextCortex performs ephemeral shallow clones (`git clone --depth 1`) directly into an isolated workspace, parses AST chunks and docstrings into the vector store, updates the unified catalog, and purges the cloned files immediately from disk. +ContextCortex supports both **ephemeral** and **persistent** shallow ingestion: +- **Ephemeral Sync (Default)**: Authenticated shallow clone (`git clone --depth 1`) directly into an isolated directory, extracts AST chunks, symbols, and vector embeddings into the vector store, updates the relational catalog, and immediately purges the cloned files from disk to conserve host storage. +- **Persistent Shallow Copy Retention**: Retains the `--depth 1` clone on the persistent `repo_cache` Docker volume, enabling instantaneous full source inspection in Code Navigator and lightning-fast delta updates. ### Manual Synchronization -Click the **Sync Now** button on any repository card to trigger an immediate update. A live progress drawer displays: -- Git clone phase +Click the **Sync** button on any repository row or card to trigger an immediate update. A live progress drawer displays: +- Remote ref verification +- Shallow clone / incremental fetch phase - Tree-sitter AST parsing status - Vector embedding generation progress -- Total vectors created and elapsed time +- Total vectors created, AST symbols extracted, and elapsed time ### Automated Webhooks & Polling -- **Auto-Sync Poller**: Repositories with auto-sync enabled are re-indexed periodically by the background daemon based on the configured interval (default: every 15 minutes). -- **Webhooks**: Click **Webhook** on any repository card to view the pre-configured webhook endpoint (`/api/webhooks/git`) and secret payload for GitHub, GitLab, or Gitea push events. +- **Auto-Sync Poller**: Repositories with auto-sync enabled are re-indexed periodically by the background daemon based on the configured interval (default: every 15 minutes). You can toggle auto-sync directly from the table. +- **Webhooks**: Click **Webhook** on any repository card to open the webhook modal showing the pre-configured endpoint (`/api/webhooks/git`) and HMAC secret token for GitHub, GitLab, or Gitea push events. Both the Add Repo and Webhook modals render via full-screen React Portals to guarantee clean, unclipped displays. --- diff --git a/docs/guide/user-guide/navigator.md b/docs/guide/user-guide/navigator.md index eee7ac4..8220988 100644 --- a/docs/guide/user-guide/navigator.md +++ b/docs/guide/user-guide/navigator.md @@ -1,56 +1,52 @@ -# 3-Pane Codebase Navigator +# Codebase Navigator & Omni-Search -The **Codebase Navigator** provides high-performance exploration of project directory structures, Tree-sitter AST declarations, API endpoints, and caller/callee code relationships. +The **Codebase Navigator** provides high-performance exploration of project directory structures, Tree-sitter AST declarations, API endpoints, caller/callee code relationships, full syntax-highlighted source code, and rendered Markdown documentation. --- ![Codebase Navigator](/assets/desktop_codebase-navigator.png) -## The Three Synchronized Panes +## Hero Split View Architecture -The Codebase Navigator organizes project structure into three synchronized interactive panes: +The Codebase Navigator features a modern split layout optimized for code reading and deep architectural navigation: -### Pane 1: Files & Modules Tree -- Browse directory hierarchies and file trees with instant search filtering. -- Displays AST symbol badges and REST route counts next to each file. -- Filter by file extension or path fragment using the quick search bar. -- Selecting any file automatically populates Panes 2 and 3. +### 1. Left Sidebar: Dual-Tab Tree & Outline +- **Files Tab**: + - Hierarchical directory and file tree with live search filtering and expand/collapse controls. + - Per-repository directory expansion persistence: expanded folder states are automatically saved and restored per repo across browser sessions (`sessionStorage`). + - AST symbol count badges and API route indicators next to files and directories. + - File-type icons (Python, TypeScript, JavaScript, C#, C++, Go, Rust, SQL, COBOL, JSON, Markdown, Dockerfile, etc.). +- **Symbols Tab**: + - Syntax-aware outline of all functions, classes, interfaces, and API routes extracted by Tree-sitter. + - Quick category filter chips: **All Symbols**, **Functions**, **Classes & Structs**, and **API Routes**. + - Parameter signatures, return types, line ranges, and HTTP method badges (`GET`, `POST`, etc.). -### Pane 2: Symbols & Routes Outline -- Lists all declared functions, classes, interfaces, and API routes extracted by Tree-sitter. -- Filter symbols with quick category chips: - - **All Symbols** - - **Functions** (`def`, `function`, `fn`) - - **Classes & Structs** (`class`, `struct`, `interface`) - - **API Routes** (`@app.get`, `app.post`, etc.) -- View parameter signatures, return types, and starting/ending line ranges. - -### Universal Omni-Search -- **Global Command Palette**: Search across AST symbols, files, API routes, and raw code text in real time from the center toolbar. +### 2. Universal Omni-Search Command Palette (`Ctrl+K` / `Cmd+K`) +- **Global Command Palette**: Instant fuzzy search across repositories, file paths, AST symbols, and API endpoints from the toolbar or via `Ctrl+K` / `Cmd+K`. - **Match Types & Confidence Scoring**: - - `[symbol]` – High confidence AST matches (e.g., `99% AST exact match`, `94% AST prefix match`). + - `[symbol]` – High-confidence AST matches (e.g. `99% AST exact match`, `94% AST prefix match`). - `[file]` – File path and module matches (`92% filename match`). - `[route]` – REST endpoint paths (`95% route match`). - - `[code]` – Raw source code substring occurrences (`88% code match`). - **Keyboard Navigation**: Navigate results with ↑ and ↓, press Enter to jump, or Esc to dismiss. -- **Zero-Shift Floating Overlay**: Floats smoothly over the 3 panes without shifting header layout or altering pane heights. +- **Zero-Shift Floating Overlay**: Floats smoothly over the viewports without altering page heights. -### Pane 3: Code Intelligence & Full Source Viewer (or Document Reader) -- **Symbol Intelligence Mode**: - - **Callers & Callees**: Inspect incoming callers and outgoing references identified by AST cross-file analysis. - - **Click-Through Navigation**: Click any caller or callee chip to jump directly to its declaration in Pane 1 and Pane 2. - - **HTTP Route Specifications**: View endpoint paths, HTTP verbs, and request/response models. - - **Syntax Preview**: Read formatted implementation code blocks with line numbers and syntax highlighting. +### 3. Center Hero Viewport: Code Viewer, Doc Reader & Intelligence - **Full Source Code Viewer**: - - Automatically loads full file contents with line numbering. - - **Target Line Highlighting**: Seamlessly scrolls to and highlights target line ranges (`targetStartLine` / `targetEndLine`) when jumping from Omni-Search or symbols outline. - - **Docked Symbol Impact Drawer**: Collapsible bottom drawer showing caller and callee counts with instant click-to-jump. - - **Copy Code & Link**: One-click actions to copy clean source code or deep links to specific lines. -- **Full Document & Markdown Reader Mode**: - - Automatically activates when selecting non-code files (`.md`, `.markdown`, `.txt`, `.json`, etc.) or clicking **Read Full Document** from Pane 2. - - **Rendered View**: Safe Markdown parser supporting formatted headings, lists, blockquotes, inline code, and fenced code blocks. - - **Raw Source View**: Full file content display with line-by-line numbering. - - **One-Click Copy**: Copy complete document contents to clipboard with instant visual confirmation. + - Syntax highlighting powered by Prism across 8+ languages including C#, Python, JavaScript/TypeScript, C++, Go, Rust, SQL, and COBOL. + - Line numbers with target line range highlighting (`targetStartLine`-`targetEndLine`) when jumping from Omni-Search or the symbols outline. + - Copy clean file contents or permalinks directly to the clipboard. + - Collapsible bottom drawer for quick caller and callee inspection. +- **Enhanced Markdown Document Reader**: + - Safe GitHub Flavored Markdown (GFM) renderer for `.md` and `.markdown` files. + - **Automatic Table Normalization**: Automatically bridges blank lines between table rows and synthesizes missing separator rows (`| --- | --- |`) for non-standard Markdown tables. + - **Interactive Mermaid Diagrams**: Embedded Mermaid diagrams (`flowchart`, `sequenceDiagram`, `classDiagram`, `erDiagram`, `stateDiagram`) render directly inline. + - Switch seamlessly between **Rendered** and **Raw Source** view modes. +- **Instant Code Intelligence & Impact Inspector**: + - Immediately available via the **Intelligence** tab on any opened file—no prior outline clicks required. + - Symbol kind, file path, line ranges, and 4-metric overview (Incoming Callers, Outgoing Callees, Total Imports, Language). + - Framework API route mapping card (`POST /v1/chat/completions`). + - Formatted signature block and docstring summary. + - Clickable caller and callee cards for cross-file jump navigation. --- diff --git a/docs/images/codebase-navigator.png b/docs/images/codebase-navigator.png index 0b64072..2cb74b4 100644 Binary files a/docs/images/codebase-navigator.png and b/docs/images/codebase-navigator.png differ diff --git a/docs/public/assets/desktop_add-repo-modal.png b/docs/public/assets/desktop_add-repo-modal.png new file mode 100644 index 0000000..1b1a5d6 Binary files /dev/null and b/docs/public/assets/desktop_add-repo-modal.png differ diff --git a/docs/public/assets/desktop_codebase-navigator.png b/docs/public/assets/desktop_codebase-navigator.png index 0b64072..2cb74b4 100644 Binary files a/docs/public/assets/desktop_codebase-navigator.png and b/docs/public/assets/desktop_codebase-navigator.png differ diff --git a/docs/public/assets/desktop_diagnostics.png b/docs/public/assets/desktop_diagnostics.png index a82cf0b..b20f0d4 100644 Binary files a/docs/public/assets/desktop_diagnostics.png and b/docs/public/assets/desktop_diagnostics.png differ diff --git a/docs/public/assets/desktop_git-repos.png b/docs/public/assets/desktop_git-repos.png index 32058f2..7230f59 100644 Binary files a/docs/public/assets/desktop_git-repos.png and b/docs/public/assets/desktop_git-repos.png differ diff --git a/docs/public/assets/desktop_local-paths.png b/docs/public/assets/desktop_local-paths.png index 407d2aa..673f517 100644 Binary files a/docs/public/assets/desktop_local-paths.png and b/docs/public/assets/desktop_local-paths.png differ diff --git a/docs/public/assets/desktop_overview.png b/docs/public/assets/desktop_overview.png index baa1ed6..afccbc4 100644 Binary files a/docs/public/assets/desktop_overview.png and b/docs/public/assets/desktop_overview.png differ diff --git a/docs/public/assets/desktop_search-inspector.png b/docs/public/assets/desktop_search-inspector.png index 78fa215..49b722b 100644 Binary files a/docs/public/assets/desktop_search-inspector.png and b/docs/public/assets/desktop_search-inspector.png differ diff --git a/docs/public/assets/desktop_settings.png b/docs/public/assets/desktop_settings.png index b67181b..f0951fe 100644 Binary files a/docs/public/assets/desktop_settings.png and b/docs/public/assets/desktop_settings.png differ diff --git a/docs/superpowers/plans/2026-09-29-code-navigator-and-settings-overhaul.md b/docs/superpowers/plans/2026-09-29-code-navigator-and-settings-overhaul.md new file mode 100644 index 0000000..6b79572 --- /dev/null +++ b/docs/superpowers/plans/2026-09-29-code-navigator-and-settings-overhaul.md @@ -0,0 +1,507 @@ +# Code Navigator Hero Layout, Stable Toolbar, OpenAI Gateway, and Settings Overhaul Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Overhaul the Code Navigator into an IDE/GitHub-style code-hero experience with breadcrumbs and back/forward history, lock the toolbar to a non-wrapping single row, eliminate phantom root empty folders, replace broken search spinners, introduce a UI-first OpenAI-compatible model gateway, and reorganize Settings into a clean sidebar dashboard with no emojis. + +**Architecture:** +- **Backend:** Update `app/services/navigator.py` to sanitize protocol prefixes (`repo://`) and discard empty path segments when constructing repository trees. Create dedicated OpenAI-compatible gateway endpoints (`/admin/api/settings/ai-gateway`) storing URL, masked key, and model choices in SQLite `system_metadata` with one-time env seeding. +- **Frontend Navigator:** Re-architect `CodeNavigator.tsx` into a two-section view: collapsible left sidebar (~260px) switching between Files and Outline, and a primary Hero Code/Document Viewer (~75–80% width). Add a dedicated `NavigatorBreadcrumbs` component with Back/Forward history traversal. Lock `NavigatorToolbar` to a single 44px non-wrapping flex row. +- **Frontend Settings:** Redesign `Settings.tsx` into a two-pane layout with a vertical category sub-navigation sidebar (~220px) using Material/SVG icons (strictly zero emojis) and dedicated views for AI Gateway, Vector DB, Embedding Engine, Auto-Sync, Git Hosts, File Settings, and Appearance. +- **Frontend Catalog & Search:** Format Ingestion Catalog stat cards, filter pills, and tables with consistent SVG icons. Replace font-dependent spinners with standalone SVG `animate-spin` components. + +**Tech Stack:** +- Backend: Python 3.11, FastAPI, SQLite 3, Pydantic 2, Pytest +- Frontend: React 19, TypeScript 5, Vite, Vitest, Testing Library, Playwright E2E + +## Global Constraints +- Strictly NO emojis in navigation, buttons, titles, or settings; use clean SVG or FontAwesome/Material icons. +- Toolbar must remain a single row (`<= 48px` height) across desktop viewports (1280px, 1024px, 768px). +- Database is the sole source of truth for runtime AI Gateway settings; env vars serve only as initial seed if uninitialized. +- Selecting any file (code or document) must render its full content immediately without dead buttons or empty states. + +--- + +### Task 1: Backend Navigator Tree Protocol Sanitization (Fix Root Empty Folder) + +**Files:** +- Modify: `app/services/navigator.py:45-85` +- Test: `tests/backend/test_navigator_router.py` + +**Interfaces:** +- Consumes: SQLite `indexed_files` rows with filepath formats like `mcp-router-code://path/to/file.py` or `/path/to/file.py`. +- Produces: `get_navigator_tree(repo: str) -> Dict[str, Any]` where `tree` contains only valid top-level directories and files, with no node having `name == ""` or `path.endswith(":/")`. + +- [ ] **Step 1: Write failing test in `tests/backend/test_navigator_router.py`** + +```python +def test_navigator_tree_has_no_empty_folder_root(client, sample_repo_with_protocol): + """Verify get_navigator_tree strips URI schemes and never produces empty name root folders.""" + resp = client.get("/admin/api/navigator/tree?repo=mcp-router-code") + assert resp.status_code == 200 + data = resp.json() + assert "tree" in data + # Root level children must not contain empty string name or URI scheme artifacts + for node in data["tree"]: + assert node["name"] != "", f"Found node with empty name: {node}" + assert not node["name"].endswith(":"), f"Found raw scheme node: {node}" + if node.get("children"): + for child in node["children"]: + assert child["name"] != "", f"Found child with empty name: {child}" +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/backend/test_navigator_router.py::test_navigator_tree_has_no_empty_folder_root -v` +Expected: FAIL with assertion error (`node['name'] == ''` or scheme node). + +- [ ] **Step 3: Implement path sanitization in `app/services/navigator.py`** + +```python +def _sanitize_tree_path(filepath: str, repo: str) -> List[str]: + cleaned = filepath.strip() + # Strip protocol prefix e.g. repo:// or storage:// + if "://" in cleaned: + cleaned = cleaned.split("://", 1)[1] + elif cleaned.startswith(f"{repo}:"): + cleaned = cleaned[len(repo) + 1:] + cleaned = cleaned.lstrip("/") + return [part for part in cleaned.split("/") if part.strip()] +``` +Update tree construction in `get_navigator_tree` to use `_sanitize_tree_path`. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/backend/test_navigator_router.py -v` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add app/services/navigator.py tests/backend/test_navigator_router.py +git commit -m "fix(navigator): sanitize tree filepath protocols to eliminate empty root folder" +``` + +--- + +### Task 2: Backend OpenAI-Compatible Model Gateway & Settings Endpoints + +**Files:** +- Modify: `app/services/database/connection.py` +- Modify: `app/api/routers/settings.py` +- Create: `tests/backend/test_ai_gateway_settings.py` + +**Interfaces:** +- Produces: + - `GET /admin/api/settings/ai-gateway` -> `{"url": str, "has_api_key": bool, "masked_api_key": str, "chat_model": str, "vision_ocr_model": str, "embedding_model": str}` + - `POST /admin/api/settings/ai-gateway` -> updates SQLite `system_metadata` without clobbering existing API key if left empty. + +- [ ] **Step 1: Write failing test in `tests/backend/test_ai_gateway_settings.py`** + +```python +def test_ai_gateway_settings_lifecycle(client): + # Initial read + resp = client.get("/admin/api/settings/ai-gateway") + assert resp.status_code == 200 + data = resp.json() + assert "masked_api_key" in data + assert "url" in data + + # Update settings + payload = { + "url": "http://my-litellm:4000/v1", + "api_key": "sk-secret12345678", + "chat_model": "gpt-4o", + "vision_ocr_model": "gpt-4o-mini", + "embedding_model": "text-embedding-3-small" + } + update_resp = client.post("/admin/api/settings/ai-gateway", json=payload) + assert update_resp.status_code == 200 + updated = update_resp.json()["config"] + assert updated["url"] == "http://my-litellm:4000/v1" + assert updated["has_api_key"] is True + assert updated["masked_api_key"].startswith("sk-") + assert "12345678" not in updated["masked_api_key"] +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/backend/test_ai_gateway_settings.py -v` +Expected: FAIL with 404 Not Found for `/admin/api/settings/ai-gateway`. + +- [ ] **Step 3: Implement database metadata helpers & routes** + +In `app/services/database/connection.py`: +- Implement `get_ai_gateway_config()` and `set_ai_gateway_config(...)`. +- Include initial seed logic from `LITELLM_URL` and `LITELLM_API_KEY` only when DB metadata does not exist. + +In `app/api/routers/settings.py`: +- Add `GET /admin/api/settings/ai-gateway` and `POST /admin/api/settings/ai-gateway`. +- Ensure `POST /admin/api/settings/embedding` does not touch or clobber `openai_api_url` or `openai_api_key`. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/backend/test_ai_gateway_settings.py -v` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add app/services/database/connection.py app/api/routers/settings.py tests/backend/test_ai_gateway_settings.py +git commit -m "feat(settings): add dedicated openai-compatible ai gateway persistence and endpoints" +``` + +--- + +### Task 3: Search & Navigator SVG Loading Spinners (Fix Broken Spinners) + +**Files:** +- Modify: `frontend/src/components/navigator/NavigatorOmniSearch.tsx` +- Modify: `frontend/src/SearchInspector.tsx` +- Modify: `frontend/src/styles/navigator.css` +- Modify: `frontend/src/tests/NavigatorOmniSearch.test.tsx` +- Modify: `frontend/src/tests/SearchInspector.test.tsx` + +**Interfaces:** +- Produces: Resilient SVG spinner component with CSS `animate-spin` that renders reliably across browsers without relying on external webfonts. + +- [ ] **Step 1: Write failing test in `frontend/src/tests/NavigatorOmniSearch.test.tsx`** + +```tsx +it('renders standalone svg spinner when searching', async () => { + render(); + const input = screen.getByRole('textbox', { name: /omni-search/i }); + fireEvent.change(input, { target: { value: 'query' } }); + + const spinner = await screen.findByTestId('omni-search-spinner'); + expect(spinner).toBeInTheDocument(); + expect(spinner.tagName.toLowerCase()).toBe('svg'); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `npm --prefix frontend test -- NavigatorOmniSearch.test.tsx` +Expected: FAIL (no element with `data-testid="omni-search-spinner"` or tag is `div`). + +- [ ] **Step 3: Update `NavigatorOmniSearch.tsx`, `SearchInspector.tsx`, and CSS** + +Replace FontAwesome icon / div spinner with inline SVG with `data-testid="omni-search-spinner"` and `data-testid="search-inspector-spinner"`. +Add keyframes in `navigator.css` and `index.css`: +```css +@keyframes nav-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} +.nav-svg-spinner { + animation: nav-spin 0.8s linear infinite; +} +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `npm --prefix frontend test -- NavigatorOmniSearch.test.tsx SearchInspector.test.tsx` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add frontend/src/components/navigator/NavigatorOmniSearch.tsx frontend/src/SearchInspector.tsx frontend/src/styles/navigator.css frontend/src/tests/NavigatorOmniSearch.test.tsx frontend/src/tests/SearchInspector.test.tsx +git commit -m "fix(ui): replace font-dependent search spinners with robust svg animation" +``` + +--- + +### Task 4: Stable Single-Row Navigator Toolbar (Fix Height Resizing/Wrapping) + +**Files:** +- Modify: `frontend/src/components/navigator/NavigatorToolbar.tsx` +- Modify: `frontend/src/styles/navigator.css` +- Modify: `frontend/src/tests/NavigatorToolbar.test.tsx` + +**Interfaces:** +- Produces: Single-row toolbar (`height: 44px`, `flex-wrap: nowrap`) with compact repo selector, responsive omni-search, density selector, and refresh button. + +- [ ] **Step 1: Write failing test in `frontend/src/tests/NavigatorToolbar.test.tsx`** + +```tsx +it('renders as a single row toolbar container with nowrap styling class', () => { + render( + + ); + const toolbar = screen.getByTestId('navigator-toolbar'); + expect(toolbar).toHaveClass('nav-toolbar-single-row'); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `npm --prefix frontend test -- NavigatorToolbar.test.tsx` +Expected: FAIL with class missing. + +- [ ] **Step 3: Implement single-row styling and structure** + +In `NavigatorToolbar.tsx`: +- Add `nav-toolbar-single-row` class. +- Structure elements with flex-shrink constraints. +In `frontend/src/styles/navigator.css`: +- Lock `.nav-toolbar` to `height: 44px; flex-wrap: nowrap; align-items: center;`. +- Set center omni-search container to `max-width: 380px; flex: 1 1 auto;`. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `npm --prefix frontend test -- NavigatorToolbar.test.tsx` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add frontend/src/components/navigator/NavigatorToolbar.tsx frontend/src/styles/navigator.css frontend/src/tests/NavigatorToolbar.test.tsx +git commit -m "fix(navigator): lock toolbar to single non-wrapping row to prevent height jumping" +``` + +--- + +### Task 5: Code Navigator Layout: Code as Hero, Breadcrumbs, History, & Collapsible Sidebar + +**Files:** +- Create: `frontend/src/components/navigator/NavigatorBreadcrumbs.tsx` +- Modify: `frontend/src/CodeNavigator.tsx` +- Modify: `frontend/src/components/navigator/NavigatorOutline.tsx` +- Modify: `frontend/src/styles/navigator.css` +- Create: `frontend/src/tests/NavigatorBreadcrumbs.test.tsx` +- Modify: `frontend/src/tests/CodeNavigator.test.tsx` +- Modify: `frontend/src/tests/NavigatorOutline.test.tsx` + +**Interfaces:** +- `NavigatorBreadcrumbs`: `(props: { repo: string, path: string | null, symbol: string | null, onNavigatePath: (path: string) => void, canGoBack: boolean, canGoForward: boolean, onGoBack: () => void, onGoForward: () => void, onToggleSidebar: () => void, isSidebarOpen: boolean }) => JSX.Element` +- `CodeNavigator`: Renders left sidebar (Files/Outline tabs) and Hero Code Viewer (75–80%). Auto-renders Markdown for docs with zero dead buttons. + +- [ ] **Step 1: Write failing test in `frontend/src/tests/NavigatorBreadcrumbs.test.tsx`** + +```tsx +it('renders breadcrumb segments and fires callbacks on click', () => { + const onNavigatePath = vi.fn(); + render( + + ); + expect(screen.getByText('my-repo')).toBeInTheDocument(); + expect(screen.getByText('src')).toBeInTheDocument(); + expect(screen.getByText('components')).toBeInTheDocument(); + expect(screen.getByText('Button.tsx')).toBeInTheDocument(); + expect(screen.getByText('Button')).toBeInTheDocument(); + + fireEvent.click(screen.getByText('src')); + expect(onNavigatePath).toHaveBeenCalledWith('src'); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `npm --prefix frontend test -- NavigatorBreadcrumbs.test.tsx` +Expected: FAIL (component not found). + +- [ ] **Step 3: Implement `NavigatorBreadcrumbs.tsx` and refactor `CodeNavigator.tsx`** + +1. Build `NavigatorBreadcrumbs.tsx` with Material/SVG icons (strictly no emoji), Back/Forward history buttons, path tokens, and sidebar toggle. +2. In `CodeNavigator.tsx`: + - Implement navigation history stack (`historyPaths`, `historyIndex`, `pushHistory`). + - Implement left sidebar tab switching (`sidebarTab: 'files' | 'outline'`). + - When a file is clicked, immediately fetch and display full code/document content in the Hero Viewer. + - For Markdown/text files, outline generates section titles or document overview rather than empty dead-end card. +3. In `NavigatorOutline.tsx`: + - Remove dead "Read Full Document" button; replace with active document outline headings or immediate full viewer indicator. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `npm --prefix frontend test -- NavigatorBreadcrumbs.test.tsx NavigatorOutline.test.tsx CodeNavigator.test.tsx` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add frontend/src/components/navigator/NavigatorBreadcrumbs.tsx frontend/src/CodeNavigator.tsx frontend/src/components/navigator/NavigatorOutline.tsx frontend/src/styles/navigator.css frontend/src/tests/NavigatorBreadcrumbs.test.tsx frontend/src/tests/CodeNavigator.test.tsx frontend/src/tests/NavigatorOutline.test.tsx +git commit -m "feat(navigator): implement code-first hero layout with breadcrumbs and history navigation" +``` + +--- + +### Task 6: Redesigned Settings Dashboard: Category Left Sidebar & UI-First AI Gateway + +**Files:** +- Create: `frontend/src/components/settings/AIGatewaySettings.tsx` +- Modify: `frontend/src/Settings.tsx` +- Modify: `frontend/src/styles/settings.css` +- Create: `frontend/src/tests/AIGatewaySettings.test.tsx` +- Modify: `frontend/src/tests/Settings.test.tsx` + +**Interfaces:** +- `AIGatewaySettings`: Pure UI management of OpenAI-compatible endpoints (`http://litellm:4000/v1`), masked API key with Change Key workflow, Test Connection & Discover Models button, and model selection. +- `Settings.tsx`: Sub-navigation sidebar with 7 categories (AI Gateway, Vector DB, Embedding Engine, Auto-Sync, Git Hosts, File Settings, Appearance) using clean Material/SVG icons (no emojis). + +- [ ] **Step 1: Write failing test in `frontend/src/tests/AIGatewaySettings.test.tsx`** + +```tsx +it('renders masked api key and exposes change key input when clicked', async () => { + render( + + ); + expect(screen.getByText(/sk-••••••••••••3a9f/i)).toBeInTheDocument(); + const changeBtn = screen.getByRole('button', { name: /change key/i }); + fireEvent.click(changeBtn); + expect(screen.getByPlaceholderText(/enter new api key/i)).toBeInTheDocument(); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `npm --prefix frontend test -- AIGatewaySettings.test.tsx` +Expected: FAIL (component not found). + +- [ ] **Step 3: Implement `AIGatewaySettings.tsx` and refactor `Settings.tsx`** + +1. Create `AIGatewaySettings.tsx`: + - Connection status badge with latency in ms. + - Endpoint URL input. + - Masked key display + "Change Key" & "Clear Key" actions. + - "Test Connection & Discover Models" button triggering live `/admin/api/models/discover`. + - Dropdown selectors for Chat, Vision, and Embedding models populated from discovered models + manual input toggle. +2. Refactor `Settings.tsx`: + - Add left sidebar with category buttons: + - `ai-gateway` (AI & Model Gateway) + - `vector-store` (Vector Database) + - `embedding` (Embedding Engine) + - `auto-sync` (Auto-Sync & Webhooks) + - `git-hosts` (Git & Host Credentials) + - `files` (File Processing & Summaries) + - `appearance` (Appearance & Theme) + - Render only active section card on the right. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `npm --prefix frontend test -- AIGatewaySettings.test.tsx Settings.test.tsx` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add frontend/src/components/settings/AIGatewaySettings.tsx frontend/src/Settings.tsx frontend/src/styles/settings.css frontend/src/tests/AIGatewaySettings.test.tsx frontend/src/tests/Settings.test.tsx +git commit -m "feat(settings): redesign settings dashboard with category sidebar and ui-first ai gateway" +``` + +--- + +### Task 7: Ingestion Catalog UI Formatting Fixes + +**Files:** +- Modify: `frontend/src/IngestionCatalogViewer.tsx` +- Modify: `frontend/src/styles/components.css` +- Modify: `frontend/src/tests/IngestionCatalogViewer.test.tsx` + +**Interfaces:** +- Produces: Polished 4-card statistics grid with aligned SVG icons, consistent 36px filter bar, and clean monospace table chips without text clipping. + +- [ ] **Step 1: Write failing test in `frontend/src/tests/IngestionCatalogViewer.test.tsx`** + +```tsx +it('renders 4 distinct aligned stat cards with formatted badges and no emojis', async () => { + render(); + const cards = screen.getAllByTestId('catalog-stat-card'); + expect(cards).toHaveLength(4); + for (const card of cards) { + expect(card.textContent).not.toMatch(/[\u{1F300}-\u{1F9FF}]/u); // zero emojis + } +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `npm --prefix frontend test -- IngestionCatalogViewer.test.tsx` +Expected: FAIL (no `data-testid="catalog-stat-card"`). + +- [ ] **Step 3: Update `IngestionCatalogViewer.tsx` markup and CSS** + +- Format the stat cards with `data-testid="catalog-stat-card"`. +- Clean up filter pill spacing and unify input heights to 36px. +- Use clean SVGs / Material icons. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `npm --prefix frontend test -- IngestionCatalogViewer.test.tsx` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add frontend/src/IngestionCatalogViewer.tsx frontend/src/styles/components.css frontend/src/tests/IngestionCatalogViewer.test.tsx +git commit -m "fix(catalog): polish ingestion catalog stat cards, filters, and table formatting" +``` + +--- + +### Task 8: End-to-End Verification & Playwright Layout Inspection + +**Files:** +- Modify: `frontend/e2e/navigator.spec.ts` +- Modify: `frontend/e2e/layout-inspector.spec.ts` +- Modify: `scripts/generate_requirements.py` + +**Interfaces:** +- Produces: Passing end-to-end tests validating: + - Document viewing on `.md` file click (zero dead buttons). + - Single-row toolbar height stability (`<= 48px`) across 1280px, 1024px, 768px. + - Settings category sidebar switching. + +- [ ] **Step 1: Write E2E assertions for document reading and single-row toolbar** + +In `frontend/e2e/navigator.spec.ts`: +- Assert clicking `README.md` immediately renders markdown text in the Hero Viewer. +- Assert toolbar bounding box height is `<= 48px`. + +- [ ] **Step 2: Run Playwright tests and layout inspection** + +Run: `npm --prefix frontend run test:e2e` +Run: `npm --prefix frontend run inspect:layout` +Expected: All E2E and layout tests PASS. + +- [ ] **Step 3: Sync requirements and verify full test baseline** + +Run: `python3 scripts/generate_requirements.py` +Run: `pytest -v` +Run: `npm --prefix frontend test` +Expected: 100% PASS across all suites. + +- [ ] **Step 4: Commit** + +```bash +git add frontend/e2e/navigator.spec.ts frontend/e2e/layout-inspector.spec.ts REQUIREMENTS.md docs/TEST_COVERAGE.md +git commit -m "test(e2e): verify code-first hero layout, toolbar height stability, and requirements sync" +``` diff --git a/docs/superpowers/specs/2026-09-29-code-navigator-and-settings-overhaul-design.md b/docs/superpowers/specs/2026-09-29-code-navigator-and-settings-overhaul-design.md new file mode 100644 index 0000000..e8a2696 --- /dev/null +++ b/docs/superpowers/specs/2026-09-29-code-navigator-and-settings-overhaul-design.md @@ -0,0 +1,198 @@ +# Design Specification: Code Navigator Hero Layout, Stable Toolbar, OpenAI-Compatible Model Gateway, and Settings Dashboard Overhaul + +- **Date**: 2026-09-29 +- **Status**: Approved for Planning +- **Author**: Antigravity Assistant & Engineering Team +- **Target Release**: ContextCortex Next Local Release + +--- + +## 1. Executive Summary & Goals + +This specification resolves critical usability, architectural, and layout issues identified in ContextCortex: +1. **Code-First Navigator**: Refactor the Code Navigator into an IDE/GitHub-style experience where the full code/document viewer is the primary hero (~75–80% width) with clear breadcrumbs, back/forward history, and a unified collapsible sidebar. +2. **Stable Single-Row Toolbar**: Condense the Navigator toolbar into a single, non-wrapping row (44px height) to eliminate viewport resizing and vertical jitter. +3. **Repository Tree Root Hygiene**: Eliminate phantom empty folders created by double-slash URI tokens (`repo://...`). +4. **Reliable Document & Code Viewing**: Ensure clicking any document or source file immediately displays its full content without dead buttons or empty states. +5. **UI-First OpenAI-Compatible Model Gateway**: Decouple LLM gateway configuration from local FastEmbed embeddings. Store API URL, API key, and model choices directly in SQLite (`system_metadata`) with secure masked credentials (`sk-••••••••`), live connection testing, and dynamic model discovery. +6. **Settings Page Redesign**: Replace the long vertical page with a categorized left-sidebar dashboard with clean Material/SVG icons (strictly no emojis). +7. **Ingestion Catalog Formatting & Search Spinners**: Standardize catalog stat cards, pill filters, and tables, and replace font-dependent search spinners with robust SVG animations. +8. **End-to-End Quality Gates**: Back all changes with rigorous, non-mock-theater unit, component, and Playwright layout/behavioral tests. + +--- + +## 2. Architecture & Detailed Component Design + +### 2.1 Code Navigator: Code as Hero & Navigation History + +#### Component Structure: +* [`frontend/src/CodeNavigator.tsx`](file:///containers/dev/contexthub/frontend/src/CodeNavigator.tsx): + * **Layout**: + * **Top**: Compact `NavigatorToolbar` (Repo selector, stats, Omni-Search, density, refresh). + * **Sub-Header / Breadcrumb Bar**: + * Back / Forward navigation buttons (`<`, `>`) tied to a navigation stack (`history: string[]`, `historyIndex: number`). + * Breadcrumb trail: `[Repo]` → `[Folder]` → `[File.ext]` → `[Symbol/Route (if selected)]`. Each segment is clickable to navigate upward. + * Quick actions: Copy Permalink, Raw view toggle, Intelligence drawer toggle button. + * **Main Workspace**: Two-section flex layout: + * **Left Sidebar (~260px, collapsible via toggle button or `Ctrl+B`)**: + * Header tab pills: **Files** (File Tree) vs. **Outline** (AST Symbols & Routes). + * For document files (`.md`, `.txt`, `.json`), Outline displays Markdown headers/sections rather than an empty dead-end card. + * **Center Hero Viewport (Full remaining width, ~75–80%)**: + * Renders [`NavigatorCodeViewer`](file:///containers/dev/contexthub/frontend/src/components/navigator/NavigatorCodeViewer.tsx) or [`NavigatorDocReader`](file:///containers/dev/contexthub/frontend/src/components/navigator/NavigatorDocReader.tsx) immediately upon file selection. + * Highlights symbol start/end lines smoothly and automatically scrolls into view. + * **Collapsible Sliding Drawer / Side-Dock (Callers, Callees & Impact)**: + * Slides in from the right or docks at the bottom when requested. + * Renders caller/callee chips and API route cards without intruding on code reading. + +#### Tree Protocol Sanitization: +* [`app/services/navigator.py`](file:///containers/dev/contexthub/app/services/navigator.py): + * `_clean_path(path: str)` strips any leading protocol like `repo://` or `storage://` and removes empty split segments. + * Ensures `parts = [p for p in raw_path.split("/") if p.strip()]`. + * The root tree node contains only valid top-level directories and files; no node with `name: ""` is ever generated. + +--- + +### 2.2 Navigator Toolbar Condensation + +* **CSS / Layout (`frontend/src/styles/navigator.css`)**: + * `.nav-toolbar` updated with `flex-wrap: nowrap`, `height: 44px`, `overflow-x: auto`, `overflow-y: hidden`. + * Left section: Compact repo select dropdown + minimal stats chips (`12 files`, `48 symbols`). + * Center section: `NavigatorOmniSearch` with fixed responsive width (`min-width: 220px; max-width: 380px;`). + * Right section: Segmented density toggle (Compact / Balanced / Spacious) and SVG refresh button. + * Responsive breakpoint: At `<768px`, toolbar stays single-line with horizontally scrollable or iconified density buttons. + +--- + +### 2.3 Search Loading Spinner Fix + +* Replace FontAwesome `fa-spinner fa-spin` in [`SearchInspector.tsx`](file:///containers/dev/contexthub/frontend/src/SearchInspector.tsx) and [`NavigatorOmniSearch.tsx`](file:///containers/dev/contexthub/frontend/src/components/navigator/NavigatorOmniSearch.tsx) with a standalone inline SVG: + ```tsx + + + + + ``` +* Standardize CSS animation `nav-spin` with `1s linear infinite` rotation. + +--- + +### 2.4 UI-First OpenAI-Compatible Model Gateway & Settings Redesign + +#### Database & Backend Persistence: +* Database table `system_metadata` in SQLite stores: + * `openai_api_url`: string (e.g. `http://litellm:4000/v1`) + * `openai_api_key`: string (encrypted or raw masked token) + * `openai_chat_model`: string (e.g. `gemini-2.5-flash`) + * `openai_vision_model`: string (e.g. `gemini-2.5-flash`) + * `openai_embedding_model`: string (e.g. `text-embedding-3-small`) +* **Environment Fallback Seeding**: + * On initial boot, if `openai_api_url` or `openai_api_key` are not set in the database, seed them once from `LITELLM_URL` and `LITELLM_API_KEY`. + * After initial seeding, the database is the **exclusive source of truth**. + * Endpoint `GET /admin/api/settings/ai-gateway` returns: + * `url`: configured URL + * `has_api_key`: boolean + * `masked_api_key`: e.g. `sk-••••••••••••3a9f` or `••••••••` + * `chat_model`, `vision_ocr_model`, `embedding_model` + * Endpoint `POST /admin/api/settings/ai-gateway`: + * Accepts `url`, `api_key` (optional, only updated if provided), `chat_model`, `vision_ocr_model`, `embedding_model`. + * Clears API key if explicitly passed as empty string. + * Decouple from `/admin/api/settings/embedding` so saving embedding limits (threads, batch size) never wipes or modifies the AI Gateway configuration. + +#### Settings Dashboard Layout: +* [`frontend/src/Settings.tsx`](file:///containers/dev/contexthub/frontend/src/Settings.tsx) redesigned into a two-column responsive layout: + * **Left Sidebar (220px)** with clean SVG / Material design icons (strictly no emojis): + 1. **AI & Model Gateway** (`fa-solid fa-robot` or SVG chip) + 2. **Vector Database** (`fa-solid fa-database`) + 3. **Embedding Engine** (`fa-solid fa-microchip`) + 4. **Auto-Sync & Webhooks** (`fa-solid fa-arrows-rotate`) + 5. **Git & Host Credentials** (`fa-solid fa-key`) + 6. **File Processing & Summaries** (`fa-solid fa-file-lines`) + 7. **Appearance & Theme** (`fa-solid fa-palette`) + * **Right Panel**: Shows only the currently active category card. + * Mobile view: Collapses into an accessible top dropdown or scrollable sub-tabs. + +#### Model Discovery & Connection Testing: +* "Test Connection & Discover Models" button pings `/models`. +* Displays real status: Latency in ms, total model count, and populates model dropdowns for Chat, Vision OCR, and Embedding models, with a custom input fallback. + +--- + +### 2.5 Ingestion Catalog UI Formatting Fixes + +* [`frontend/src/IngestionCatalogViewer.tsx`](file:///containers/dev/contexthub/frontend/src/IngestionCatalogViewer.tsx): + * **Stat Counters**: Clean CSS grid with 4 equal cards. Distinct SVG icons, bold numbers, and subtitle labels without clipping. + * **Filter Bar**: Consistent 36px inputs, clean pill buttons with active states, and aligned action buttons. + * **Data Tables**: Explicit column widths, monospace font for branches and commits (`git` provider badges, `synced` status badges), responsive horizontal scrolling. + +--- + +## 3. Data Flow & State Management + +```mermaid +flowchart TD + subgraph UI["Frontend UI (React 19)"] + Nav["Code Navigator"] + Breadcrumb["Breadcrumbs Bar & History Stack"] + Tree["Left Sidebar (Files & Symbols)"] + Viewer["Hero Code / Doc Viewer (75-80%)"] + Drawer["Sliding Intelligence Drawer"] + SettingsUI["Categorized Settings Sidebar"] + GatewayUI["AI & Model Gateway (OpenAI Compatible)"] + end + + subgraph Backend["FastAPI Backend"] + NavRouter["/admin/api/navigator/*"] + FilesRouter["/admin/api/files/read"] + AIGatewayRouter["/admin/api/settings/ai-gateway"] + DiscoveryRouter["/admin/api/models/discover"] + end + + subgraph Storage["Persistence & External Services"] + SQLite[("SQLite system_metadata & index_cache.db")] + OpenAIGw["OpenAI-Compatible Gateway\n(LiteLLM / Ollama / vLLM / OpenAI)"] + Disk["Filesystem Workspace Repos"] + end + + Nav --> Breadcrumb + Nav --> Tree + Nav --> Viewer + Nav --> Drawer + Tree -- "Select File/Symbol" --> NavRouter + Viewer -- "Read File Content" --> FilesRouter + FilesRouter --> Disk + SettingsUI --> GatewayUI + GatewayUI -- "Save Settings" --> AIGatewayRouter + AIGatewayRouter --> SQLite + GatewayUI -- "Discover Models" --> DiscoveryRouter + DiscoveryRouter --> OpenAIGw +``` + +--- + +## 4. Verification & Testing Strategy + +### 4.1 Backend Pytest +* `tests/backend/test_navigator_router.py`: + * Verify `get_navigator_tree` strips URI schemes and contains no directory node with `name: ""`. +* `tests/backend/test_ai_gateway_settings.py`: + * Verify `GET /admin/api/settings/ai-gateway` masks the API key (`sk-••••••••`). + * Verify `POST /admin/api/settings/ai-gateway` updates URL and models without requiring key re-entry. + * Verify initial seeding from environment variables only occurs if database rows are absent. + * Verify saving embedding settings never clobbers AI Gateway settings. + +### 4.2 Frontend Vitest Tests +* `NavigatorCodeViewer.test.tsx` & `CodeNavigator.test.tsx`: + * Verify selecting a file renders code/doc content immediately in the hero viewer. + * Verify breadcrumbs render clickable segments and update on navigation. + * Verify Back and Forward history buttons navigate through previously visited files. + * Verify left sidebar collapses and expands cleanly. +* `Settings.test.tsx` & `AIGatewaySettings.test.tsx`: + * Verify category sidebar switches between settings views. + * Verify masked API key display and "Change Key" input revelation. + * Verify Model Discovery populates Chat, Vision, and Embedding select menus. +* `SearchInspector.test.tsx` & `NavigatorOmniSearch.test.tsx`: + * Verify SVG spinner renders during loading states. + +### 4.3 Playwright E2E & Layout Tests +* Layout Inspector test checking toolbar height stability (`expect(toolbarHeight).toBeLessThanOrEqual(48)`) across 1280px, 1024px, and 768px viewports. +* Document Reader test: Click `README.md` → verify markdown rendered immediately in the hero viewer with zero dead buttons. diff --git a/docs/superpowers/specs/2026-09-30-subbar-navigation-design.md b/docs/superpowers/specs/2026-09-30-subbar-navigation-design.md new file mode 100644 index 0000000..4caff41 --- /dev/null +++ b/docs/superpowers/specs/2026-09-30-subbar-navigation-design.md @@ -0,0 +1,60 @@ +# Sub-Navigation Bar & UI Workflow Redesign Spec + +**Date:** 2026-09-30 +**Status:** Approved +**Topic:** Separate Top Header and Dedicated Sub-Navigation Bar with Rapid Vite Dev Workflow + +--- + +## 1. Problem Statement & Motivation +In an earlier iteration, all 9 navigation tabs, the brand logo, and 3 status badges were compressed into a single 48px header row. This forced navigation tabs down to `0.73rem` (~11px) with abbreviated labels (e.g. "Repos", "Paths", "Logs"), making the interface hard to read and visually cramped. Additionally, iterating via Docker image builds was unnecessarily slow for pure UI development. + +## 2. Goals & Constraints +- **Readability**: Restore standard, comfortable tab typography (`0.875rem` / 14px) and full descriptive labels. +- **Clean Structure**: Separate the top brand/system header from the application tab navigation. +- **Fast Development Loop**: Use the local Vite dev server (`http://localhost:5173`) proxying to the running backend on port 8021 for rapid, instant UI iteration. +- **Responsiveness**: Maintain smooth horizontal scrolling on tablet/narrow viewports and a collapsible mobile drawer for phones without layout overflow violations. + +--- + +## 3. Component & Layout Architecture + +### A. Top Header Bar (`.dashboard-header`) +- **Brand & Logo (Left)**: Icon, ContextCortex title, and version tag (`v2.16.0`). +- **System Status (Right)**: + - Engine State (`Idle` / `Syncing...`) + - Vector Backend (`Qdrant (Remote) Healthy`) + - Collection Name (`notes_rag`) + - Full labels restored for maximum legibility. +- **Mobile Menu Toggle**: Visible only on mobile/narrow screens to toggle the navigation drawer. + +### B. Dedicated Sub-Navigation Bar (`.dashboard-nav`) +- **Placement**: Directly below the main header in its own card/bar container. +- **Tabs**: + 1. `Overview` (`fa-chart-pie`) + 2. `Navigator` (`fa-code-fork`) + 3. `Git Repositories` (`fa-github`) + 4. `Local Paths` (`fa-folder-tree`) + 5. `Local Storage` (`fa-hard-drive`) + 6. `Ingestion Catalog` (`fa-book-bookmark`) + 7. `Search & Inspector` (`fa-magnifying-glass`) + 8. `Settings` (`fa-gear`) + 9. `Diagnostics & Logs` (`fa-terminal`) +- **Sizing & Styling**: + - `font-size: 0.875rem; font-weight: 500;` + - `padding: 8px 14px; min-height: 38px; gap: 8px;` + - Fully removed `.tab-short` label truncation hacks. + - Active tab highlighted with accent background and border. + - `overflow-x: auto; scrollbar-width: none;` for smooth touch/wheel panning on mid-sized viewports. + +--- + +## 4. Development & Verification Strategy +1. **Live UI Iteration**: Start Vite dev server in the background: + ```bash + npm --prefix frontend run dev -- --host 0.0.0.0 --port 5173 + ``` +2. **Automated Verification**: + - Vitest unit tests: `npm --prefix frontend test -- --run` + - Playwright E2E & Layout Inspector: `npm --prefix frontend run inspect:layout` & `npx playwright test` + - Production bundle build: `npm --prefix frontend run build` diff --git a/frontend/dist/assets/index-B8UpBGpc.js b/frontend/dist/assets/index-B8UpBGpc.js deleted file mode 100644 index 49b4a57..0000000 --- a/frontend/dist/assets/index-B8UpBGpc.js +++ /dev/null @@ -1,15 +0,0 @@ -var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,o)=>(o=n==null?{}:e(i(n)),s(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function D(e,t){return E(e.type,t,e.props)}function O(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function k(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var ee=/\/+/g;function A(e,t){return typeof e==`object`&&e&&e.key!=null?k(``+e.key):t.toString(36)}function te(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function ne(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,ne(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+A(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(ee,`$&/`)+`/`),ne(o,r,i,``,function(e){return e})):o!=null&&(O(o)&&(o=D(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(ee,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=l()})),d=o((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m){if(n(c)!==null)m=!0,S||(S=!0,O());else{var t=n(l);t!==null&&A(x,t.startTime-e)}}}var S=!1,C=-1,w=5,T=-1;function E(){return g?!0:!(e.unstable_now()-Tt&&E());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&A(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}}}finally{i?O():S=!1}}}var O;if(typeof y==`function`)O=function(){y(D)};else if(typeof MessageChannel<`u`){var k=new MessageChannel,ee=k.port2;k.port1.onmessage=D,O=function(){ee.postMessage(null)}}else O=function(){_(D,0)};function A(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,A(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,O()))),r},e.unstable_shouldYield=E,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),f=o(((e,t)=>{t.exports=d()})),p=o((e=>{var t=u();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=p()})),h=o((e=>{var t=f(),n=u(),r=m();function i(e){var t=`https://react.dev/errors/`+e;if(1ae||(e.current=ie[ae],ie[ae]=null,ae--)}function L(e,t){ae++,ie[ae]=e.current,e.current=t}var oe=F(null),se=F(null),ce=F(null),le=F(null);function ue(e,t){switch(L(ce,t),L(se,e),L(oe,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}I(oe),L(oe,e)}function de(){I(oe),I(se),I(ce)}function fe(e){e.memoizedState!==null&&L(le,e);var t=oe.current,n=Hd(t,e.type);t!==n&&(L(se,e),L(oe,n))}function pe(e){se.current===e&&(I(oe),I(se)),le.current===e&&(I(le),Qf._currentValue=P)}var me,he;function ge(e){if(me===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);me=t&&t[1]||``,he=-1)`:-1i||c[r]!==l[i]){var u=` -`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{_e=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?ge(n):``}function ye(e,t){switch(e.tag){case 26:case 27:case 5:return ge(e.type);case 16:return ge(`Lazy`);case 13:return e.child!==t&&t!==null?ge(`Suspense Fallback`):ge(`Suspense`);case 19:return ge(`SuspenseList`);case 0:case 15:return ve(e.type,!1);case 11:return ve(e.type.render,!1);case 1:return ve(e.type,!0);case 31:return ge(`Activity`);default:return``}}function be(e){try{var t=``,n=null;do t+=ye(e,n),n=e,e=e.return;while(e);return t}catch(e){return` -Error generating stack: `+e.message+` -`+e.stack}}var xe=Object.prototype.hasOwnProperty,Se=t.unstable_scheduleCallback,Ce=t.unstable_cancelCallback,we=t.unstable_shouldYield,Te=t.unstable_requestPaint,Ee=t.unstable_now,De=t.unstable_getCurrentPriorityLevel,Oe=t.unstable_ImmediatePriority,ke=t.unstable_UserBlockingPriority,Ae=t.unstable_NormalPriority,je=t.unstable_LowPriority,Me=t.unstable_IdlePriority,Ne=t.log,Pe=t.unstable_setDisableYieldValue,Fe=null,Ie=null;function Le(e){if(typeof Ne==`function`&&Pe(e),Ie&&typeof Ie.setStrictMode==`function`)try{Ie.setStrictMode(Fe,e)}catch{}}var Re=Math.clz32?Math.clz32:Ve,ze=Math.log,Be=Math.LN2;function Ve(e){return e>>>=0,e===0?32:31-(ze(e)/Be|0)|0}var He=256,Ue=262144,We=4194304;function Ge(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Ke(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Ge(n))):i=Ge(o):i=Ge(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Ge(n))):i=Ge(o)):i=Ge(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function qe(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Je(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Ye(){var e=We;return We<<=1,!(We&62914560)&&(We=4194304),e}function Xe(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function Ze(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Qe(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),un=!1;if(ln)try{var dn={};Object.defineProperty(dn,"passive",{get:function(){un=!0}}),window.addEventListener(`test`,dn,dn),window.removeEventListener(`test`,dn,dn)}catch{un=!1}var fn=null,pn=null,mn=null;function hn(){if(mn)return mn;var e,t=pn,n=t.length,r,i=`value`in fn?fn.value:fn.textContent,a=i.length;for(e=0;e=qn),Xn=` `,Zn=!1;function Qn(e,t){switch(e){case`keyup`:return Gn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function $n(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var er=!1;function tr(e,t){switch(e){case`compositionend`:return $n(t);case`keypress`:return t.which===32?(Zn=!0,Xn):null;case`textInput`:return e=t.data,e===Xn&&Zn?null:e;default:return null}}function nr(e,t){if(er)return e===`compositionend`||!Kn&&Qn(e,t)?(e=hn(),mn=pn=fn=null,er=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=wr(n)}}function Er(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Er(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Dr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=It(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=It(e.document)}return t}function Or(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var kr=ln&&`documentMode`in document&&11>=document.documentMode,Ar=null,jr=null,Mr=null,Nr=!1;function Pr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Nr||Ar==null||Ar!==It(r)||(r=Ar,`selectionStart`in r&&Or(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Mr&&Cr(Mr,r)||(Mr=r,r=Ed(jr,`onSelect`),0>=o,i-=o,Ei=1<<32-Re(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),B&&Oi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),B&&Oi(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return B&&Oi(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),B&&Oi(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===O&&Da(l)===r.type){n(e,r.sibling),c=a(r,o.props),Pa(c,o),c.return=e,e=c;break a}n(e,r);break}t(e,r),r=r.sibling}o.type===y?(c=pi(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=fi(o.type,o.key,o.props,null,e.mode,c),Pa(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l){if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}n(e,r);break}t(e,r),r=r.sibling}c=gi(o,e.mode,c),c.return=e,e=c}return s(e);case O:return o=Da(o),b(e,r,o,c)}if(j(o))return h(e,r,o,c);if(te(o)){if(l=te(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Na(o),c);if(o.$$typeof===C)return b(e,r,ta(e,o),c);Fa(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=mi(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Ma=0;var i=b(e,t,n,r);return ja=null,i}catch(t){if(t===xa||t===Ca)throw t;var a=ci(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var La=Ia(!0),Ra=Ia(!1),za=!1;function Ba(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Va(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Ha(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ua(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,K&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=ai(e),ii(e,null,n),t}return ti(e,r,t,n),ai(e)}function Wa(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,et(e,n)}}function Ga(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ka=!1;function qa(){if(Ka){var e=fa;if(e!==null)throw e}}function Ja(e,t,n,r){Ka=!1;var i=e.updateQueue;za=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(Y&f)===f:(r&f)===f){f!==0&&f===da&&(Ka=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(m=g.payload,typeof m==`function`){d=m.call(_,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,f=typeof m==`function`?m.call(_,d,f):m,f==null)break a;d=h({},d,f);break a;case 2:za=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Gl|=o,e.lanes=o,e.memoizedState=d}}function Ya(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function Xa(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=M.T,s={};M.T=s,Ps(e,!1,t,n);try{var c=i(),l=M.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Ns(e,t,ha(c,r),pu(e)):Ns(e,t,r,pu(e))}catch(n){Ns(e,t,{then:function(){},status:`rejected`,reason:n},pu())}finally{N.p=a,o!==null&&s.types!==null&&(o.types=s.types),M.T=o}}function Cs(){}function ws(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Ts(e).queue;Ss(e,a,t,P,n===null?Cs:function(){return Es(e),n(r)})}function Ts(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:P,baseState:P,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Fo,lastRenderedState:P},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Fo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Es(e){var t=Ts(e);t.next===null&&(t=e.alternate.memoizedState),Ns(e,t.next.queue,{},pu())}function Ds(){return ea(Qf)}function Os(){return Ao().memoizedState}function ks(){return Ao().memoizedState}function As(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=pu();e=Ha(n);var r=Ua(t,e,n);r!==null&&(hu(r,t,n),Wa(r,t,n)),t={cache:sa()},e.payload=t;return}t=t.return}}function js(e,t,n){var r=pu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Fs(e)?Is(t,n):(n=ni(e,t,n,r),n!==null&&(hu(n,e,r),Ls(n,t,r)))}function Ms(e,t,n){Ns(e,t,n,pu())}function Ns(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Fs(e))Is(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Sr(s,o))return ti(e,t,i,0),q===null&&ei(),!1}catch{}if(n=ni(e,t,i,r),n!==null)return hu(n,e,r),Ls(n,t,r),!0}return!1}function Ps(e,t,n,r){if(r={lane:2,revertLane:dd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Fs(e)){if(t)throw Error(i(479))}else t=ni(e,n,r,2),t!==null&&hu(t,e,2)}function Fs(e){var t=e.alternate;return e===V||t!==null&&t===V}function Is(e,t){ho=mo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Ls(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,et(e,n)}}var Rs={readContext:ea,use:No,useCallback:U,useContext:U,useEffect:U,useImperativeHandle:U,useLayoutEffect:U,useInsertionEffect:U,useMemo:U,useReducer:U,useRef:U,useState:U,useDebugValue:U,useDeferredValue:U,useTransition:U,useSyncExternalStore:U,useId:U,useHostTransitionStatus:U,useFormState:U,useActionState:U,useOptimistic:U,useMemoCache:U,useCacheRefresh:U};Rs.useEffectEvent=U;var zs={readContext:ea,use:No,useCallback:function(e,t){return ko().memoizedState=[e,t===void 0?null:t],e},useContext:ea,useEffect:ls,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),ss(4194308,4,hs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ss(4194308,4,e,t)},useInsertionEffect:function(e,t){ss(4,2,e,t)},useMemo:function(e,t){var n=ko();t=t===void 0?null:t;var r=e();if(go){Le(!0);try{e()}finally{Le(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=ko();if(n!==void 0){var i=n(t);if(go){Le(!0);try{n(t)}finally{Le(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=js.bind(null,V,e),[r.memoizedState,e]},useRef:function(e){var t=ko();return e={current:e},t.memoizedState=e},useState:function(e){e=Go(e);var t=e.queue,n=Ms.bind(null,V,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:_s,useDeferredValue:function(e,t){return bs(ko(),e,t)},useTransition:function(){var e=Go(!1);return e=Ss.bind(null,V,e.queue,!0,!1),ko().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=V,a=ko();if(B){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),q===null)throw Error(i(349));Y&127||Bo(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,ls(Ho.bind(null,r,o,e),[e]),r.flags|=2048,as(9,{destroy:void 0},Vo.bind(null,r,o,n,t),null),n},useId:function(){var e=ko(),t=q.identifierPrefix;if(B){var n=Di,r=Ei;n=(r&~(1<<32-Re(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=_o++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[ot]=t,o[st]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Pd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Nc(t)}}return W(t),Pc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Nc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=ce.current,Bi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Ni,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[ot]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Md(e.nodeValue,n)),e||Li(t,!0)}else e=Bd(e).createTextNode(r),e[ot]=t,t.stateNode=e}return W(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Bi(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[ot]=t}else Vi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;W(t),e=!1}else n=Hi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(co(t),t):(co(t),null);if(t.flags&128)throw Error(i(558))}return W(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Bi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[ot]=t}else Vi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;W(t),a=!1}else a=Hi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(co(t),t):(co(t),null)}return co(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Ic(t,t.updateQueue),W(t),null);case 4:return de(),e===null&&Sd(t.stateNode.containerInfo),W(t),null;case 10:return Ji(t.type),W(t),null;case 19:if(I(lo),r=t.memoizedState,r===null)return W(t),null;if(a=!!(t.flags&128),o=r.rendering,o===null){if(a)Lc(r,!1);else{if(Wl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=uo(e),o!==null){for(t.flags|=128,Lc(r,!1),e=o.updateQueue,t.updateQueue=e,Ic(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)di(n,e),n=n.sibling;return L(lo,lo.current&1|2),B&&Oi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Ee()>tu&&(t.flags|=128,a=!0,Lc(r,!1),t.lanes=4194304)}}else{if(!a){if(e=uo(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Ic(t,e),Lc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!B)return W(t),null}else 2*Ee()-r.renderingStartTime>tu&&n!==536870912&&(t.flags|=128,a=!0,Lc(r,!1),t.lanes=4194304)}r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(W(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Ee(),e.sibling=null,n=lo.current,L(lo,a?n&1|2:n&1),B&&Oi(t,r.treeForkCount),e);case 22:case 23:return co(t),to(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(W(t),t.subtreeFlags&6&&(t.flags|=8192)):W(t),n=t.updateQueue,n!==null&&Ic(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&I(_a),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Ji(oa),W(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function zc(e,t){switch(ji(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Ji(oa),de(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return pe(t),null;case 31:if(t.memoizedState!==null){if(co(t),t.alternate===null)throw Error(i(340));Vi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(co(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Vi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return I(lo),null;case 4:return de(),null;case 10:return Ji(t.type),null;case 22:case 23:return co(t),to(),e!==null&&I(_a),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Ji(oa),null;case 25:return null;default:return null}}function Bc(e,t){switch(ji(t),t.tag){case 3:Ji(oa),de();break;case 26:case 27:case 5:pe(t);break;case 4:de();break;case 31:t.memoizedState!==null&&co(t);break;case 13:co(t);break;case 19:I(lo);break;case 10:Ji(t.type);break;case 22:case 23:co(t),to(),e!==null&&I(_a);break;case 24:Ji(oa)}}function Vc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Z(t,t.return,e)}}function Hc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Z(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Z(t,t.return,e)}}function Uc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Xa(t,n)}catch(t){Z(e,e.return,t)}}}function Wc(e,t,n){n.props=Ks(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Z(e,t,n)}}function Gc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Z(e,t,n)}}function Kc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null){if(typeof r==`function`)try{r()}catch(n){Z(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Z(e,t,n)}else n.current=null}}function qc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Z(e,e.return,t)}}function Jc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[st]=t}catch(t){Z(e,e.return,t)}}function Yc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function Xc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Yc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Zc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=$t));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Zc(e,t,n),e=e.sibling;e!==null;)Zc(e,t,n),e=e.sibling}function Qc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(Qc(e,t,n),e=e.sibling;e!==null;)Qc(e,t,n),e=e.sibling}function $c(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[ot]=e,t[st]=n}catch(t){Z(e,e.return,t)}}var el=!1,tl=!1,nl=!1,rl=typeof WeakSet==`function`?WeakSet:Set,il=null;function al(e,t){if(e=e.containerInfo,Rd=sp,e=Dr(e),Or(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,il=t;il!==null;)if(t=il,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,il=e;else for(;il!==null;){switch(t=il,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(o,r,n),o[ot]=e,yt(o),r=o;break a;case`link`:var s=Vf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Tr(s,h),v=Tr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,M.T=null,n=lu,lu=null;var o=au,s=su;if(iu=0,ou=au=null,su=0,K&6)throw Error(i(331));var c=K;if(K|=4,Pl(o.current),El(o,o.current,s,n),K=c,id(0,!1),Ie&&typeof Ie.onPostCommitFiberRoot==`function`)try{Ie.onPostCommitFiberRoot(Fe,o)}catch{}return!0}finally{N.p=a,M.T=r,Vu(e,t)}}function Wu(e,t,n){t=vi(n,t),t=Qs(e.stateNode,t,2),e=Ua(e,t,2),e!==null&&(Ze(e,2),rd(e))}function Z(e,t,n){if(e.tag===3)Wu(e,e,n);else for(;t!==null;){if(t.tag===3){Wu(t,e,n);break}if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(ru===null||!ru.has(r))){e=vi(n,e),n=$s(2),r=Ua(t,n,2),r!==null&&(ec(n,r,t,e),Ze(r,2),rd(r));break}}t=t.return}}function Gu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Rl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Hl=!0,i.add(n),e=Ku.bind(null,e,t,n),t.then(e,e))}function Ku(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,q===e&&(Y&n)===n&&(Wl===4||Wl===3&&(Y&62914560)===Y&&300>Ee()-$l?!(K&2)&&Su(e,0):ql|=n,Yl===Y&&(Yl=0)),rd(e)}function qu(e,t){t===0&&(t=Ye()),e=ri(e,t),e!==null&&(Ze(e,t),rd(e))}function Ju(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),qu(e,n)}function Yu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),qu(e,n)}function Xu(e,t){return Se(e,t)}var Zu=null,Qu=null,$u=!1,ed=!1,td=!1,nd=0;function rd(e){e!==Qu&&e.next===null&&(Qu===null?Zu=Qu=e:Qu=Qu.next=e),ed=!0,$u||($u=!0,ud())}function id(e,t){if(!td&&ed){td=!0;do for(var n=!1,r=Zu;r!==null;){if(!t){if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Re(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ld(r,a))}else a=Y,a=Ke(r,r===q?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||qe(r,a)||(n=!0,ld(r,a))}r=r.next}while(n);td=!1}}function ad(){od()}function od(){ed=$u=!1;var e=0;nd!==0&&Gd()&&(e=nd);for(var t=Ee(),n=null,r=Zu;r!==null;){var i=r.next,a=sd(r,t);a===0?(r.next=null,n===null?Zu=i:n.next=i,i===null&&(Qu=n)):(n=r,(e!==0||a&3)&&(ed=!0)),r=i}iu!==0&&iu!==5||id(e,!1),nd!==0&&(nd=0)}function sd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Rt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),yt(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Rt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Rt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Rt(n.imageSizes)+`"]`)):i+=`[href="`+Rt(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),yt(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Rt(r)+`"][href="`+Rt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),yt(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=vt(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);yt(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=vt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),yt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=vt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),yt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var a=(a=ce.current)?gf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=vt(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var o=vt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(jf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),o||Nf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=vt(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Af(e){return`href="`+Rt(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),yt(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Rt(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Rt(n.href)+`"]`);if(r)return t.instance=r,yt(r),r;var a=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),yt(r),Pd(r,`style`,a),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Af(n.href);var o=e.querySelector(jf(a));if(o)return t.state.loading|=4,t.instance=o,yt(o),o;r=Mf(n),(a=mf.get(a))&&Rf(r,a),o=(e.ownerDocument||e).createElement(`link`),yt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Pd(o,`link`,r),t.state.loading|=4,Lf(o,n.precedence,e),t.instance=o;case`script`:return o=Pf(n.src),(a=e.querySelector(Ff(o)))?(t.instance=a,yt(a),a):(r=n,(a=mf.get(o))&&(r=h({},n),zf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),yt(a),Pd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,yt(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),yt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=h()})),_=c(u(),1),v=g(),y=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),b=o(((e,t)=>{t.exports=y()}))(),x=(0,_.createContext)(void 0);function S(){let e=(0,_.useContext)(x);if(!e)throw Error(`useToast must be used within a ToastProvider`);return e.toast}var C=0;function w({children:e}){let[t,n]=(0,_.useState)([]),r=(0,_.useState)(()=>new Map)[0];(0,_.useEffect)(()=>()=>{r.forEach(e=>clearTimeout(e)),r.clear()},[r]);let i=(0,_.useCallback)(e=>{let t=r.get(e);t&&(clearTimeout(t),r.delete(e)),n(t=>t.filter(t=>t.id!==e))},[r]),a=(0,_.useCallback)((e,t=`info`)=>{let i=++C;n(n=>[...n,{id:i,message:e,type:t}]);let a=setTimeout(()=>{r.delete(i),n(e=>e.filter(e=>e.id!==i))},4e3);r.set(i,a)},[r]),o=(0,_.useMemo)(()=>({success:e=>a(e,`success`),error:e=>a(e,`error`),info:e=>a(e,`info`),warning:e=>a(e,`warning`),showToast:(e,t)=>a(e,t||`info`),dismiss:i}),[a,i]);return(0,b.jsxs)(x.Provider,{value:{toast:o},children:[e,(0,b.jsx)(`div`,{className:`toast-container`,"aria-live":`polite`,"aria-atomic":`true`,children:t.map(e=>(0,b.jsxs)(`div`,{className:`toast toast-${e.type}`,role:`status`,children:[e.type===`error`&&(0,b.jsx)(`i`,{className:`fa-solid fa-circle-exclamation`,"aria-hidden":`true`}),e.type===`success`&&(0,b.jsx)(`i`,{className:`fa-solid fa-circle-check`,"aria-hidden":`true`}),e.type===`info`&&(0,b.jsx)(`i`,{className:`fa-solid fa-circle-info`,"aria-hidden":`true`}),e.type===`warning`&&(0,b.jsx)(`i`,{className:`fa-solid fa-triangle-exclamation`,"aria-hidden":`true`}),(0,b.jsx)(`span`,{className:`toast-message`,children:e.message}),(0,b.jsx)(`button`,{type:`button`,className:`toast-dismiss-btn`,onClick:()=>i(e.id),"aria-label":`Dismiss notification`,title:`Dismiss`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-xmark`,"aria-hidden":`true`})})]},e.id))})]})}function T({stats:e,refreshStats:t}){let n=S();return e?(0,b.jsxs)(`div`,{className:`tab-content active`,children:[(0,b.jsxs)(`div`,{className:`overview-grid`,children:[(0,b.jsxs)(`div`,{className:`glass-card stat-metric`,children:[(0,b.jsx)(`div`,{className:`metric-icon`,children:(0,b.jsx)(`i`,{className:`fa-brands fa-github`})}),(0,b.jsxs)(`div`,{className:`metric-info`,children:[(0,b.jsx)(`span`,{className:`stat-number`,children:(e.repos_count??e.git_repos??0).toLocaleString()}),(0,b.jsx)(`span`,{className:`stat-label`,children:`Git Repositories`})]})]}),(0,b.jsxs)(`div`,{className:`glass-card stat-metric`,children:[(0,b.jsx)(`div`,{className:`metric-icon`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-code`})}),(0,b.jsxs)(`div`,{className:`metric-info`,children:[(0,b.jsx)(`span`,{className:`stat-number`,children:(e.symbols_count||0).toLocaleString()}),(0,b.jsx)(`span`,{className:`stat-label`,children:`AST Code Symbols`})]})]}),(0,b.jsxs)(`div`,{className:`glass-card stat-metric`,children:[(0,b.jsx)(`div`,{className:`metric-icon`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-file-code`})}),(0,b.jsxs)(`div`,{className:`metric-info`,children:[(0,b.jsx)(`span`,{className:`stat-number`,children:(e.files_count||0).toLocaleString()}),(0,b.jsx)(`span`,{className:`stat-label`,children:`Indexed Files`})]})]}),(0,b.jsxs)(`div`,{className:`glass-card stat-metric`,children:[(0,b.jsx)(`div`,{className:`metric-icon`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-network-wired`})}),(0,b.jsxs)(`div`,{className:`metric-info`,children:[(0,b.jsx)(`span`,{className:`stat-number`,children:(e.points_count||0).toLocaleString()}),(0,b.jsxs)(`span`,{className:`stat-label`,children:[(e.vector_store_provider||`Qdrant`).toUpperCase(),` (`,(e.vector_store_mode||`embedded`)===`embedded`?`Embedded`:`Remote`,`) Vectors`]})]})]})]}),(0,b.jsxs)(`div`,{className:`two-col-layout`,style:{marginTop:`20px`},children:[(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-server`}),` System & Embedding Specs`]}),(0,b.jsxs)(`div`,{className:`specs-list`,children:[(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Vector Database:`}),(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`},children:[(0,b.jsxs)(`span`,{className:`badge badge-accent`,children:[e.vector_store_provider===`chroma`?`ChromaDB`:`Qdrant`,` (`,(e.vector_store_mode||`embedded`)===`embedded`?`Embedded Disk`:`Remote Server`,`)`]}),e.vector_db_status&&(0,b.jsx)(`span`,{className:`badge ${e.vector_db_status===`Healthy`?`badge-success`:`badge-danger`}`,"data-testid":`overview-vector-db-status`,children:e.vector_db_status})]})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Dense Embedding Model:`}),(0,b.jsx)(`code`,{children:e.dense_model||`bge-small-en-v1.5 (384d)`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Sparse BM25 Model:`}),(0,b.jsx)(`code`,{children:e.sparse_model?`${e.sparse_model} (FastEmbed)`:`Qdrant/bm25 (FastEmbed)`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Retrieval Strategy:`}),(0,b.jsx)(`span`,{className:`badge badge-accent`,children:e.vector_store_provider===`chroma`?`Dense Vector Cosine Similarity`:`Dense + BM25 Normalized Weighted Fusion`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`AST Chunker:`}),(0,b.jsx)(`span`,{children:`Tree-sitter AST (Classes, Functions, Methods)`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Last Global Index:`}),(0,b.jsx)(`span`,{className:`code`,children:e.last_indexed||`Never`})]})]}),(0,b.jsx)(`div`,{style:{marginTop:`20px`},children:(0,b.jsx)(`button`,{className:`btn btn-primary`,onClick:async()=>{try{let e=await fetch(`/admin/api/reindex`,{method:`POST`});if(!e.ok){let t=await e.json().catch(()=>({}));throw Error(t.error||`Failed to trigger reindex`)}n.success(`Re-indexing triggered successfully`),t()}catch(e){n.error(`Reindex error: `+e.message)}},disabled:e.is_indexing,children:e.is_indexing?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Syncing...`]}):(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-arrows-rotate`}),` Reindex All Sources`]})})})]}),(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-tags`}),` Top Extracted Topics & Symbols`]}),(0,b.jsx)(`div`,{className:`tag-cloud`,children:!e.top_keywords||e.top_keywords.length===0?(0,b.jsx)(`span`,{className:`text-muted`,children:`No topics extracted yet. Sync repositories to populate.`}):e.top_keywords.map(e=>(0,b.jsx)(`span`,{className:`topic-tag`,children:e},e))})]})]})]}):(0,b.jsx)(`div`,{className:`tab-content active`,children:(0,b.jsx)(`p`,{children:`Loading...`})})}function E({isOpen:e,onClose:t,onSave:n,alias:r,setAlias:i,url:a,setUrl:o,branch:s,setBranch:c,provider:l,setProvider:u,authUser:d,setAuthUser:f,token:p,setToken:m,isSaving:h}){return e?(0,b.jsx)(`div`,{className:`modal-backdrop`,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,b.jsxs)(`div`,{className:`glass-card modal-card`,children:[(0,b.jsxs)(`div`,{className:`modal-header`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-code-branch`}),` Register Git Repository`]}),(0,b.jsx)(`button`,{className:`btn-close`,onClick:t,children:`×`})]}),(0,b.jsxs)(`form`,{onSubmit:n,children:[(0,b.jsxs)(`div`,{className:`form-row`,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`repo-alias`,children:`Repository Alias / Identifier`}),(0,b.jsx)(`input`,{type:`text`,id:`repo-alias`,required:!0,placeholder:`e.g. backend-api or contextcortex`,value:r,onChange:e=>i(e.target.value)})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`repo-provider`,children:`Git Provider`}),(0,b.jsxs)(`select`,{id:`repo-provider`,value:l,onChange:e=>u(e.target.value),children:[(0,b.jsx)(`option`,{value:`auto`,children:`Auto-Detect`}),(0,b.jsx)(`option`,{value:`github`,children:`GitHub / GitHub Enterprise`}),(0,b.jsx)(`option`,{value:`gitlab`,children:`GitLab (Cloud / Self-Hosted)`}),(0,b.jsx)(`option`,{value:`gitea`,children:`Gitea / Forgejo`}),(0,b.jsx)(`option`,{value:`bitbucket`,children:`Bitbucket`}),(0,b.jsx)(`option`,{value:`generic`,children:`Generic Git (HTTP / HTTPS)`})]})]})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`repo-url`,children:`Git Clone URL (HTTP / HTTPS)`}),(0,b.jsx)(`input`,{type:`text`,id:`repo-url`,required:!0,placeholder:`https://github.com/owner/repo.git or http://git.lan:3000/repo.git`,value:a,onChange:e=>o(e.target.value)})]}),(0,b.jsxs)(`div`,{className:`form-row form-row-3col`,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`repo-branch`,children:`Branch / Tag`}),(0,b.jsx)(`input`,{type:`text`,id:`repo-branch`,placeholder:`main`,value:s,onChange:e=>c(e.target.value)})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`repo-user`,children:`Auth User (Optional)`}),(0,b.jsx)(`input`,{type:`text`,id:`repo-user`,placeholder:`e.g. oauth2`,value:d,onChange:e=>f(e.target.value)})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`repo-token`,children:`Auth Token (Optional)`}),(0,b.jsx)(`input`,{type:`password`,id:`repo-token`,placeholder:`Token override`,value:p,onChange:e=>m(e.target.value)})]})]}),(0,b.jsxs)(`div`,{className:`modal-footer`,children:[(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:t,children:`Cancel`}),(0,b.jsx)(`button`,{type:`submit`,className:`btn btn-primary`,disabled:h,children:h?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Adding & Syncing...`]}):`Add & Start Sync`})]})]})]})}):null}function D({repo:e,onClose:t,onCopyUrl:n,copiedUrl:r}){if(!e)return null;let i=`${typeof window<`u`?window.location.origin:``}/api/webhooks/git`;return(0,b.jsx)(`div`,{className:`modal-backdrop`,onClick:e=>{e.target===e.currentTarget&&t()},"data-testid":`webhook-modal-backdrop`,children:(0,b.jsxs)(`div`,{className:`glass-card modal-card`,style:{maxWidth:`650px`},children:[(0,b.jsxs)(`div`,{className:`modal-header`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-bolt`}),` Webhook Setup: `,e.name]}),(0,b.jsx)(`button`,{className:`btn-close`,onClick:t,"aria-label":`Close webhook modal`,children:`×`})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`16px`,fontSize:`0.9rem`},children:[(0,b.jsx)(`p`,{className:`text-muted`,children:`Configure a webhook in your Git repository provider to automatically trigger synchronization on every push event.`}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{children:`Webhook URL (Payload URL)`}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`8px`},children:[(0,b.jsx)(`input`,{type:`text`,readOnly:!0,value:i,style:{fontFamily:`var(--font-family-mono)`,fontSize:`0.85rem`},"aria-label":`Webhook Payload URL`}),(0,b.jsxs)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:()=>n(i),style:{minWidth:`95px`},"aria-label":`Copy Webhook URL`,children:[(0,b.jsx)(`i`,{className:`fa-solid ${r?`fa-check`:`fa-copy`}`}),` `,r?`Copied!`:`Copy`]})]})]}),e.webhook_secret&&(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{children:`Repository Secret Token (HMAC)`}),(0,b.jsx)(`input`,{type:`text`,readOnly:!0,value:e.webhook_secret,style:{fontFamily:`var(--font-family-mono)`,fontSize:`0.85rem`},"aria-label":`Repository Secret Token`})]}),(0,b.jsxs)(`div`,{style:{background:`rgba(0, 0, 0, 0.25)`,border:`1px solid var(--border-card)`,borderRadius:`8px`,padding:`14px`},children:[(0,b.jsxs)(`h3`,{style:{fontSize:`0.95rem`,marginBottom:`10px`,color:`var(--text)`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-list-check`}),` Provider Setup Instructions`]}),(0,b.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`10px`,fontSize:`0.85rem`},children:[(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`strong`,{style:{color:`#fff`},children:[(0,b.jsx)(`i`,{className:`fa-brands fa-github`,style:{marginRight:`6px`}}),` GitHub:`]}),(0,b.jsxs)(`div`,{style:{color:`var(--text-muted)`,marginTop:`2px`,marginLeft:`18px`},children:[`Navigate to `,(0,b.jsx)(`code`,{children:`Settings > Webhooks > Add webhook`}),` → set `,(0,b.jsx)(`em`,{children:`Payload URL`}),` to the URL above → set `,(0,b.jsx)(`em`,{children:`Content type`}),` to `,(0,b.jsx)(`code`,{children:`application/json`}),` → select `,(0,b.jsx)(`em`,{children:`Push events`}),` → click `,(0,b.jsx)(`strong`,{children:`Add webhook`}),`.`]})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`strong`,{style:{color:`#fff`},children:[(0,b.jsx)(`i`,{className:`fa-brands fa-gitlab`,style:{color:`#fc6d26`,marginRight:`6px`}}),` GitLab:`]}),(0,b.jsxs)(`div`,{style:{color:`var(--text-muted)`,marginTop:`2px`,marginLeft:`18px`},children:[`Navigate to `,(0,b.jsx)(`code`,{children:`Settings > Webhooks`}),` (or `,(0,b.jsx)(`code`,{children:`Settings > Integrations`}),`) → set `,(0,b.jsx)(`em`,{children:`URL`}),` → select `,(0,b.jsx)(`em`,{children:`Push events`}),` → click `,(0,b.jsx)(`strong`,{children:`Add webhook`}),`.`]})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`strong`,{style:{color:`#fff`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-mug-hot`,style:{color:`#609926`,marginRight:`6px`}}),` Gitea / Forgejo:`]}),(0,b.jsxs)(`div`,{style:{color:`var(--text-muted)`,marginTop:`2px`,marginLeft:`18px`},children:[`Navigate to `,(0,b.jsx)(`code`,{children:`Settings > Webhooks > Add Webhook > Gitea`}),` → set `,(0,b.jsx)(`em`,{children:`Target URL`}),` → set `,(0,b.jsx)(`em`,{children:`HTTP Method`}),` to `,(0,b.jsx)(`code`,{children:`POST`}),` → select `,(0,b.jsx)(`em`,{children:`Push Events`}),` → click `,(0,b.jsx)(`strong`,{children:`Add Webhook`}),`.`]})]})]})]}),(0,b.jsx)(`div`,{className:`modal-footer`,style:{marginTop:`8px`,padding:0},children:(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:t,children:`Close`})})]})]})})}function O({repos:e,isLoading:t,syncStates:n,onSync:r,onToggleAutoSync:i,onOpenWebhook:a,onDelete:o,onOpenSyncDrawer:s}){let c=e=>{let t=(e||`github`).toLowerCase();return t===`gitlab`?(0,b.jsx)(`i`,{className:`fa-brands fa-gitlab`,style:{color:`#fc6d26`,marginRight:`6px`},title:`GitLab`}):t===`gitea`||t===`forgejo`?(0,b.jsx)(`i`,{className:`fa-solid fa-mug-hot`,style:{color:`#609926`,marginRight:`6px`},title:`Gitea / Forgejo`}):t===`bitbucket`?(0,b.jsx)(`i`,{className:`fa-brands fa-bitbucket`,style:{color:`#2684ff`,marginRight:`6px`},title:`Bitbucket`}):t===`generic`?(0,b.jsx)(`i`,{className:`fa-solid fa-code-branch`,style:{color:`var(--accent)`,marginRight:`6px`},title:`Generic Git`}):(0,b.jsx)(`i`,{className:`fa-brands fa-github`,style:{marginRight:`6px`},title:`GitHub`})},l=(e,t)=>{let n=t?.step||1,r=t?.total_steps||5,i=t?.step_name||`Syncing...`,a=t?.percent??0,o=t?.current_file;return(0,b.jsxs)(`div`,{className:`sync-progress-container`,onClick:()=>s?.(e.id),role:`button`,tabIndex:0,style:{cursor:`pointer`},title:`Click to view live ingestion progress & logs`,onKeyDown:t=>{(t.key===`Enter`||t.key===` `)&&s?.(e.id)},children:[(0,b.jsxs)(`div`,{className:`progress-pill badge badge-warning`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),(0,b.jsxs)(`span`,{children:[`Step `,n,`/`,r,`: `,i,` (`,a,`%)`]})]}),(0,b.jsx)(`div`,{className:`sync-progress-bar-wrapper`,children:(0,b.jsx)(`div`,{className:`sync-progress-bar-fill fill-active`,style:{width:`${a}%`}})}),o&&(0,b.jsxs)(`div`,{className:`sync-file-caption`,title:o,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-code`,style:{marginRight:`4px`}}),(0,b.jsx)(`code`,{children:o})]})]})};return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`div`,{className:`table-container desktop-table-view`,children:(0,b.jsxs)(`table`,{children:[(0,b.jsx)(`thead`,{children:(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`th`,{children:`Repo Alias`}),(0,b.jsx)(`th`,{children:`Git URL`}),(0,b.jsx)(`th`,{children:`Branch`}),(0,b.jsx)(`th`,{children:`Commit SHA`}),(0,b.jsx)(`th`,{children:`Status`}),(0,b.jsx)(`th`,{children:`Auto-Sync`}),(0,b.jsx)(`th`,{children:`Files`}),(0,b.jsx)(`th`,{children:`Last Synced`}),(0,b.jsx)(`th`,{children:`Actions`})]})}),(0,b.jsx)(`tbody`,{children:t&&e.length===0?(0,b.jsx)(`tr`,{children:(0,b.jsxs)(`td`,{colSpan:9,className:`empty-state`,"data-testid":`repo-loading-state`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`,style:{marginRight:`8px`,color:`var(--primary)`}}),`Loading repositories...`]})}):e.length===0?(0,b.jsx)(`tr`,{children:(0,b.jsx)(`td`,{colSpan:9,className:`empty-state`,children:`No Git repositories registered. Click "Add Repository" to index a remote repo.`})}):e.map(e=>{let t=e.auto_sync!==!1&&e.auto_sync!==0,u=n?.[e.id],d=e.status===`syncing`||u?.status===`syncing`;return(0,b.jsxs)(`tr`,{children:[(0,b.jsxs)(`td`,{children:[c(e.provider),(0,b.jsx)(`strong`,{children:e.name})]}),(0,b.jsx)(`td`,{children:(0,b.jsxs)(`a`,{href:e.url,target:`_blank`,rel:`noreferrer`,className:`repo-url-link`,style:{color:`var(--primary)`,textDecoration:`none`,fontSize:`0.85rem`},title:e.url,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-arrow-up-right-from-square`}),` `,e.url]})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`code`,{children:e.branch})}),(0,b.jsx)(`td`,{children:e.commit_sha?(0,b.jsx)(`code`,{children:e.commit_sha.substring(0,8)}):(0,b.jsx)(`span`,{className:`text-muted`,children:`-`})}),(0,b.jsx)(`td`,{children:d?l(e,u):e.status===`error`||u?.status===`error`?(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`span`,{className:`badge badge-danger`,title:e.last_error||u?.error||`Sync failed`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-circle-exclamation`}),` Error`]}),(e.last_error||u?.error)&&(0,b.jsx)(`div`,{style:{fontSize:`0.75rem`,color:`var(--danger)`,marginTop:`4px`,maxWidth:`180px`,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`},title:e.last_error||u?.error||``,children:e.last_error||u?.error})]}):e.status===`pending`||u?.status===`pending`?(0,b.jsxs)(`span`,{className:`badge badge-primary`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-clock`}),` Pending`]}):(0,b.jsxs)(`span`,{className:`badge badge-success`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-check`}),` Synced`]})}),(0,b.jsx)(`td`,{children:(0,b.jsxs)(`button`,{type:`button`,className:`badge ${t?`badge-success`:`badge-danger`}`,style:{cursor:`pointer`,background:t?`rgba(16, 185, 129, 0.2)`:`rgba(239, 68, 68, 0.2)`,border:t?`1px solid rgba(16, 185, 129, 0.4)`:`1px solid rgba(239, 68, 68, 0.4)`,color:t?`#6ee7b7`:`#fca5a5`,padding:`4px 8px`,display:`inline-flex`,alignItems:`center`,gap:`6px`},onClick:()=>i(e.id,t),title:`Auto-Sync: ${t?`ON`:`OFF`} (Click to toggle)`,"aria-label":`Toggle auto-sync for ${e.name}`,children:[(0,b.jsx)(`i`,{className:`fa-solid ${t?`fa-toggle-on`:`fa-toggle-off`}`}),`Auto-Sync: `,t?`ON`:`OFF`]})}),(0,b.jsxs)(`td`,{children:[(e.file_count||0).toLocaleString(),` files`]}),(0,b.jsx)(`td`,{style:{fontSize:`0.8rem`,color:`var(--text-muted)`},children:e.last_synced||`Never`}),(0,b.jsx)(`td`,{children:(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`6px`},children:[(0,b.jsxs)(`button`,{className:`btn btn-secondary`,style:{padding:`4px 8px`,fontSize:`0.8rem`},onClick:()=>s?.(e.id),title:`View Ingestion Logs`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-terminal`}),` Logs`]}),(0,b.jsxs)(`button`,{className:`btn btn-secondary`,style:{padding:`4px 8px`,fontSize:`0.8rem`},onClick:()=>r(e.id),title:`Trigger Sync`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-arrows-rotate`}),` Sync`]}),(0,b.jsxs)(`button`,{className:`btn btn-secondary`,style:{padding:`4px 8px`,fontSize:`0.8rem`},onClick:()=>a(e),title:`Webhook Setup`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-bolt`}),` Webhook`]}),(0,b.jsx)(`button`,{className:`btn-icon btn-delete`,onClick:()=>o(e.id,e.name),title:`Delete Repo`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-trash-can`})})]})})]},e.id)})})]})}),(0,b.jsx)(`div`,{className:`mobile-card-list`,children:t&&e.length===0?(0,b.jsxs)(`div`,{className:`empty-state`,"data-testid":`repo-loading-state-mobile`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`,style:{marginRight:`8px`,color:`var(--primary)`}}),`Loading repositories...`]}):e.length===0?(0,b.jsx)(`div`,{className:`empty-state`,children:`No Git repositories registered. Click "Add Repository" to index a remote repo.`}):e.map(e=>{let t=e.auto_sync!==!1&&e.auto_sync!==0,u=n?.[e.id],d=e.status===`syncing`||u?.status===`syncing`;return(0,b.jsxs)(`div`,{className:`data-mobile-card`,children:[(0,b.jsxs)(`div`,{className:`data-mobile-card-header`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`},children:[c(e.provider),(0,b.jsx)(`strong`,{style:{fontSize:`1rem`},children:e.name})]}),!d&&(e.status===`error`||u?.status===`error`?(0,b.jsxs)(`span`,{className:`badge badge-danger`,title:e.last_error||u?.error||`Sync failed`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-circle-exclamation`}),` Error`]}):e.status===`pending`||u?.status===`pending`?(0,b.jsxs)(`span`,{className:`badge badge-primary`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-clock`}),` Pending`]}):(0,b.jsxs)(`span`,{className:`badge badge-success`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-check`}),` Synced`]}))]}),d&&(0,b.jsx)(`div`,{style:{margin:`4px 0 2px 0`},children:l(e,u)}),(0,b.jsxs)(`div`,{className:`data-mobile-card-body`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`data-label`,children:`URL:`}),(0,b.jsx)(`a`,{href:e.url,target:`_blank`,rel:`noreferrer`,style:{color:`var(--primary)`,textDecoration:`none`},children:e.url})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`data-label`,children:`Branch:`}),(0,b.jsx)(`code`,{children:e.branch})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`data-label`,children:`Commit:`}),e.commit_sha?(0,b.jsx)(`code`,{children:e.commit_sha.substring(0,8)}):`-`]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`data-label`,children:`Auto-Sync:`}),(0,b.jsx)(`button`,{type:`button`,className:`badge ${t?`badge-success`:`badge-danger`}`,style:{cursor:`pointer`,padding:`2px 6px`,fontSize:`0.75rem`},onClick:()=>i(e.id,t),title:`Auto-Sync: ${t?`ON`:`OFF`} (Click to toggle)`,"aria-label":`Toggle auto-sync for ${e.name}`,children:t?`ON`:`OFF`})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`data-label`,children:`Files:`}),(e.file_count||0).toLocaleString(),` files`]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`data-label`,children:`Last Synced:`}),e.last_synced||`Never`]}),!d&&(e.last_error||u?.error)&&(0,b.jsxs)(`div`,{style:{color:`var(--danger)`,fontSize:`0.75rem`},children:[(0,b.jsx)(`span`,{className:`data-label`,children:`Error:`}),e.last_error||u?.error]})]}),(0,b.jsxs)(`div`,{className:`data-mobile-card-actions`,children:[(0,b.jsxs)(`button`,{className:`btn btn-secondary`,onClick:()=>s?.(e.id),title:`View Ingestion Logs`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-terminal`}),` Logs`]}),(0,b.jsxs)(`button`,{className:`btn btn-secondary`,onClick:()=>r(e.id),title:`Trigger Sync`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-arrows-rotate`}),` Sync`]}),(0,b.jsxs)(`button`,{className:`btn btn-secondary`,onClick:()=>a(e),title:`Webhook Setup`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-bolt`}),` Webhook`]}),(0,b.jsxs)(`button`,{className:`btn btn-secondary btn-delete`,onClick:()=>o(e.id,e.name),title:`Delete Repo`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-trash-can`}),` Delete`]})]})]},`card-${e.id}`)})})]})}var k=[{step:1,title:`1. Connecting & Remote Check`},{step:2,title:`2. Shallow Cloning Repository`},{step:3,title:`3. Computing File Delta & Scanning`},{step:4,title:`4. Parsing AST Symbols & API Routes`},{step:5,title:`5. Upserting Embeddings & Finalizing`}];function ee({isOpen:e,onClose:t,repoId:n,repoName:r,job:i,onCancelSync:a}){let[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(!0),[u,d]=(0,_.useState)(!1),[f,p]=(0,_.useState)(0),[m,h]=(0,_.useState)(!1),g=(0,_.useRef)(null),v=(0,_.useRef)(null),y=n??i?.repo_id??null,x=r||i?.repo_name||`Repository`;(0,_.useEffect)(()=>{if(!e||!i?.started_at){p(0);return}let t=()=>{let e=i.started_at>1e11?i.started_at:i.started_at*1e3;return Math.max(0,Math.floor((Date.now()-e)/1e3))};if(p(t()),i.status===`syncing`||i.status===`pending`){let e=setInterval(()=>{p(t())},1e3);return()=>clearInterval(e)}},[e,i?.started_at,i?.status]);let S=e=>{let t=Math.floor(e/60),n=e%60;return`${t.toString().padStart(2,`0`)}:${n.toString().padStart(2,`0`)}`},C=i?.logs,w=(0,_.useMemo)(()=>{let e=C||[];if(!o.trim())return e;let t=o.toLowerCase();return e.filter(e=>e.message.toLowerCase().includes(t)||e.level.toLowerCase().includes(t)||e.timestamp.toLowerCase().includes(t))},[C,o]);(0,_.useEffect)(()=>{c&&g.current&&typeof g.current.scrollIntoView==`function`&&g.current.scrollIntoView({behavior:`smooth`})},[w,c]);let T=async()=>{let e=(i?.logs||[]).map(e=>`[${e.timestamp}] [${e.level.toUpperCase()}] ${e.message}`).join(` -`);try{await navigator.clipboard.writeText(e),d(!0),setTimeout(()=>d(!1),2e3)}catch(e){console.error(`Failed to copy logs:`,e)}},E=async()=>{if(!(!y||!a))try{h(!0),await a(y)}catch(e){console.error(`Failed to cancel sync:`,e)}finally{h(!1)}};if(!e)return null;let D=i?.status||`pending`,O=i?.percent??(D===`synced`?100:0),ee=i?.step??(D===`synced`?5:1);return(0,b.jsx)(`div`,{className:`sync-drawer-backdrop`,onClick:e=>{e.target===e.currentTarget&&t()},"data-testid":`sync-drawer-backdrop`,children:(0,b.jsxs)(`div`,{className:`sync-drawer`,children:[(0,b.jsxs)(`div`,{className:`sync-drawer-header`,children:[(0,b.jsxs)(`div`,{className:`sync-drawer-title-group`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`10px`,flexWrap:`wrap`},children:[(0,b.jsxs)(`h2`,{style:{fontSize:`1.1rem`,margin:0,fontWeight:700,color:`var(--text)`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-code-branch`,style:{marginRight:`8px`,color:`var(--primary)`}}),x,` Ingestion Progress & Live Logs`]}),D===`syncing`?(0,b.jsxs)(`span`,{className:`badge badge-warning`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Syncing`]}):D===`synced`?(0,b.jsxs)(`span`,{className:`badge badge-success`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-check`}),` Synced`]}):D===`error`?(0,b.jsxs)(`span`,{className:`badge badge-danger`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-circle-exclamation`}),` Error`]}):(0,b.jsxs)(`span`,{className:`badge badge-primary`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-clock`}),` Pending`]})]}),(0,b.jsxs)(`div`,{className:`sync-drawer-meta`,style:{display:`flex`,alignItems:`center`,gap:`12px`,marginTop:`6px`,fontSize:`0.82rem`,color:`var(--text-muted)`},children:[(0,b.jsxs)(`span`,{children:[(0,b.jsx)(`i`,{className:`fa-regular fa-clock`,style:{marginRight:`4px`}}),`Elapsed: `,(0,b.jsx)(`strong`,{children:S(f)})]}),i?.step_name&&(0,b.jsxs)(`span`,{children:[`• Current: `,(0,b.jsx)(`strong`,{children:i.step_name})]})]})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`},children:[D===`syncing`&&a&&y!=null&&(0,b.jsxs)(`button`,{type:`button`,className:`btn btn-secondary btn-cancel-sync`,onClick:E,disabled:m,style:{fontSize:`0.8rem`,padding:`6px 12px`,color:`var(--danger)`,borderColor:`rgba(239, 68, 68, 0.4)`},children:[(0,b.jsx)(`i`,{className:`fa-solid ${m?`fa-spinner fa-spin`:`fa-stop`}`}),m?`Cancelling...`:`Cancel Sync`]}),(0,b.jsx)(`button`,{className:`btn-close`,onClick:t,"aria-label":`Close sync drawer`,style:{fontSize:`1.4rem`,lineHeight:1},children:`×`})]})]}),(0,b.jsxs)(`div`,{className:`sync-drawer-body`,children:[(0,b.jsxs)(`div`,{className:`sync-progress-section`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,marginBottom:`6px`,fontSize:`0.85rem`},children:[(0,b.jsx)(`span`,{style:{fontWeight:600,color:`var(--text)`},children:`Overall Ingestion`}),(0,b.jsxs)(`span`,{style:{fontWeight:700,color:D===`error`?`var(--danger)`:`var(--primary)`},children:[O,`%`]})]}),(0,b.jsx)(`div`,{className:`sync-progress-bar-container`,children:(0,b.jsx)(`div`,{className:`sync-progress-bar-fill ${D===`error`?`fill-error`:D===`synced`?`fill-success`:`fill-active`}`,style:{width:`${O}%`}})})]}),(0,b.jsxs)(`div`,{className:`sync-stepper-container`,children:[(0,b.jsx)(`h4`,{style:{fontSize:`0.82rem`,textTransform:`uppercase`,letterSpacing:`0.05em`,color:`var(--text-muted)`,marginBottom:`10px`},children:`Ingestion Stages`}),(0,b.jsx)(`div`,{className:`sync-stepper-list`,children:k.map(e=>{let t=D===`synced`||D!==`error`&&ee>e.step||D===`error`&&ee>e.step,n=D===`syncing`&&ee===e.step,r=D===`error`&&ee===e.step;return(0,b.jsxs)(`div`,{className:`sync-stepper-item ${t?`item-completed`:n?`item-active`:r?`item-error`:`item-pending`}`,children:[(0,b.jsxs)(`div`,{className:`sync-stepper-icon`,children:[t&&(0,b.jsx)(`i`,{className:`fa-solid fa-circle-check`,style:{color:`#10b981`}}),n&&(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`,style:{color:`#f59e0b`}}),r&&(0,b.jsx)(`i`,{className:`fa-solid fa-circle-xmark`,style:{color:`#ef4444`}}),!t&&!n&&!r&&(0,b.jsx)(`i`,{className:`fa-regular fa-circle`,style:{color:`var(--text-muted)`}})]}),(0,b.jsxs)(`div`,{className:`sync-stepper-content`,children:[(0,b.jsx)(`div`,{className:`sync-stepper-title`,style:{fontWeight:n?600:500},children:e.title}),n&&(0,b.jsxs)(`div`,{className:`sync-stepper-details`,children:[i?.current_file&&(0,b.jsxs)(`div`,{className:`sync-current-file`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-code`,style:{marginRight:`4px`}}),(0,b.jsx)(`code`,{children:i.current_file})]}),i?.total_files!=null&&i.total_files>0&&(0,b.jsxs)(`div`,{className:`sync-file-count`,style:{marginTop:`2px`,color:`var(--text-muted)`},children:[i.processed_files,` / `,i.total_files,` files (`,O,`%)`]})]}),r&&i?.error&&(0,b.jsxs)(`div`,{className:`sync-stepper-error-msg`,style:{marginTop:`4px`,color:`var(--danger)`,fontSize:`0.82rem`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-triangle-exclamation`,style:{marginRight:`4px`}}),i.error]})]})]},e.step)})})]}),(0,b.jsxs)(`div`,{className:`sync-terminal-section`,children:[(0,b.jsxs)(`div`,{className:`sync-terminal-header`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-terminal`,style:{color:`var(--accent)`}}),(0,b.jsx)(`span`,{style:{fontSize:`0.85rem`,fontWeight:600,color:`var(--text)`},children:`Live Terminal Output`}),(0,b.jsxs)(`span`,{className:`badge badge-secondary`,style:{fontSize:`0.75rem`,padding:`1px 6px`},children:[w.length,` `,w.length===1?`event`:`events`]})]}),(0,b.jsxs)(`div`,{className:`sync-terminal-actions`,children:[(0,b.jsxs)(`label`,{className:`sync-autoscroll-toggle`,children:[(0,b.jsx)(`input`,{type:`checkbox`,checked:c,onChange:e=>l(e.target.checked),"aria-label":`Autoscroll`}),(0,b.jsx)(`span`,{children:`Autoscroll`})]}),(0,b.jsxs)(`button`,{type:`button`,className:`btn btn-secondary btn-sm`,onClick:T,"aria-label":`Copy logs`,style:{fontSize:`0.75rem`,padding:`3px 8px`},children:[(0,b.jsx)(`i`,{className:`fa-solid ${u?`fa-check`:`fa-copy`}`}),` `,u?`Copied!`:`Copy Logs`]})]})]}),(0,b.jsxs)(`div`,{className:`sync-terminal-search`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-magnifying-glass`,style:{color:`var(--text-muted)`,fontSize:`0.8rem`}}),(0,b.jsx)(`input`,{type:`text`,placeholder:`Filter logs by keyword or level...`,value:o,onChange:e=>s(e.target.value),style:{width:`100%`,background:`transparent`,border:`none`,outline:`none`,color:`var(--text)`,fontSize:`0.82rem`,padding:`4px 0`}}),o&&(0,b.jsx)(`button`,{type:`button`,onClick:()=>s(``),style:{background:`transparent`,border:`none`,color:`var(--text-muted)`,cursor:`pointer`,fontSize:`0.8rem`},children:`×`})]}),(0,b.jsx)(`div`,{className:`sync-terminal-body`,ref:v,children:w.length===0?(0,b.jsxs)(`div`,{className:`sync-terminal-empty`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-clock-rotate-left`,style:{marginBottom:`6px`,fontSize:`1.2rem`,opacity:.5}}),(0,b.jsx)(`div`,{children:(C?.length||0)===0?`Waiting for sync activity...`:`No logs matching filter`})]}):(0,b.jsxs)(`div`,{className:`sync-terminal-lines`,children:[w.map((e,t)=>{let n=(e.level||`INFO`).toUpperCase(),r=`level-info`;return(n===`WARN`||n===`WARNING`)&&(r=`level-warn`),(n===`ERROR`||n===`FATAL`)&&(r=`level-error`),(0,b.jsxs)(`div`,{className:`sync-terminal-line`,children:[(0,b.jsxs)(`span`,{className:`log-time`,children:[`[`,e.timestamp,`]`]}),(0,b.jsx)(`span`,{className:`log-badge ${r}`,children:n}),(0,b.jsx)(`span`,{className:`log-msg`,children:e.message})]},t)}),(0,b.jsx)(`div`,{ref:g})]})})]})]})]})})}function A(){let[e,t]=(0,_.useState)({}),[n,r]=(0,_.useState)(!1),i=(0,_.useRef)(null),a=(0,_.useCallback)(async()=>{try{let e=await fetch(`/admin/api/repos/sync-status`);if(e&&e.ok){let n=await e.json();if(n&&typeof n==`object`&&!Array.isArray(n)){let e={};Object.entries(n).forEach(([t,n])=>{let r=n;r&&(r.repo_id!=null||r.step!=null||r.status!=null)&&(e[Number(t)]=r)}),t(t=>({...t,...e}))}else if(Array.isArray(n)){let e={};n.forEach(t=>{t&&t.repo_id!=null&&(t.step!=null||t.status===`syncing`)&&(e[t.repo_id]=t)}),Object.keys(e).length>0&&t(t=>({...t,...e}))}}}catch(e){console.error(`Failed to fetch initial sync status snapshot:`,e)}},[]);return(0,_.useEffect)(()=>{if(a(),typeof EventSource>`u`)return;let e=null;try{e=new EventSource(`/admin/api/repos/sync/stream`),i.current=e,e.onopen=()=>{r(!0)},e.onerror=()=>{r(!1)},e.addEventListener(`init`,e=>{try{let n=JSON.parse(e.data);if(n&&typeof n==`object`){let e={};Object.entries(n).forEach(([t,n])=>{e[Number(t)]=n}),t(e),r(!0)}}catch(e){console.error(`Failed to parse init sync stream event:`,e)}}),e.addEventListener(`progress`,e=>{try{let n=JSON.parse(e.data),i=n.data||n;i&&i.repo_id!=null&&(t(e=>{let t=e[i.repo_id];return{...e,[i.repo_id]:{...t||{},...i,logs:i.logs??t?.logs??[]}}}),r(!0))}catch(e){console.error(`Failed to parse progress event:`,e)}}),e.addEventListener(`log`,e=>{try{let n=JSON.parse(e.data),r=n.repo_id??n.data?.repo_id,i=n.data&&!n.data.repo_id?n.data:n.log||n.data;r!=null&&i&&t(e=>{let t=e[r];if(!t)return e;let n=t.logs||[];return{...e,[r]:{...t,logs:[...n,i]}}})}catch(e){console.error(`Failed to parse log event:`,e)}})}catch(e){console.error(`Failed to initialize EventSource:`,e),r(!1)}return()=>{e&&(e.close(),i.current=null)}},[a]),{syncStates:e,isConnected:n,cancelSync:(0,_.useCallback)(async e=>{try{let t=await fetch(`/admin/api/repos/${e}/cancel-sync`,{method:`POST`});if(!t.ok){let e=await t.json().catch(()=>({}));throw Error(e.error||`Failed to cancel sync: ${t.statusText}`)}return await t.json()}catch(t){throw console.error(`Error cancelling sync for repo ${e}:`,t),t}},[])}}function te({refreshStats:e}){let[t,n]=(0,_.useState)([]),[r,i]=(0,_.useState)(!0),[a,o]=(0,_.useState)(!1),[s,c]=(0,_.useState)(null),[l,u]=(0,_.useState)(!1),[d,f]=(0,_.useState)(null),p=S(),{syncStates:m,cancelSync:h}=A(),[g,v]=(0,_.useState)(``),[y,x]=(0,_.useState)(``),[C,w]=(0,_.useState)(`main`),[T,k]=(0,_.useState)(`auto`),[te,ne]=(0,_.useState)(``),[re,j]=(0,_.useState)(``),[M,N]=(0,_.useState)(!1),P=(0,_.useCallback)(async()=>{try{let e=await fetch(`/admin/api/repos`);if(!e.ok){p.error(`Failed to load repositories`);return}let t=await e.json();n(Array.isArray(t)?t:[])}catch(e){p.error(`Error loading repos: `+e.message),console.error(`Error loading repos:`,e)}finally{i(!1)}},[p]);(0,_.useEffect)(()=>{P();let e=setInterval(P,8e3);return()=>clearInterval(e)},[P]);let ie=async t=>{t.preventDefault(),N(!0);try{let t=await fetch(`/admin/api/repos`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({name:g.trim(),url:y.trim(),branch:C.trim()||`main`,provider:T===`auto`?void 0:T,auth_user:te.trim()||null,auth_token:re.trim()||null})}),n=await t.json();if(!t.ok)throw Error(n.error||`Failed to add repository`);o(!1),v(``),x(``),w(`main`),k(`auto`),ne(``),j(``),P(),e(),p.success(`Repository '${g.trim()}' added successfully`)}catch(e){p.error(`Error: ${e.message}`)}finally{N(!1)}},ae=async t=>{n(e=>e.map(e=>e.id===t?{...e,status:`syncing`}:e));try{let n=await fetch(`/admin/api/repos/sync/${t}`,{method:`POST`});if(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.error||`Failed to trigger sync`)}P(),e(),p.info(`Sync triggered successfully`)}catch(e){P(),p.error(`Failed to trigger sync: `+e.message)}},F=async(e,t)=>{let r=!t;n(t=>t.map(t=>t.id===e?{...t,auto_sync:r}:t));try{let t=await fetch(`/admin/api/repos/${e}/auto-sync`,{method:`PATCH`,headers:{"Content-Type":`application/json`},body:JSON.stringify({auto_sync:r})});if(!t.ok){let e=await t.json().catch(()=>({}));throw Error(e.error||e.detail||`Failed to update auto-sync`)}p.info(`Auto-sync ${r?`enabled`:`disabled`}`),P()}catch(e){P(),p.error(`Failed to update auto-sync: `+e.message)}},I=async e=>{try{navigator?.clipboard?.writeText&&await navigator.clipboard.writeText(e),u(!0),p.info(`Webhook URL copied to clipboard`),setTimeout(()=>u(!1),2e3)}catch(e){p.error(`Failed to copy: `+e.message)}},L=async(t,r)=>{if(window.confirm(`Are you sure you want to delete repository '${r}'? All vectors and indexed symbols for this repo will be permanently purged.`)){n(e=>e.filter(e=>e.id!==t));try{let n=await fetch(`/admin/api/repos/${t}`,{method:`DELETE`});if(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.error||`Failed to delete repo`)}P(),e(),p.success(`Repository '${r}' deleted successfully`)}catch(e){P(),p.error(`Failed to delete repo: `+e.message)}}},oe=d===null?null:m[d],se=t.find(e=>e.id===d)?.name||oe?.repo_name||``;return(0,b.jsx)(`div`,{className:`tab-content active`,children:(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`div`,{className:`card-header-btn`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-code-branch`}),` Registered Git Repositories`]}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Supports GitHub, GitLab, Gitea, Bitbucket, and custom self-hosted Git repositories over HTTP/HTTPS.`})]}),(0,b.jsxs)(`button`,{className:`btn btn-primary`,onClick:()=>o(!0),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-plus`}),` Add Repository`]})]}),(0,b.jsx)(O,{repos:t,isLoading:r,syncStates:m,onSync:ae,onToggleAutoSync:F,onOpenWebhook:e=>c(e),onDelete:L,onOpenSyncDrawer:e=>f(e)}),(0,b.jsx)(E,{isOpen:a,onClose:()=>o(!1),onSave:ie,alias:g,setAlias:v,url:y,setUrl:x,branch:C,setBranch:w,provider:T,setProvider:k,authUser:te,setAuthUser:ne,token:re,setToken:j,isSaving:M}),(0,b.jsx)(D,{repo:s,onClose:()=>c(null),onCopyUrl:I,copiedUrl:l}),(0,b.jsx)(ee,{isOpen:d!==null,onClose:()=>f(null),repoId:d,repoName:se,job:oe,onCancelSync:h})]})})}function ne({refreshStats:e}){let[t,n]=(0,_.useState)([]),[r,i]=(0,_.useState)(!0),a=S(),[o,s]=(0,_.useState)(!1),[c,l]=(0,_.useState)(!1),[u,d]=(0,_.useState)(``),[f,p]=(0,_.useState)(`local`),[m,h]=(0,_.useState)(``),[g,v]=(0,_.useState)(`directory`),[y,x]=(0,_.useState)(1),[C,w]=(0,_.useState)(null),T=(0,_.useCallback)(async()=>{try{let e=await fetch(`/admin/api/paths`);if(!e.ok){a.error(`Failed to load local paths`);return}let t=await e.json();n(t)}catch(e){a.error(`Error loading paths: `+e.message),console.error(`Error loading paths:`,e)}finally{i(!1)}},[a]);(0,_.useEffect)(()=>{T();let e=setInterval(T,8e3);return()=>clearInterval(e)},[T]);let E=async t=>{t.preventDefault();try{let t=await fetch(`/admin/api/paths`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({path:u.trim(),repo:f.trim()||`local`,category:m.trim()||null,type:g,recursive:y,enabled:1})}),n=await t.json();if(!t.ok)throw Error(n.error||`Failed to add path`);s(!1),d(``),p(`local`),h(``),v(`directory`),x(1),T(),e(),a.success(`Path added successfully`)}catch(e){a.error(`Error: ${e.message}`)}},D=async t=>{if(window.confirm(`Are you sure you want to delete this local search path?`)){n(e=>e.filter(e=>e.id!==t));try{let n=await fetch(`/admin/api/paths/${t}`,{method:`DELETE`});if(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.error||`Failed to delete path`)}a.success(`Path deleted successfully`),T(),e()}catch(e){T(),a.error(`Failed to delete path: `+e.message)}}},O=(e=`/`)=>{l(!0),k(e)},k=async e=>{try{let t=await fetch(`/admin/api/browse?path=${encodeURIComponent(e)}`),n=await t.json();if(!t.ok){a.error(n.error||`Failed to browse directory`);return}w(n)}catch(e){a.error(`Browse error: `+e.message),console.error(`Browse error:`,e)}};return(0,b.jsxs)(`div`,{className:`tab-content active`,children:[(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`div`,{className:`card-header-btn`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-folder-tree`}),` Monitored Local Paths`]}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Mounted documentation vaults and local workspaces scanned for changes.`})]}),(0,b.jsxs)(`button`,{className:`btn btn-primary`,onClick:()=>s(!0),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-plus`}),` Add Local Path`]})]}),(0,b.jsx)(`div`,{className:`table-container desktop-table-view`,children:(0,b.jsxs)(`table`,{children:[(0,b.jsx)(`thead`,{children:(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`th`,{children:`Path`}),(0,b.jsx)(`th`,{children:`Repo Alias`}),(0,b.jsx)(`th`,{children:`Type`}),(0,b.jsx)(`th`,{children:`Recursive`}),(0,b.jsx)(`th`,{children:`Category`}),(0,b.jsx)(`th`,{children:`Enabled`}),(0,b.jsx)(`th`,{children:`Actions`})]})}),(0,b.jsx)(`tbody`,{children:r&&t.length===0?(0,b.jsx)(`tr`,{children:(0,b.jsxs)(`td`,{colSpan:7,className:`empty-state`,"data-testid":`path-loading-state`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`,style:{marginRight:`8px`,color:`var(--primary)`}}),`Loading local paths...`]})}):t.length===0?(0,b.jsx)(`tr`,{children:(0,b.jsx)(`td`,{colSpan:7,className:`empty-state`,children:`No local search paths configured.`})}):t.map(e=>(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`td`,{children:(0,b.jsx)(`code`,{children:e.path})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`strong`,{children:e.repo||`local`})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`span`,{className:`badge badge-primary`,children:e.type})}),(0,b.jsx)(`td`,{children:e.recursive?`Yes`:`No`}),(0,b.jsx)(`td`,{children:e.category?(0,b.jsx)(`span`,{className:`badge badge-accent`,children:e.category}):(0,b.jsx)(`span`,{className:`text-muted`,children:`-`})}),(0,b.jsx)(`td`,{children:e.enabled?(0,b.jsx)(`span`,{className:`badge badge-success`,children:`Enabled`}):(0,b.jsx)(`span`,{className:`badge badge-danger`,children:`Disabled`})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`button`,{className:`btn-icon btn-delete`,onClick:()=>D(e.id),title:`Delete Path`,"aria-label":`Delete Path`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-trash-can`})})})]},e.id))})]})}),(0,b.jsx)(`div`,{className:`mobile-card-list`,children:r&&t.length===0?(0,b.jsxs)(`div`,{className:`empty-state`,"data-testid":`path-loading-state-mobile`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`,style:{marginRight:`8px`,color:`var(--primary)`}}),`Loading local paths...`]}):t.length===0?(0,b.jsx)(`div`,{className:`empty-state`,children:`No local search paths configured.`}):t.map(e=>(0,b.jsxs)(`div`,{className:`data-mobile-card`,children:[(0,b.jsxs)(`div`,{className:`data-mobile-card-header`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,minWidth:0},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-folder-tree`,style:{color:`var(--accent)`}}),(0,b.jsx)(`strong`,{style:{fontSize:`1rem`},children:e.repo||`local`})]}),e.enabled?(0,b.jsx)(`span`,{className:`badge badge-success`,children:`Enabled`}):(0,b.jsx)(`span`,{className:`badge badge-danger`,children:`Disabled`})]}),(0,b.jsxs)(`div`,{className:`data-mobile-card-body`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Path: `}),(0,b.jsx)(`code`,{style:{wordBreak:`break-all`},children:e.path})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`8px`,flexWrap:`wrap`,alignItems:`center`},children:[(0,b.jsxs)(`span`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Type: `}),(0,b.jsx)(`span`,{className:`badge badge-primary`,children:e.type})]}),(0,b.jsxs)(`span`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Recursive: `}),(0,b.jsx)(`span`,{children:e.recursive?`Yes`:`No`})]})]}),e.category&&(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Category: `}),(0,b.jsx)(`span`,{className:`badge badge-accent`,children:e.category})]})]}),(0,b.jsx)(`div`,{className:`data-mobile-card-actions`,children:(0,b.jsxs)(`button`,{className:`btn btn-secondary btn-delete`,onClick:()=>D(e.id),title:`Delete Path`,"aria-label":`Delete Path`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-trash-can`}),` Delete`]})})]},`card-${e.id}`))})]}),o&&(0,b.jsx)(`div`,{className:`modal-backdrop`,children:(0,b.jsxs)(`div`,{className:`glass-card modal-card`,children:[(0,b.jsxs)(`div`,{className:`modal-header`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-folder-open`}),` Add Monitored Local Path`]}),(0,b.jsx)(`button`,{className:`btn-close`,onClick:()=>s(!1),children:`×`})]}),(0,b.jsxs)(`form`,{onSubmit:E,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{children:`Selected Directory / File`}),(0,b.jsxs)(`div`,{className:`path-input-row`,children:[(0,b.jsx)(`input`,{type:`text`,readOnly:!0,required:!0,placeholder:`Browse workspace directories...`,value:u}),(0,b.jsxs)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:()=>O(C?.current_path||`/`),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-magnifying-glass`}),` Browse`]})]})]}),(0,b.jsxs)(`div`,{className:`form-row`,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`path-repo-alias`,children:`Repo / Vault Alias`}),(0,b.jsx)(`input`,{type:`text`,id:`path-repo-alias`,placeholder:`local`,value:f,onChange:e=>p(e.target.value)})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`path-category`,children:`Category Override`}),(0,b.jsx)(`input`,{type:`text`,id:`path-category`,placeholder:`Optional category`,value:m,onChange:e=>h(e.target.value)})]})]}),(0,b.jsxs)(`div`,{className:`form-row`,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`path-type`,children:`Path Type`}),(0,b.jsxs)(`select`,{id:`path-type`,value:g,onChange:e=>v(e.target.value),children:[(0,b.jsx)(`option`,{value:`directory`,children:`Directory`}),(0,b.jsx)(`option`,{value:`file`,children:`Single File`})]})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`path-recursive`,children:`Scan Subfolders`}),(0,b.jsxs)(`select`,{id:`path-recursive`,value:y,onChange:e=>x(parseInt(e.target.value)),children:[(0,b.jsx)(`option`,{value:1,children:`Yes (Recursive)`}),(0,b.jsx)(`option`,{value:0,children:`No (Top-level only)`})]})]})]}),(0,b.jsxs)(`div`,{className:`modal-footer`,children:[(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:()=>s(!1),children:`Cancel`}),(0,b.jsx)(`button`,{type:`submit`,className:`btn btn-primary`,children:`Save Path`})]})]})]})}),c&&C&&(0,b.jsx)(`div`,{className:`modal-backdrop`,style:{zIndex:1100},children:(0,b.jsxs)(`div`,{className:`glass-card modal-card browser-card`,children:[(0,b.jsxs)(`div`,{className:`modal-header`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-search`}),` Browse Workspace Files`]}),(0,b.jsx)(`button`,{className:`btn-close`,onClick:()=>l(!1),children:`×`})]}),(0,b.jsxs)(`div`,{className:`browser-body`,children:[(0,b.jsxs)(`div`,{className:`browser-breadcrumbs`,children:[(0,b.jsx)(`span`,{className:`label`,children:`Current: `}),(0,b.jsx)(`span`,{className:`code`,style:{wordBreak:`break-all`},children:C.current_path})]}),(0,b.jsx)(`div`,{className:`browser-list-container`,children:(0,b.jsxs)(`ul`,{className:`browser-list`,children:[C.parent_path&&(0,b.jsxs)(`li`,{className:`browser-item`,onClick:()=>k(C.parent_path),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-level-up-alt`,style:{color:`var(--accent)`}}),` `,(0,b.jsx)(`span`,{children:`.. (Parent Directory)`})]}),C.directories.map(e=>(0,b.jsxs)(`li`,{className:`browser-item`,onClick:()=>k(e.path),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-folder`,style:{color:`#fbbf24`}}),` `,(0,b.jsx)(`span`,{style:{wordBreak:`break-all`},children:e.name})]},e.path)),C.files.map(e=>(0,b.jsxs)(`li`,{className:`browser-item`,onClick:()=>{d(e.path),v(`file`),l(!1)},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-code`,style:{color:`var(--text-muted)`}}),` `,(0,b.jsx)(`span`,{style:{wordBreak:`break-all`},children:e.name})]},e.path))]})})]}),(0,b.jsxs)(`div`,{className:`modal-footer`,children:[(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:()=>l(!1),children:`Cancel`}),(0,b.jsx)(`button`,{type:`button`,className:`btn btn-primary`,onClick:()=>{C&&(d(C.current_path),v(`directory`),l(!1))},children:`Select Current Folder`})]})]})})]})}function re(){let e=S(),[t,n]=(0,_.useState)(``),[r,i]=(0,_.useState)(`code`),[a,o]=(0,_.useState)(``),[s,c]=(0,_.useState)(!1),[l,u]=(0,_.useState)(null),[d,f]=(0,_.useState)(null);return(0,b.jsx)(`div`,{className:`tab-content active`,children:(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-magnifying-glass`}),` Live Hybrid Search Inspector`]}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Test hybrid search results across code and documentation directly from the browser.`}),(0,b.jsx)(`form`,{onSubmit:async n=>{n.preventDefault(),c(!0),f(null),u(null);try{let e=await fetch(`/admin/api/search/test`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({query:t.trim(),type:r,repo:a.trim()||null})}),n=await e.json();if(!e.ok)throw Error(n.error||`Search failed`);u(n.results||[])}catch(t){f(t.message),e.error(`Search failed: `+t.message)}finally{c(!1)}},style:{marginTop:`16px`},children:(0,b.jsxs)(`div`,{className:`form-row`,children:[(0,b.jsxs)(`div`,{className:`form-group`,style:{flex:2},children:[(0,b.jsx)(`label`,{children:`Search Query`}),(0,b.jsx)(`input`,{type:`text`,required:!0,placeholder:`e.g. JWT token authentication or chunk_markdown`,value:t,onChange:e=>n(e.target.value)})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{children:`Target Type`}),(0,b.jsxs)(`select`,{value:r,onChange:e=>i(e.target.value),children:[(0,b.jsx)(`option`,{value:`code`,children:`Code Snippets & Symbols`}),(0,b.jsx)(`option`,{value:`doc`,children:`Documentation & Notes`})]})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{children:`Repo Filter (Optional)`}),(0,b.jsx)(`input`,{type:`text`,placeholder:`All Repos`,value:a,onChange:e=>o(e.target.value)})]}),(0,b.jsx)(`div`,{className:`form-group search-form-btn-group`,style:{alignSelf:`flex-end`},children:(0,b.jsx)(`button`,{type:`submit`,className:`btn btn-primary`,disabled:s,children:s?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Searching...`]}):(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-play`}),` Search`]})})})]})}),(0,b.jsxs)(`div`,{style:{marginTop:`20px`},children:[s&&(0,b.jsx)(`div`,{className:`empty-state`,children:`Running hybrid retrieval with Normalized Weighted Fusion...`}),d&&(0,b.jsxs)(`div`,{className:`empty-state`,style:{color:`var(--danger)`},children:[`Search error: `,d]}),!s&&!d&&l===null&&(0,b.jsx)(`div`,{className:`empty-state`,children:`Enter a query above to test hybrid retrieval.`}),!s&&!d&&l!==null&&l.length===0&&(0,b.jsx)(`div`,{className:`empty-state`,children:`No matching results found in index.`}),!s&&!d&&l!==null&&l.length>0&&l.map((e,t)=>{let n=e.payload;return(0,b.jsxs)(`div`,{className:`search-hit-card`,children:[(0,b.jsxs)(`div`,{className:`search-hit-header`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,flexWrap:`wrap`,alignItems:`center`,gap:`6px`},children:[(0,b.jsx)(`span`,{className:`badge badge-primary`,children:n.repo}),(0,b.jsx)(`strong`,{children:n.rel_path}),n.symbol&&(0,b.jsx)(`span`,{className:`badge badge-accent`,children:n.symbol}),(0,b.jsxs)(`span`,{className:`text-muted`,style:{fontSize:`0.8rem`},children:[`(Lines `,n.start_line,`-`,n.end_line,`)`]}),n.github_url&&(()=>{let e=``;try{e=new URL(n.github_url).hostname.toLowerCase()}catch{}let t=n.github_url.toLowerCase(),r=`View Source`,i=`fa-solid fa-code-branch`;return e===`gitlab.com`||e.endsWith(`.gitlab.com`)||t.includes(`/-/blob/`)?(r=`View on GitLab`,i=`fa-brands fa-gitlab`):e.includes(`gitea`)||e.includes(`forgejo`)?(r=`View on Gitea`,i=`fa-solid fa-mug-hot`):e===`bitbucket.org`||e.endsWith(`.bitbucket.org`)?(r=`View on Bitbucket`,i=`fa-brands fa-bitbucket`):(e===`github.com`||e.endsWith(`.github.com`))&&(r=`View on GitHub`,i=`fa-brands fa-github`),(0,b.jsxs)(`a`,{href:n.github_url,target:`_blank`,rel:`noreferrer`,style:{color:`var(--primary)`,fontSize:`0.8rem`},children:[(0,b.jsx)(`i`,{className:i,style:{marginRight:`4px`}}),r]})})()]}),(0,b.jsx)(`div`,{children:(0,b.jsxs)(`span`,{className:`badge badge-success`,children:[`Score: `,(e.score*100).toFixed(1),`% (`,e.score.toFixed(4),`)`]})})]}),(0,b.jsx)(`pre`,{className:`search-hit-code`,children:n.content})]},t)})]})]})})}function j({vectorStore:e,isLoadingVs:t,testFeedback:n,vsProvider:r,vsMode:i,vsStoragePath:a,setVsStoragePath:o,vsUrl:s,setVsUrl:c,vsCollection:l,setVsCollection:u,isTestingVs:d,isSwitchingVs:f,onProviderChange:p,onModeChange:m,onTestConnection:h,onSwitchBackend:g}){return(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-database`}),` Vector Database Engine`]}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Configure and switch between vector database backends (Qdrant & ChromaDB). Switching backends updates system metadata and triggers full re-indexing.`}),(0,b.jsxs)(`div`,{className:`vs-config-layout`,children:[(0,b.jsxs)(`div`,{className:`vs-box`,children:[(0,b.jsxs)(`h3`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-circle-nodes`}),` Active Vector Backend`]}),t&&!e?(0,b.jsx)(`p`,{className:`text-muted`,children:`Loading vector store configuration...`}):e?(0,b.jsxs)(`div`,{className:`specs-list`,style:{marginTop:0},children:[(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Provider:`}),(0,b.jsx)(`span`,{className:`badge badge-accent`,children:e.provider===`chroma`?`ChromaDB`:`Qdrant`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Operating Mode:`}),(0,b.jsx)(`span`,{className:`badge badge-primary`,children:e.mode===`embedded`?`Embedded Disk`:`Remote Server`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:e.mode===`embedded`?`Storage Path:`:`Server URL:`}),(0,b.jsx)(`code`,{children:e.mode===`embedded`?e.storage_path||`data/qdrant_db`:e.url||`http://localhost:6333`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Collection Name:`}),(0,b.jsx)(`code`,{children:e.collection||`knowledge_rag_v1`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Points Count:`}),(0,b.jsx)(`span`,{style:{fontSize:`0.9rem`,fontWeight:600},children:(e.points_count||0).toLocaleString()})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Health Status:`}),e.healthy?(0,b.jsxs)(`span`,{className:`badge badge-success`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-circle-check`}),` Healthy`]}):(0,b.jsxs)(`span`,{className:`badge badge-danger`,title:e.health_message||`Unhealthy`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-triangle-exclamation`}),` `,e.health_message?e.health_message.length>35?e.health_message.slice(0,35)+`...`:e.health_message:`Unhealthy`]})]})]}):(0,b.jsx)(`p`,{className:`text-muted`,children:`No vector store configuration found.`})]}),(0,b.jsxs)(`div`,{className:`vs-box`,children:[(0,b.jsxs)(`h3`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-sliders`}),` Configure & Switch Backend`]}),n&&(0,b.jsxs)(`div`,{className:`vs-feedback-banner ${n.success?`feedback-success`:`feedback-error`}`,children:[(0,b.jsx)(`i`,{className:n.success?`fa-solid fa-circle-check`:`fa-solid fa-circle-exclamation`}),(0,b.jsx)(`span`,{children:n.message})]}),(0,b.jsxs)(`form`,{onSubmit:e=>{e.preventDefault(),g()},children:[(0,b.jsxs)(`div`,{className:`form-row`,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`vs-provider`,children:`Vector Store Provider`}),(0,b.jsxs)(`select`,{id:`vs-provider`,value:r,onChange:e=>p(e.target.value),children:[(0,b.jsx)(`option`,{value:`qdrant`,children:`Qdrant (Hybrid Dense + BM25)`}),(0,b.jsx)(`option`,{value:`chroma`,children:`ChromaDB (Dense Vectors)`})]})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`vs-mode`,children:`Operating Mode`}),(0,b.jsxs)(`select`,{id:`vs-mode`,value:i,onChange:e=>m(e.target.value),children:[(0,b.jsx)(`option`,{value:`embedded`,children:`Embedded Disk Storage`}),(0,b.jsx)(`option`,{value:`remote`,children:`Remote Server URL`})]})]})]}),i===`embedded`?(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`vs-storage-path`,children:`Storage Directory Path`}),(0,b.jsx)(`input`,{id:`vs-storage-path`,type:`text`,value:a,onChange:e=>o(e.target.value),placeholder:r===`chroma`?`data/chroma_db`:`data/qdrant_db`})]}):(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`vs-url`,children:`Remote Server URL`}),(0,b.jsx)(`input`,{id:`vs-url`,type:`text`,value:s,onChange:e=>c(e.target.value),placeholder:r===`chroma`?`http://localhost:8000`:`http://localhost:6333`})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`vs-collection`,children:`Collection Name`}),(0,b.jsx)(`input`,{id:`vs-collection`,type:`text`,value:l,onChange:e=>u(e.target.value),placeholder:`knowledge_rag_v1`})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`10px`,marginTop:`16px`,flexWrap:`wrap`},children:[(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:h,disabled:d||f,children:d?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Testing Connection...`]}):(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-plug`}),` Test Connection`]})}),(0,b.jsx)(`button`,{type:`button`,className:`btn btn-primary`,onClick:g,disabled:d||f,children:f?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Switching Backend...`]}):(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-arrows-rotate`}),` Save & Switch Backend`]})})]})]})]})]})]})}function M({embeddingConfig:e,isLoadingEmb:t,isSavingEmb:n,embProvider:r,setEmbProvider:i,embThreads:a,setEmbThreads:o,embBatchSize:s,setEmbBatchSize:c,embDenseModel:l,setEmbDenseModel:u,embSparseModel:d,setEmbSparseModel:f,embLitellmUrl:p,setEmbLitellmUrl:m,embLitellmApiKey:h=``,setEmbLitellmApiKey:g,embVisionOcrModel:v=`gemini-2.5-flash`,setEmbVisionOcrModel:y,embChatModel:x=`gemini-2.5-flash`,setEmbChatModel:S,discoveryResult:C=null,isDiscovering:w=!1,onDiscoverModels:T,onSaveEmbeddingSettings:E}){let D=e?.system_cpus||2,O=e?.system_memory_gb||4,[k,ee]=(0,_.useState)(!1),[A,te]=(0,_.useState)(!1),[ne,re]=(0,_.useState)(!1),j=C?.embedding_models||[],M=C?.vision_models||[],N=C?.chat_models||[];return(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-microchip`}),` Embedding Engine & Resource Limits`]}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Configure local FastEmbed (ONNX) resource limits, CPU thread concurrency, or remote LiteLLM endpoints with dynamic model discovery for embeddings, Vision AI OCR, and chat synthesis.`}),(0,b.jsxs)(`div`,{className:`vs-config-layout`,children:[(0,b.jsxs)(`div`,{className:`vs-box`,children:[(0,b.jsxs)(`h3`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-gauge-high`}),` Active Embedding Engine`]}),t&&!e?(0,b.jsx)(`p`,{className:`text-muted`,children:`Loading embedding configuration...`}):e?(0,b.jsxs)(`div`,{className:`specs-list`,style:{marginTop:0},children:[(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Execution Provider:`}),(0,b.jsx)(`span`,{className:`badge badge-accent`,children:e.provider===`api`?`Remote API (LiteLLM)`:`Local FastEmbed (ONNX)`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`CPU Threads Allocation:`}),(0,b.jsxs)(`span`,{className:`badge badge-primary`,children:[e.threads,` `,e.threads===1?`Core`:`Cores`,` (of `,D,` detected)`]})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Batch Processing Size:`}),(0,b.jsxs)(`span`,{style:{fontSize:`0.9rem`,fontWeight:600},children:[e.batch_size,` chunks/batch`]})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`System RAM Capacity:`}),(0,b.jsxs)(`span`,{style:{fontSize:`0.9rem`,fontWeight:600},children:[O,` GB`]})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Dense Model:`}),(0,b.jsx)(`code`,{children:e.dense_model||`BAAI/bge-small-en-v1.5`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Sparse Model (BM25):`}),(0,b.jsx)(`code`,{children:e.sparse_model||`Qdrant/bm25`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Vision AI OCR Model:`}),(0,b.jsx)(`code`,{children:e.vision_ocr_model||`gemini-2.5-flash`})]}),(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`Chat Completion Model:`}),(0,b.jsx)(`code`,{children:e.chat_model||`gemini-2.5-flash`})]}),e.provider===`api`&&(0,b.jsxs)(`div`,{className:`spec-row`,children:[(0,b.jsx)(`span`,{children:`API Endpoint URL:`}),(0,b.jsx)(`code`,{children:e.litellm_url||`http://litellm:4000/v1`})]})]}):(0,b.jsx)(`p`,{className:`text-muted`,children:`No embedding configuration found.`})]}),(0,b.jsxs)(`div`,{className:`vs-box`,children:[(0,b.jsxs)(`h3`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-sliders`}),` Configure Models & Resources`]}),(0,b.jsxs)(`form`,{onSubmit:E,children:[(0,b.jsxs)(`div`,{className:`form-row`,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`emb-provider`,children:`Embedding Provider`}),(0,b.jsxs)(`select`,{id:`emb-provider`,value:r,onChange:e=>i(e.target.value),children:[(0,b.jsx)(`option`,{value:`local`,children:`Local Model (FastEmbed / ONNX Runtime)`}),(0,b.jsx)(`option`,{value:`api`,children:`API Endpoint (LiteLLM / OpenAI Compatible)`})]})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`emb-threads`,children:`CPU Thread Cap`}),(0,b.jsx)(`input`,{id:`emb-threads`,type:`number`,min:`1`,max:Math.max(D,128),value:a,onChange:e=>o(Math.max(1,parseInt(e.target.value)||1)),placeholder:`2`}),(0,b.jsx)(`span`,{className:`text-muted`,style:{fontSize:`0.75rem`,marginTop:`2px`,display:`block`},children:`Recommended: 2 cores. Prevents container CPU spikes.`})]})]}),(0,b.jsxs)(`div`,{className:`form-row`,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`emb-batch-size`,children:`Embedding Batch Size`}),(0,b.jsxs)(`select`,{id:`emb-batch-size`,value:s,onChange:e=>c(parseInt(e.target.value)||32),children:[(0,b.jsx)(`option`,{value:`16`,children:`16 chunks (Lowest RAM footprint)`}),(0,b.jsx)(`option`,{value:`32`,children:`32 chunks (Balanced Default)`}),(0,b.jsx)(`option`,{value:`64`,children:`64 chunks (High throughput)`}),(0,b.jsx)(`option`,{value:`128`,children:`128 chunks (Large RAM environments)`}),(0,b.jsx)(`option`,{value:`256`,children:`256 chunks (FastEmbed default)`})]})]}),r===`api`?(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`emb-litellm-url`,children:`API Endpoint URL (LiteLLM)`}),(0,b.jsx)(`input`,{id:`emb-litellm-url`,type:`text`,value:p,onChange:e=>m(e.target.value),placeholder:`http://litellm:4000/v1`})]}):(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`emb-dense-model`,children:`Dense Model Name`}),(0,b.jsx)(`input`,{id:`emb-dense-model`,type:`text`,value:l,onChange:e=>u(e.target.value),placeholder:`BAAI/bge-small-en-v1.5`})]})]}),r===`api`&&(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`div`,{className:`form-row`,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`emb-litellm-api-key`,children:`LiteLLM API Key (Optional / Bearer)`}),(0,b.jsx)(`input`,{id:`emb-litellm-api-key`,type:`password`,value:h,onChange:e=>g?.(e.target.value),placeholder:`sk-...`,autoComplete:`off`})]}),(0,b.jsx)(`div`,{className:`form-group`,style:{display:`flex`,flexDirection:`column`,justifyContent:`flex-end`},children:(0,b.jsx)(`button`,{type:`button`,id:`btn-discover-models`,className:`btn btn-secondary`,onClick:T,disabled:w,style:{height:`38px`,whiteSpace:`nowrap`},children:w?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Discovering...`]}):(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-bolt`}),` Discover Models`]})})})]}),C&&(0,b.jsx)(`div`,{style:{marginBottom:`16px`},children:C.status===`success`?(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`,padding:`8px 12px`,background:`rgba(16, 185, 129, 0.1)`,border:`1px solid rgba(16, 185, 129, 0.3)`,borderRadius:`6px`,fontSize:`0.85rem`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-circle-check`,style:{color:`#10b981`}}),(0,b.jsxs)(`span`,{children:[`Connected to LiteLLM — `,(0,b.jsxs)(`strong`,{children:[C.total_models,` models available`]}),` (`,j.length,` embedding, `,M.length,` vision, `,N.length,` chat)`]})]}):(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`,padding:`8px 12px`,background:`rgba(239, 68, 68, 0.1)`,border:`1px solid rgba(239, 68, 68, 0.3)`,borderRadius:`6px`,fontSize:`0.85rem`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-triangle-exclamation`,style:{color:`#ef4444`}}),(0,b.jsx)(`span`,{children:C.message||`Could not connect to LiteLLM endpoint`})]})}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,marginBottom:`4px`},children:[(0,b.jsx)(`label`,{htmlFor:`emb-dense-model`,style:{marginBottom:0},children:`Dense Embedding Model`}),j.length>0&&(0,b.jsx)(`button`,{type:`button`,className:`btn-link`,onClick:()=>ee(!k),style:{fontSize:`0.75rem`,background:`none`,border:`none`,color:`#38bdf8`,cursor:`pointer`,padding:0},children:k?`← Select from discovered`:`Enter custom model →`})]}),!k&&j.length>0?(0,b.jsxs)(`select`,{id:`emb-dense-model`,value:l,onChange:e=>{e.target.value===`__custom__`?ee(!0):u(e.target.value)},children:[j.map(e=>(0,b.jsx)(`option`,{value:e,children:e},e)),(0,b.jsx)(`option`,{value:`__custom__`,children:`Custom / Enter manually...`})]}):(0,b.jsx)(`input`,{id:`emb-dense-model`,type:`text`,value:l,onChange:e=>u(e.target.value),placeholder:`gemini-embedding-2`})]})]}),r===`local`&&(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`emb-sparse-model`,children:`Sparse BM25 Model`}),(0,b.jsx)(`input`,{id:`emb-sparse-model`,type:`text`,value:d,onChange:e=>f(e.target.value),placeholder:`Qdrant/bm25`})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,marginBottom:`4px`},children:[(0,b.jsxs)(`label`,{htmlFor:`emb-vision-ocr-model`,style:{marginBottom:0},children:[`Vision AI OCR Model `,(0,b.jsx)(`span`,{className:`text-muted`,style:{fontWeight:400,fontSize:`0.8rem`},children:`(PDF Ingestion Fallback)`})]}),M.length>0&&(0,b.jsx)(`button`,{type:`button`,className:`btn-link`,onClick:()=>te(!A),style:{fontSize:`0.75rem`,background:`none`,border:`none`,color:`#38bdf8`,cursor:`pointer`,padding:0},children:A?`← Select from discovered`:`Enter custom model →`})]}),!A&&M.length>0?(0,b.jsxs)(`select`,{id:`emb-vision-ocr-model`,value:v,onChange:e=>{e.target.value===`__custom__`?te(!0):y?.(e.target.value)},children:[M.map(e=>(0,b.jsx)(`option`,{value:e,children:e},e)),(0,b.jsx)(`option`,{value:`__custom__`,children:`Custom / Enter manually...`})]}):(0,b.jsx)(`input`,{id:`emb-vision-ocr-model`,type:`text`,value:v,onChange:e=>y?.(e.target.value),placeholder:`gemini-2.5-flash`})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,marginBottom:`4px`},children:[(0,b.jsx)(`label`,{htmlFor:`emb-chat-model`,style:{marginBottom:0},children:`General Chat & Synthesis Model`}),N.length>0&&(0,b.jsx)(`button`,{type:`button`,className:`btn-link`,onClick:()=>re(!ne),style:{fontSize:`0.75rem`,background:`none`,border:`none`,color:`#38bdf8`,cursor:`pointer`,padding:0},children:ne?`← Select from discovered`:`Enter custom model →`})]}),!ne&&N.length>0?(0,b.jsxs)(`select`,{id:`emb-chat-model`,value:x,onChange:e=>{e.target.value===`__custom__`?re(!0):S?.(e.target.value)},children:[N.map(e=>(0,b.jsx)(`option`,{value:e,children:e},e)),(0,b.jsx)(`option`,{value:`__custom__`,children:`Custom / Enter manually...`})]}):(0,b.jsx)(`input`,{id:`emb-chat-model`,type:`text`,value:x,onChange:e=>S?.(e.target.value),placeholder:`gemini-2.5-flash`})]}),(0,b.jsx)(`div`,{style:{display:`flex`,gap:`10px`,marginTop:`16px`},children:(0,b.jsx)(`button`,{type:`submit`,className:`btn btn-primary`,disabled:n||t,children:n?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Saving & Applying...`]}):(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-floppy-disk`}),` Save & Apply Embedding Limits & Model Settings`]})})})]})]})]})]})}function N({isLoadingAutoSync:e,intervalMins:t,setIntervalMins:n,hasGlobalSecret:r,showWebhookSecret:i,setShowWebhookSecret:a,webhookSecret:o,setWebhookSecret:s,fullWebhookUrl:c,copiedWebhookUrl:l,isSavingAutoSync:u,onSaveAutoSync:d,onClearWebhookSecret:f,onCopyWebhookUrl:p}){return(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-arrows-rotate`}),` Auto-Sync & Webhooks`]}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Configure scheduled background repository polling interval and global incoming webhook triggers.`}),e&&!t?(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`12px`},children:`Loading auto-sync settings...`}):(0,b.jsxs)(`form`,{onSubmit:d,style:{marginTop:`16px`},children:[(0,b.jsxs)(`div`,{className:`form-row`,children:[(0,b.jsxs)(`div`,{className:`form-group`,style:{flex:1},children:[(0,b.jsx)(`label`,{htmlFor:`auto-sync-interval`,children:`Repository Polling Interval`}),(0,b.jsxs)(`select`,{id:`auto-sync-interval`,value:t,onChange:e=>n(Number(e.target.value)),style:{width:`100%`},children:[(0,b.jsx)(`option`,{value:0,children:`Disabled (0m)`}),(0,b.jsx)(`option`,{value:5,children:`5 minutes`}),(0,b.jsx)(`option`,{value:15,children:`15 minutes (Default)`}),(0,b.jsx)(`option`,{value:30,children:`30 minutes`}),(0,b.jsx)(`option`,{value:60,children:`1 hour`}),(0,b.jsx)(`option`,{value:360,children:`6 hours`}),![0,5,15,30,60,360].includes(t)&&(0,b.jsxs)(`option`,{value:t,children:[t,` minutes (Custom)`]})]})]}),(0,b.jsxs)(`div`,{className:`form-group`,style:{flex:1},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,justifyContent:`space-between`,marginBottom:`6px`},children:[(0,b.jsx)(`label`,{htmlFor:`auto-sync-secret`,style:{marginBottom:0},children:`Global Webhook Secret (HMAC / Token)`}),r?(0,b.jsx)(`span`,{className:`badge badge-accent`,children:`Secret Active`}):(0,b.jsx)(`span`,{className:`badge badge-secondary`,style:{color:`var(--text-muted)`},children:`None`})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`8px`},children:[(0,b.jsx)(`input`,{id:`auto-sync-secret`,type:i?`text`:`password`,value:o,onChange:e=>s(e.target.value),placeholder:r?`Secret configured (enter new to change)`:`Enter webhook secret (optional)`,style:{flex:1}}),(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:()=>a(!i),title:i?`Hide secret`:`Reveal secret`,"aria-label":i?`Hide secret`:`Reveal secret`,style:{minWidth:`42px`,padding:`0 12px`},children:(0,b.jsx)(`i`,{className:`fa-solid ${i?`fa-eye-slash`:`fa-eye`}`})}),(r||o)&&(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:f,title:`Clear secret`,"aria-label":`Clear secret`,disabled:u,style:{padding:`0 12px`},children:`Clear`})]})]})]}),(0,b.jsxs)(`div`,{className:`form-group`,style:{marginTop:`14px`,minWidth:0,width:`100%`},children:[(0,b.jsx)(`label`,{htmlFor:`auto-sync-webhook-url`,children:`Incoming Webhook Payload URL`}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`8px`,minWidth:0,width:`100%`},children:[(0,b.jsx)(`input`,{id:`auto-sync-webhook-url`,type:`text`,readOnly:!0,value:c,style:{fontFamily:`var(--font-family-mono)`,fontSize:`0.85rem`,flex:1,minWidth:0,width:`100%`},"aria-label":`Webhook Payload URL`}),(0,b.jsxs)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:p,style:{minWidth:`95px`},"aria-label":`Copy Webhook URL`,children:[(0,b.jsx)(`i`,{className:`fa-solid ${l?`fa-check`:`fa-copy`}`}),` `,l?`Copied!`:`Copy`]})]}),(0,b.jsx)(`p`,{className:`text-muted`,style:{fontSize:`0.8rem`,marginTop:`6px`},children:`Payload URL for repository push webhooks. Webhooks trigger immediate background synchronization for registered repositories.`})]}),(0,b.jsx)(`div`,{style:{display:`flex`,gap:`10px`,marginTop:`18px`},children:(0,b.jsx)(`button`,{type:`submit`,className:`btn btn-primary`,disabled:u,children:u?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Saving...`]}):(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-floppy-disk`}),` Save Auto-Sync Settings`]})})})]})]})}function P({stats:e,ghAuth:t,glAuth:n,gtAuth:r,ghToken:i,setGhToken:a,glToken:o,setGlToken:s,gtToken:c,setGtToken:l,hostCredentials:u,isHostModalOpen:d,setIsHostModalOpen:f,newHost:p,setNewHost:m,newHostProvider:h,setNewHostProvider:g,newHostUser:_,setNewHostUser:v,newHostToken:y,setNewHostToken:x,isSavingHost:S,onSaveToken:C,onClearToken:w,onSaveHostCredential:T,onDeleteHostCredential:E}){return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-key`}),` Global Git Provider Authentication`]}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Default tokens are automatically applied to repositories matching these providers when no per-repo or host-specific override exists.`}),(0,b.jsxs)(`div`,{style:{display:`grid`,gridTemplateColumns:`repeat(auto-fit, minmax(min(100%, 300px), 1fr))`,gap:`20px`,marginTop:`20px`},children:[(0,b.jsxs)(`div`,{className:`settings-provider-box`,style:{background:`rgba(255,255,255,0.02)`,padding:`16px`,borderRadius:`10px`,border:`1px solid var(--border-card)`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,justifyContent:`space-between`,marginBottom:`12px`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`,fontWeight:600},children:[(0,b.jsx)(`i`,{className:`fa-brands fa-github fa-lg`}),` GitHub`]}),(0,b.jsx)(`span`,{className:`badge badge-accent`,children:t.token_source})]}),(0,b.jsxs)(`div`,{style:{fontSize:`0.8rem`,color:`var(--text-muted)`,marginBottom:`8px`,wordBreak:`break-all`},children:[`Active Token: `,(0,b.jsx)(`code`,{style:{wordBreak:`break-all`},children:t.masked_token})]}),e?.rate_limit&&(0,b.jsxs)(`div`,{style:{fontSize:`0.78rem`,color:`var(--text-muted)`,marginBottom:`12px`},children:[`Rate Limit: `,e.rate_limit.remaining,` / `,e.rate_limit.limit,` requests`]}),(0,b.jsxs)(`form`,{onSubmit:e=>{e.preventDefault(),C(`github_token`,i,`GitHub`)},children:[(0,b.jsx)(`div`,{className:`form-group`,style:{marginBottom:`10px`},children:(0,b.jsx)(`input`,{type:`password`,placeholder:`ghp_xxxxxxxxxxxx`,value:i,onChange:e=>a(e.target.value)})}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`8px`},children:[(0,b.jsx)(`button`,{type:`submit`,className:`btn btn-primary`,style:{padding:`4px 10px`,fontSize:`0.8rem`},children:`Save`}),(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,style:{padding:`4px 10px`,fontSize:`0.8rem`},onClick:()=>w(`github_token`,`GitHub`),children:`Clear`})]})]})]}),(0,b.jsxs)(`div`,{className:`settings-provider-box`,style:{background:`rgba(255,255,255,0.02)`,padding:`16px`,borderRadius:`10px`,border:`1px solid var(--border-card)`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,justifyContent:`space-between`,marginBottom:`12px`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`,fontWeight:600},children:[(0,b.jsx)(`i`,{className:`fa-brands fa-gitlab fa-lg`,style:{color:`#fc6d26`}}),` GitLab (Global)`]}),(0,b.jsx)(`span`,{className:`badge badge-accent`,children:n.token_source})]}),(0,b.jsxs)(`div`,{style:{fontSize:`0.8rem`,color:`var(--text-muted)`,marginBottom:`16px`,wordBreak:`break-all`},children:[`Active Token: `,(0,b.jsx)(`code`,{style:{wordBreak:`break-all`},children:n.masked_token})]}),(0,b.jsxs)(`form`,{onSubmit:e=>{e.preventDefault(),C(`gitlab_token`,o,`GitLab`)},children:[(0,b.jsx)(`div`,{className:`form-group`,style:{marginBottom:`10px`},children:(0,b.jsx)(`input`,{type:`password`,placeholder:`glpat-xxxxxxxxxxxx`,value:o,onChange:e=>s(e.target.value)})}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`8px`},children:[(0,b.jsx)(`button`,{type:`submit`,className:`btn btn-primary`,style:{padding:`4px 10px`,fontSize:`0.8rem`},children:`Save`}),(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,style:{padding:`4px 10px`,fontSize:`0.8rem`},onClick:()=>w(`gitlab_token`,`GitLab`),children:`Clear`})]})]})]}),(0,b.jsxs)(`div`,{className:`settings-provider-box`,style:{background:`rgba(255,255,255,0.02)`,padding:`16px`,borderRadius:`10px`,border:`1px solid var(--border-card)`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,justifyContent:`space-between`,marginBottom:`12px`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`,fontWeight:600},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-mug-hot fa-lg`,style:{color:`#609926`}}),` Gitea / Forgejo`]}),(0,b.jsx)(`span`,{className:`badge badge-accent`,children:r.token_source})]}),(0,b.jsxs)(`div`,{style:{fontSize:`0.8rem`,color:`var(--text-muted)`,marginBottom:`16px`,wordBreak:`break-all`},children:[`Active Token: `,(0,b.jsx)(`code`,{style:{wordBreak:`break-all`},children:r.masked_token})]}),(0,b.jsxs)(`form`,{onSubmit:e=>{e.preventDefault(),C(`gitea_token`,c,`Gitea`)},children:[(0,b.jsx)(`div`,{className:`form-group`,style:{marginBottom:`10px`},children:(0,b.jsx)(`input`,{type:`password`,placeholder:`Token / Personal Token`,value:c,onChange:e=>l(e.target.value)})}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`8px`},children:[(0,b.jsx)(`button`,{type:`submit`,className:`btn btn-primary`,style:{padding:`4px 10px`,fontSize:`0.8rem`},children:`Save`}),(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,style:{padding:`4px 10px`,fontSize:`0.8rem`},onClick:()=>w(`gitea_token`,`Gitea`),children:`Clear`})]})]})]})]})]}),(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`div`,{className:`card-header-btn`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-shield-halved`}),` Custom & Self-Hosted Git Host Vault`]}),(0,b.jsxs)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:[`Define credentials for self-hosted GitLab Enterprise, Gitea, or custom servers (e.g. `,(0,b.jsx)(`code`,{children:`gitlab.mycorp.com`}),` or `,(0,b.jsx)(`code`,{children:`git.lan:3000`}),`).`]})]}),(0,b.jsxs)(`button`,{className:`btn btn-primary`,onClick:()=>f(!0),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-plus`}),` Add Host Credential`]})]}),(0,b.jsx)(`div`,{className:`table-container desktop-table-view`,style:{marginTop:`16px`},children:(0,b.jsxs)(`table`,{children:[(0,b.jsx)(`thead`,{children:(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`th`,{children:`Host Domain / Address`}),(0,b.jsx)(`th`,{children:`Provider Type`}),(0,b.jsx)(`th`,{children:`Auth User`}),(0,b.jsx)(`th`,{children:`Masked Token`}),(0,b.jsx)(`th`,{children:`Added At`}),(0,b.jsx)(`th`,{children:`Actions`})]})}),(0,b.jsx)(`tbody`,{children:u.length===0?(0,b.jsx)(`tr`,{children:(0,b.jsx)(`td`,{colSpan:6,className:`empty-state`,children:`No custom host credentials configured. Add a host domain to authenticate self-hosted instances.`})}):u.map(e=>(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`td`,{children:(0,b.jsx)(`code`,{children:e.host})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`span`,{className:`badge badge-accent`,children:e.provider.toUpperCase()})}),(0,b.jsx)(`td`,{children:e.auth_user?(0,b.jsx)(`code`,{children:e.auth_user}):(0,b.jsx)(`span`,{className:`text-muted`,children:`Default`})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`code`,{style:{wordBreak:`break-all`},children:e.masked_token})}),(0,b.jsx)(`td`,{style:{fontSize:`0.8rem`,color:`var(--text-muted)`},children:e.added_at}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`button`,{className:`btn-icon btn-delete`,onClick:()=>E(e.id,e.host),title:`Delete Credential`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-trash-can`})})})]},e.id))})]})}),(0,b.jsx)(`div`,{className:`mobile-card-list`,children:u.length===0?(0,b.jsx)(`div`,{className:`empty-state`,children:`No custom host credentials configured. Add a host domain to authenticate self-hosted instances.`}):u.map(e=>(0,b.jsxs)(`div`,{className:`data-mobile-card`,children:[(0,b.jsxs)(`div`,{className:`data-mobile-card-header`,children:[(0,b.jsx)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`},children:(0,b.jsx)(`code`,{style:{fontSize:`0.95rem`},children:e.host})}),(0,b.jsx)(`span`,{className:`badge badge-accent`,children:e.provider.toUpperCase()})]}),(0,b.jsxs)(`div`,{className:`data-mobile-card-body`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Auth User: `}),e.auth_user?(0,b.jsx)(`code`,{children:e.auth_user}):(0,b.jsx)(`span`,{className:`text-muted`,children:`Default`})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Masked Token: `}),(0,b.jsx)(`code`,{style:{wordBreak:`break-all`},children:e.masked_token})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Added At: `}),(0,b.jsx)(`span`,{style:{color:`var(--text-muted)`},children:e.added_at})]})]}),(0,b.jsx)(`div`,{className:`data-mobile-card-actions`,children:(0,b.jsxs)(`button`,{className:`btn btn-secondary btn-delete`,onClick:()=>E(e.id,e.host),title:`Delete Credential`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-trash-can`}),` Delete`]})})]},`card-${e.id}`))})]}),d&&(0,b.jsx)(`div`,{className:`modal-backdrop`,children:(0,b.jsxs)(`div`,{className:`glass-card modal-card`,children:[(0,b.jsxs)(`div`,{className:`modal-header`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-shield-halved`}),` Add Host Credential`]}),(0,b.jsx)(`button`,{className:`btn-close`,onClick:()=>f(!1),children:`×`})]}),(0,b.jsxs)(`form`,{onSubmit:T,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`host-domain`,children:`Host Domain / IP`}),(0,b.jsx)(`input`,{type:`text`,id:`host-domain`,required:!0,placeholder:`e.g. gitlab.mycorp.internal or git.lan:3000`,value:p,onChange:e=>m(e.target.value)})]}),(0,b.jsxs)(`div`,{className:`form-row`,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`host-provider`,children:`Provider Type`}),(0,b.jsxs)(`select`,{id:`host-provider`,value:h,onChange:e=>g(e.target.value),children:[(0,b.jsx)(`option`,{value:`gitlab`,children:`GitLab Enterprise / Self-Hosted`}),(0,b.jsx)(`option`,{value:`gitea`,children:`Gitea / Forgejo`}),(0,b.jsx)(`option`,{value:`github`,children:`GitHub Enterprise`}),(0,b.jsx)(`option`,{value:`bitbucket`,children:`Bitbucket Server / Cloud`}),(0,b.jsx)(`option`,{value:`generic`,children:`Generic Git (HTTP / HTTPS)`})]})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`host-user`,children:`Auth User (Optional)`}),(0,b.jsx)(`input`,{type:`text`,id:`host-user`,placeholder:`e.g. oauth2 or gitlab-ci-token`,value:_,onChange:e=>v(e.target.value)})]})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`host-token`,children:`Personal Access Token / Password`}),(0,b.jsx)(`input`,{type:`password`,id:`host-token`,required:!0,placeholder:`Token or password`,value:y,onChange:e=>x(e.target.value)})]}),(0,b.jsxs)(`div`,{className:`modal-footer`,children:[(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:()=>f(!1),children:`Cancel`}),(0,b.jsx)(`button`,{type:`submit`,className:`btn btn-primary`,disabled:S,children:S?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Saving...`]}):`Save Host Credential`})]})]})]})})]})}var ie=`contextcortex_theme`,ae=`deep-ocean`,F=[{id:`deep-ocean`,name:`Deep Ocean`,mode:`dark`,description:`Monochromatic petrol slate with vibrant cyan and mint accents.`,swatches:[`#07181b`,`#0d2c2f`,`#0891b2`,`#2dd4bf`]},{id:`midnight-blue`,name:`Midnight Blue`,mode:`dark`,description:`Classic deep space navy with cobalt blue and teal highlights.`,swatches:[`#0a0f1d`,`#121a2f`,`#3b82f6`,`#14b8a6`]},{id:`lavender-haze`,name:`Lavender Haze`,mode:`light`,description:`Elegant soft violet canvas with vibrant purple, fuchsia accents, and plum text.`,swatches:[`#f5f3ff`,`#faf8ff`,`#7c3aed`,`#ec4899`]},{id:`amber-warmth`,name:`Amber Warmth`,mode:`light`,description:`Warm sandstone canvas with terracotta orange, amber accents, and espresso text.`,swatches:[`#fdf8f4`,`#ffffff`,`#ea580c`,`#d97706`]}];function I(){if(typeof window>`u`)return ae;try{let e=localStorage.getItem(ie);if(e===`arctic-frost`||e===`aqua-breeze`)return`lavender-haze`;if(e===`solar-daybreak`||e===`azure-daylight`)return`amber-warmth`;if(e&&F.some(t=>t.id===e))return e}catch{}return ae}function L(e){if(!(typeof document>`u`)){document.documentElement.setAttribute(`data-theme`,e);try{localStorage.setItem(ie,e)}catch{}}}function oe(){let[e,t]=(0,_.useState)(`deep-ocean`),n=S();(0,_.useEffect)(()=>{t(I())},[]);let r=(e,r)=>{L(e),t(e),n.success(`Switched theme to ${r}`)};return(0,b.jsxs)(`section`,{className:`settings-section glass-card`,"aria-labelledby":`theme-settings-heading`,children:[(0,b.jsxs)(`div`,{className:`section-header`,children:[(0,b.jsx)(`div`,{className:`header-icon`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-palette`})}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`h2`,{id:`theme-settings-heading`,style:{fontSize:`1.1rem`,fontWeight:600},children:`Appearance & Theme`}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Choose your preferred color scheme. The selected theme will persist across browser sessions.`})]})]}),(0,b.jsx)(`div`,{className:`theme-options-grid`,role:`radiogroup`,"aria-label":`Appearance Themes`,children:F.map(t=>{let n=e===t.id;return(0,b.jsxs)(`button`,{type:`button`,className:`theme-card ${n?`active`:``}`,onClick:()=>r(t.id,t.name),role:`radio`,"aria-checked":n,"aria-label":`Select ${t.name} theme`,children:[(0,b.jsxs)(`div`,{className:`theme-card-header`,children:[(0,b.jsxs)(`div`,{className:`theme-card-title`,children:[(0,b.jsx)(`span`,{children:t.name}),(0,b.jsxs)(`span`,{className:`badge`,style:{fontSize:`0.68rem`,fontWeight:500,opacity:.85,padding:`2px 6px`},children:[(0,b.jsx)(`i`,{className:`fa-solid ${t.mode===`dark`?`fa-moon`:`fa-sun`}`,style:{marginRight:`3px`}}),t.mode===`dark`?`Dark`:`Light`]})]}),n?(0,b.jsxs)(`span`,{className:`badge badge-accent`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-check`,style:{marginRight:`4px`}}),` Active`]}):(0,b.jsx)(`span`,{className:`badge badge-primary`,children:`Select`})]}),(0,b.jsx)(`div`,{className:`theme-card-desc`,children:t.description}),(0,b.jsx)(`div`,{className:`theme-swatches`,"aria-hidden":`true`,children:t.swatches.map((e,t)=>(0,b.jsx)(`span`,{className:`theme-swatch`,style:{backgroundColor:e},title:e},t))})]},t.id)})})]})}function se(){let[e,t]=(0,_.useState)({summary_enabled:!0,summary_threshold_kb:500,summary_max_file_size_mb:10,read_file_max_lines:2e3,summary_chat_model:`gemini-2.5-flash`}),[n,r]=(0,_.useState)(!1),[i,a]=(0,_.useState)(!1),o=S(),s=async()=>{r(!0);try{let e=await fetch(`/admin/api/settings/files`);if(e.ok){let n=await e.json();t({summary_enabled:n.summary_enabled!==!1,summary_threshold_kb:n.summary_threshold_kb??500,summary_max_file_size_mb:n.summary_max_file_size_mb??10,read_file_max_lines:n.read_file_max_lines??2e3,summary_chat_model:n.summary_chat_model||`gemini-2.5-flash`})}}catch(e){console.error(`Failed to load file settings:`,e)}finally{r(!1)}};return(0,_.useEffect)(()=>{s()},[]),(0,b.jsxs)(`section`,{className:`settings-section glass-card`,"aria-labelledby":`file-settings-heading`,children:[(0,b.jsxs)(`div`,{className:`section-header`,children:[(0,b.jsx)(`div`,{className:`header-icon`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-file-lines`})}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`h2`,{id:`file-settings-heading`,style:{fontSize:`1.1rem`,fontWeight:600},children:`Files & Large File Summarization`}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Configure retrieval ceilings for AI agents and automatic LLM summarization thresholds for large files in watched folders and uploads.`})]})]}),n?(0,b.jsxs)(`div`,{style:{padding:`24px`,textAlign:`center`},className:`text-muted`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`,style:{marginRight:`8px`}}),` Loading settings...`]}):(0,b.jsx)(`form`,{onSubmit:async n=>{n.preventDefault(),a(!0);try{let n=await fetch(`/admin/api/settings/files`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)});if(n.ok){let e=await n.json();t(e),o.success(`File and summarization settings updated successfully`)}else{let e=await n.json();o.error(e.error||`Failed to save settings`)}}catch(e){o.error(`Save failed: ${e.message}`)}finally{a(!1)}},style:{marginTop:`16px`},children:(0,b.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`16px`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,justifyContent:`space-between`,padding:`12px 16px`,background:`rgba(255,255,255,0.03)`,borderRadius:`8px`},children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`label`,{htmlFor:`summary-enabled-toggle`,style:{fontWeight:600,display:`block`,cursor:`pointer`},children:`Enable Large File Summarization`}),(0,b.jsx)(`span`,{className:`text-muted`,style:{fontSize:`0.8rem`},children:`Automatically generate and vector-embed an executive summary when files exceed the size threshold instead of skipping them.`})]}),(0,b.jsx)(`input`,{id:`summary-enabled-toggle`,type:`checkbox`,checked:e.summary_enabled,onChange:n=>t({...e,summary_enabled:n.target.checked}),style:{width:`18px`,height:`18px`,cursor:`pointer`},"aria-label":`Enable Large File Summarization`})]}),(0,b.jsxs)(`div`,{style:{display:`grid`,gridTemplateColumns:`repeat(auto-fit, minmax(220px, 1fr))`,gap:`16px`},children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`label`,{htmlFor:`summary-threshold-input`,style:{fontSize:`0.85rem`,fontWeight:500,display:`block`,marginBottom:`6px`},children:`Auto-Summarize Threshold (KB)`}),(0,b.jsx)(`input`,{id:`summary-threshold-input`,type:`number`,min:`10`,max:`50000`,value:e.summary_threshold_kb,onChange:n=>t({...e,summary_threshold_kb:Number(n.target.value)}),className:`input-field`,style:{width:`100%`},disabled:!e.summary_enabled}),(0,b.jsx)(`span`,{className:`text-muted`,style:{fontSize:`0.75rem`,marginTop:`4px`,display:`block`},children:`Files larger than this (default: 500 KB) trigger LLM summarization.`})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`label`,{htmlFor:`summary-max-size-input`,style:{fontSize:`0.85rem`,fontWeight:500,display:`block`,marginBottom:`6px`},children:`Max File Size for Summarization (MB)`}),(0,b.jsx)(`input`,{id:`summary-max-size-input`,type:`number`,min:`1`,max:`100`,value:e.summary_max_file_size_mb,onChange:n=>t({...e,summary_max_file_size_mb:Number(n.target.value)}),className:`input-field`,style:{width:`100%`},disabled:!e.summary_enabled}),(0,b.jsx)(`span`,{className:`text-muted`,style:{fontSize:`0.75rem`,marginTop:`4px`,display:`block`},children:`Files larger than this ceiling (default: 10 MB) are skipped to protect memory.`})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`label`,{htmlFor:`read-file-max-lines-input`,style:{fontSize:`0.85rem`,fontWeight:500,display:`block`,marginBottom:`6px`},children:`Max Lines Per Read`}),(0,b.jsx)(`input`,{id:`read-file-max-lines-input`,type:`number`,min:`50`,max:`20000`,value:e.read_file_max_lines,onChange:n=>t({...e,read_file_max_lines:Number(n.target.value)}),className:`input-field`,style:{width:`100%`}}),(0,b.jsxs)(`span`,{className:`text-muted`,style:{fontSize:`0.75rem`,marginTop:`4px`,display:`block`},children:[`Default line ceiling for `,(0,b.jsx)(`code`,{children:`read_file`}),` tool calls (default: 2000 lines).`]})]})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`label`,{htmlFor:`summary-chat-model-input`,style:{fontSize:`0.85rem`,fontWeight:500,display:`block`,marginBottom:`6px`},children:`Summarization Chat Model`}),(0,b.jsx)(`input`,{id:`summary-chat-model-input`,type:`text`,value:e.summary_chat_model,onChange:n=>t({...e,summary_chat_model:n.target.value}),placeholder:`e.g. gemini-2.5-flash or gpt-4o-mini`,className:`input-field`,style:{width:`100%`},disabled:!e.summary_enabled}),(0,b.jsx)(`span`,{className:`text-muted`,style:{fontSize:`0.75rem`,marginTop:`4px`,display:`block`},children:`LiteLLM chat model identifier used for generating file summaries.`})]}),(0,b.jsx)(`div`,{style:{display:`flex`,justifyContent:`flex-end`,marginTop:`8px`},children:(0,b.jsx)(`button`,{type:`submit`,disabled:i,className:`btn btn-primary`,style:{display:`flex`,alignItems:`center`,gap:`8px`},children:i?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Saving...`]}):(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-floppy-disk`}),` Save File Settings`]})})})]})})]})}function ce({stats:e,refreshStats:t}){let[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(``),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)([]),[u,d]=(0,_.useState)(15),[f,p]=(0,_.useState)(``),[m,h]=(0,_.useState)(!1),[g,v]=(0,_.useState)(`/api/webhooks/git`),[y,x]=(0,_.useState)(!1),[C,w]=(0,_.useState)(!1),[T,E]=(0,_.useState)(!1),[D,O]=(0,_.useState)(!1),[k,ee]=(0,_.useState)(!1),[A,te]=(0,_.useState)(``),[ne,re]=(0,_.useState)(`gitlab`),[ie,ae]=(0,_.useState)(``),[F,I]=(0,_.useState)(``),[L,ce]=(0,_.useState)(!1),[le,ue]=(0,_.useState)(null),[de,fe]=(0,_.useState)(!1),[pe,me]=(0,_.useState)(`qdrant`),[he,ge]=(0,_.useState)(`embedded`),[_e,ve]=(0,_.useState)(`data/qdrant_db`),[ye,be]=(0,_.useState)(`http://localhost:6333`),[xe,Se]=(0,_.useState)(`knowledge_rag_v1`),[Ce,we]=(0,_.useState)(!1),[Te,Ee]=(0,_.useState)(null),[De,Oe]=(0,_.useState)(!1),[ke,Ae]=(0,_.useState)(null),[je,Me]=(0,_.useState)(!1),[Ne,Pe]=(0,_.useState)(!1),[Fe,Ie]=(0,_.useState)(`local`),[Le,Re]=(0,_.useState)(2),[ze,Be]=(0,_.useState)(32),[Ve,He]=(0,_.useState)(`BAAI/bge-small-en-v1.5`),[Ue,We]=(0,_.useState)(`Qdrant/bm25`),[Ge,Ke]=(0,_.useState)(`http://litellm:4000/v1`),[qe,Je]=(0,_.useState)(``),[Ye,Xe]=(0,_.useState)(`gemini-2.5-flash`),[Ze,Qe]=(0,_.useState)(`gemini-2.5-flash`),[$e,et]=(0,_.useState)(null),[tt,nt]=(0,_.useState)(!1),R=S(),rt=(0,_.useCallback)(async()=>{try{let e=await fetch(`/admin/api/settings/hosts`);if(e.ok){let t=await e.json();l(Array.isArray(t)?t:[])}}catch(e){console.error(`Failed to load host credentials:`,e),l([])}},[]),it=(0,_.useCallback)(async()=>{fe(!0);try{let e=await fetch(`/admin/api/vector-store`);if(e.ok){let t=await e.json();ue(t),t.provider&&me(t.provider),t.mode&&ge(t.mode),t.storage_path&&ve(t.storage_path),t.url&&be(t.url),t.collection&&Se(t.collection)}}catch(e){console.error(`Failed to load vector store config:`,e)}finally{fe(!1)}},[]),at=(0,_.useCallback)(async()=>{x(!0);try{let e=await fetch(`/admin/api/settings/auto-sync`);if(e.ok){let t=await e.json();typeof t.interval_mins==`number`&&d(t.interval_mins),typeof t.webhook_url==`string`&&v(t.webhook_url),typeof t.has_global_secret==`boolean`&&h(t.has_global_secret)}}catch(e){console.error(`Failed to load auto-sync settings:`,e)}finally{x(!1)}},[]),ot=(0,_.useCallback)(async()=>{Me(!0);try{let e=await fetch(`/admin/api/settings/embedding`);if(e.ok){let t=await e.json();Ae(t),t.provider&&Ie(t.provider),typeof t.threads==`number`&&Re(t.threads),typeof t.batch_size==`number`&&Be(t.batch_size),t.dense_model&&He(t.dense_model),t.sparse_model&&We(t.sparse_model),t.litellm_url&&Ke(t.litellm_url),t.litellm_api_key&&Je(t.litellm_api_key),t.vision_ocr_model&&Xe(t.vision_ocr_model),t.chat_model&&Qe(t.chat_model)}}catch(e){console.error(`Failed to load embedding config:`,e)}finally{Me(!1)}},[]);(0,_.useEffect)(()=>{rt(),it(),at(),ot()},[rt,it,at,ot]),(0,_.useEffect)(()=>{e?.vector_store?(ue(e.vector_store),e.vector_store.provider&&me(e.vector_store.provider),e.vector_store.mode&&ge(e.vector_store.mode),e.vector_store.storage_path&&ve(e.vector_store.storage_path),e.vector_store.url&&be(e.vector_store.url),e.vector_store.collection&&Se(e.vector_store.collection)):e?.vector_store_provider&&ue(t=>t?{...t,provider:e.vector_store_provider||t.provider,mode:e.vector_store_mode||t.mode,collection:e.vector_store_collection||t.collection,points_count:e.points_count??t.points_count,healthy:e.vector_db_status?e.vector_db_status===`Healthy`:t.healthy}:null)},[e]);let st=async()=>{nt(!0);try{let e=new URLSearchParams;Ge&&e.set(`url`,Ge.trim()),qe&&e.set(`api_key`,qe.trim());let t=await(await fetch(`/admin/api/models/discover?${e.toString()}`)).json();et(t),t.status===`success`?R.success(`Discovered ${t.total_models} models from LiteLLM`):R.error(t.message||`Failed to discover models from LiteLLM`)}catch(e){et({status:`error`,total_models:0,models:[],embedding_models:[],vision_models:[],chat_models:[],message:e.message||`Network error discovering models`}),R.error(`Error discovering models: ${e.message}`)}finally{nt(!1)}},ct=async e=>{e.preventDefault(),Pe(!0);try{let e={provider:Fe,threads:Number(Le),batch_size:Number(ze),dense_model:Ve.trim()||void 0,sparse_model:Ue.trim()||void 0,litellm_url:Fe===`api`&&Ge.trim()||void 0,litellm_api_key:Fe===`api`&&qe.trim()||void 0,vision_ocr_model:Ye.trim()||void 0,chat_model:Ze.trim()||void 0},n=await fetch(`/admin/api/settings/embedding`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)}),r=await n.json();if(!n.ok)throw Error(r.error||r.message||`Failed to save embedding settings`);r.config&&Ae(r.config),R.success(`Embedding resource limits updated successfully`),t()}catch(e){R.error(`Error saving embedding settings: `+e.message)}finally{Pe(!1)}},lt=`${typeof window<`u`?window.location.origin:``}${g||`/api/webhooks/git`}`,ut=async()=>{try{navigator?.clipboard?.writeText&&await navigator.clipboard.writeText(lt),O(!0),R.info(`Webhook URL copied to clipboard`),setTimeout(()=>O(!1),2e3)}catch(e){R.error(`Failed to copy: `+e.message)}},dt=async e=>{e.preventDefault(),w(!0);try{let e={interval_mins:Number(u)};f.trim()&&(e.global_webhook_secret=f.trim());let t=await fetch(`/admin/api/settings/auto-sync`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)}),n=await t.json();if(!t.ok)throw Error(n.error||`Failed to save auto-sync settings`);typeof n.has_global_secret==`boolean`?h(n.has_global_secret):f.trim()&&h(!0),typeof n.interval_mins==`number`&&d(n.interval_mins),p(``),R.success(`Auto-sync settings saved successfully`)}catch(e){R.error(`Error saving auto-sync settings: `+e.message)}finally{w(!1)}},ft=async()=>{if(f&&!m){p(``);return}if(window.confirm(`Clear the global webhook secret? Incoming webhook payloads will no longer require secret verification unless configured per-repository.`)){w(!0);try{let e=await fetch(`/admin/api/settings/auto-sync`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({interval_mins:Number(u),global_webhook_secret:``})}),t=await e.json();if(!e.ok)throw Error(t.error||`Failed to clear global webhook secret`);h(!1),p(``),R.success(`Global webhook secret cleared`)}catch(e){R.error(`Failed to clear webhook secret: `+e.message)}finally{w(!1)}}},pt=e=>{me(e),e===`qdrant`?((!_e||_e===`data/chroma_db`)&&ve(`data/qdrant_db`),(!ye||ye===`http://localhost:8000`)&&be(`http://localhost:6333`)):((!_e||_e===`data/qdrant_db`)&&ve(`data/chroma_db`),(!ye||ye===`http://localhost:6333`)&&be(`http://localhost:8000`))},mt=e=>{ge(e),e===`embedded`&&!_e&&ve(pe===`chroma`?`data/chroma_db`:`data/qdrant_db`),e===`remote`&&!ye&&be(pe===`chroma`?`http://localhost:8000`:`http://localhost:6333`)},ht=async()=>{we(!0),Ee(null);try{let e={provider:pe,mode:he,storage_path:he===`embedded`?_e.trim():null,url:he===`remote`?ye.trim():null,collection:xe.trim()||`knowledge_rag_v1`},t=await fetch(`/admin/api/vector-store/test`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)}),n=await t.json();if(!t.ok||!n.success){let e=n.message||n.error||`Vector store connection test failed`;Ee({success:!1,message:e}),R.error(`Vector store test: `+e)}else{let e=n.message||`Vector store connection test successful`;Ee({success:!0,message:e}),R.success(e)}}catch(e){let t=e.message||`Connection error`;Ee({success:!1,message:t}),R.error(`Vector store test error: `+t)}finally{we(!1)}},gt=async()=>{let e=pe===`chroma`?`ChromaDB`:`Qdrant`,n=he===`embedded`?`Embedded Disk`:`Remote Server`;if(window.confirm(`Switch active vector database backend to ${e} (${n})? This will update settings and trigger a full re-indexing of all sources.`)){Oe(!0);try{let n={provider:pe,mode:he,storage_path:he===`embedded`?_e.trim():null,url:he===`remote`?ye.trim():null,collection:xe.trim()||`knowledge_rag_v1`},r=await fetch(`/admin/api/vector-store/switch`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(n)}),i=await r.json();if(!r.ok||i.status===`error`){let e=i.error||i.message||`Failed to switch vector database backend`;Ee({success:!1,message:e}),R.error(`Switch error: `+e)}else{let n=i.message||`Switched vector backend to ${e}`;Ee({success:!0,message:n}),R.success(n),await it(),t()}}catch(e){Ee({success:!1,message:e.message}),R.error(`Switch error: `+e.message)}finally{Oe(!1)}}},_t=async(e,n,i)=>{if(n.trim())try{let o=await fetch(`/admin/api/settings/token`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({[e]:n.trim()})}),c=await o.json();if(!o.ok)throw Error(c.error||`Failed to save token`);R.success(`${i} token saved successfully.`),e===`github_token`&&r(``),e===`gitlab_token`&&a(``),e===`gitea_token`&&s(``),t()}catch(e){R.error(`Error saving ${i} token: `+e.message)}},vt=async(e,n)=>{if(window.confirm(`Clear the stored ${n} token from database?`))try{let r=await fetch(`/admin/api/settings/token`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({[e]:``})});if(!r.ok){let e=await r.json().catch(()=>({}));throw Error(e.error||`Failed to clear token`)}R.success(`${n} token cleared`),t()}catch(e){R.error(`Failed to clear ${n} token: `+e.message)}},yt=async e=>{if(e.preventDefault(),!(!A.trim()||!F.trim())){ce(!0);try{let e=await fetch(`/admin/api/settings/hosts`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({host:A.trim(),provider:ne,auth_user:ie.trim()||null,auth_token:F.trim()})}),t=await e.json();if(!e.ok)throw Error(t.error||`Failed to save host credential`);R.success(`Host credential for '${A.trim()}' saved`),ee(!1),te(``),re(`gitlab`),ae(``),I(``),rt()}catch(e){R.error(`Error: `+e.message)}finally{ce(!1)}}},bt=async(e,t)=>{if(window.confirm(`Remove stored credentials for host '${t}'?`))try{let n=await fetch(`/admin/api/settings/hosts/${e}`,{method:`DELETE`});if(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.error||`Failed to delete`)}R.success(`Removed credentials for '${t}'`),rt()}catch(e){R.error(`Failed to remove: `+e.message)}},xt=e?.providers_auth?.github||{token_source:e?.token_source||`None`,masked_token:e?.masked_token||`None`},St=e?.providers_auth?.gitlab||{token_source:`None`,masked_token:`None`},Ct=e?.providers_auth?.gitea||{token_source:`None`,masked_token:`None`};return(0,b.jsxs)(`div`,{className:`tab-content active`,style:{display:`flex`,flexDirection:`column`,gap:`24px`},children:[(0,b.jsx)(oe,{}),(0,b.jsx)(j,{vectorStore:le,isLoadingVs:de,testFeedback:Te,vsProvider:pe,vsMode:he,vsStoragePath:_e,setVsStoragePath:ve,vsUrl:ye,setVsUrl:be,vsCollection:xe,setVsCollection:Se,isTestingVs:Ce,isSwitchingVs:De,onProviderChange:pt,onModeChange:mt,onTestConnection:ht,onSwitchBackend:gt}),(0,b.jsx)(M,{embeddingConfig:ke,isLoadingEmb:je,isSavingEmb:Ne,embProvider:Fe,setEmbProvider:Ie,embThreads:Le,setEmbThreads:Re,embBatchSize:ze,setEmbBatchSize:Be,embDenseModel:Ve,setEmbDenseModel:He,embSparseModel:Ue,setEmbSparseModel:We,embLitellmUrl:Ge,setEmbLitellmUrl:Ke,embLitellmApiKey:qe,setEmbLitellmApiKey:Je,embVisionOcrModel:Ye,setEmbVisionOcrModel:Xe,embChatModel:Ze,setEmbChatModel:Qe,discoveryResult:$e,isDiscovering:tt,onDiscoverModels:st,onSaveEmbeddingSettings:ct}),(0,b.jsx)(N,{isLoadingAutoSync:y,intervalMins:u,setIntervalMins:d,hasGlobalSecret:m,showWebhookSecret:T,setShowWebhookSecret:E,webhookSecret:f,setWebhookSecret:p,fullWebhookUrl:lt,copiedWebhookUrl:D,isSavingAutoSync:C,onSaveAutoSync:dt,onClearWebhookSecret:ft,onCopyWebhookUrl:ut}),(0,b.jsx)(P,{stats:e,ghAuth:xt,glAuth:St,gtAuth:Ct,ghToken:n,setGhToken:r,glToken:i,setGlToken:a,gtToken:o,setGtToken:s,hostCredentials:c,isHostModalOpen:k,setIsHostModalOpen:ee,newHost:A,setNewHost:te,newHostProvider:ne,setNewHostProvider:re,newHostUser:ie,setNewHostUser:ae,newHostToken:F,setNewHostToken:I,isSavingHost:L,onSaveToken:_t,onClearToken:vt,onSaveHostCredential:yt,onDeleteHostCredential:bt}),(0,b.jsx)(se,{})]})}function le(){let e=S(),[t,n]=(0,_.useState)([]),[r,i]=(0,_.useState)(!1),[a,o]=(0,_.useState)(`ALL`),[s,c]=(0,_.useState)(``),[l,u]=(0,_.useState)(!0),[d,f]=(0,_.useState)(new Set),p=(0,_.useRef)(null),m=(0,_.useRef)(null),h=(0,_.useCallback)(async()=>{i(!0);try{let e=await fetch(`/admin/api/logs`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();n(Array.isArray(t)?t:[])}catch(t){e.error(`Failed to load diagnostics logs: ${t.message}`)}finally{i(!1)}},[e]);(0,_.useEffect)(()=>{h();let e=setInterval(h,8e3);return()=>clearInterval(e)},[h]),(0,_.useEffect)(()=>{l&&m.current&&(m.current.scrollTop=m.current.scrollHeight)},[t,l]);let g=async()=>{if(window.confirm(`Are you sure you want to clear all server diagnostics logs?`))try{let t=await fetch(`/admin/api/logs`,{method:`DELETE`});if(!t.ok){let e=await t.json().catch(()=>({}));throw Error(e.error||`HTTP ${t.status}`)}n([]),f(new Set),e.success(`Diagnostics logs cleared.`)}catch(t){e.error(`Failed to clear logs: ${t.message}`)}},v=e=>{f(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},y=e=>{switch(e.toUpperCase()){case`ERROR`:return`badge badge-danger`;case`WARNING`:return`badge badge-warning`;case`INFO`:return`badge badge-primary`;case`DEBUG`:return`badge badge-secondary`;default:return`badge badge-secondary`}},x=t.filter(e=>{if(a!==`ALL`&&e.level.toUpperCase()!==a)return!1;if(s.trim()){let t=s.toLowerCase(),n=e.message?.toLowerCase().includes(t),r=e.logger?.toLowerCase().includes(t),i=e.traceback?.toLowerCase().includes(t),a=e.level?.toLowerCase().includes(t);if(!n&&!r&&!i&&!a)return!1}return!0}),C={ALL:t.length,INFO:t.filter(e=>e.level===`INFO`).length,WARNING:t.filter(e=>e.level===`WARNING`).length,ERROR:t.filter(e=>e.level===`ERROR`).length,DEBUG:t.filter(e=>e.level===`DEBUG`).length};return(0,b.jsx)(`div`,{className:`tab-content active`,children:(0,b.jsxs)(`div`,{className:`glass-card log-viewer-container`,children:[(0,b.jsxs)(`div`,{className:`log-viewer-header`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-terminal`}),` Diagnostics & Server Logs`]}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Inspect real-time server runtime events, tree-sitter AST parsing, background indexing, and MCP tool traces.`})]}),(0,b.jsxs)(`div`,{className:`log-viewer-actions`,children:[(0,b.jsxs)(`label`,{className:`log-autoscroll-label`,children:[(0,b.jsx)(`input`,{type:`checkbox`,checked:l,onChange:e=>u(e.target.checked)}),`Auto-scroll`]}),(0,b.jsxs)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:h,disabled:r,title:`Refresh logs`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-rotate ${r?`fa-spin`:``}`}),` Refresh`]}),(0,b.jsxs)(`button`,{type:`button`,className:`btn btn-danger`,onClick:g,title:`Clear all logs`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-trash-can`}),` Clear Logs`]})]})]}),(0,b.jsxs)(`div`,{className:`log-toolbar`,children:[(0,b.jsx)(`div`,{className:`log-filter-pills`,children:[`ALL`,`INFO`,`WARNING`,`ERROR`,`DEBUG`].map(e=>(0,b.jsxs)(`button`,{type:`button`,className:`log-filter-btn ${a===e?`active`:``} ${e.toLowerCase()}`,onClick:()=>o(e),children:[e,` `,(0,b.jsx)(`span`,{className:`pill-count`,children:C[e]})]},e))}),(0,b.jsxs)(`div`,{className:`log-search-wrapper`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-magnifying-glass search-icon`}),(0,b.jsx)(`input`,{type:`text`,className:`log-search-input`,placeholder:`Search logs by message, logger, or traceback...`,value:s,onChange:e=>c(e.target.value)}),s&&(0,b.jsx)(`button`,{type:`button`,className:`clear-search-btn`,onClick:()=>c(``),title:`Clear search`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-xmark`})})]})]}),(0,b.jsx)(`div`,{className:`log-stream-container`,ref:m,children:x.length===0?(0,b.jsxs)(`div`,{className:`empty-state`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-circle-info`,style:{fontSize:`2rem`,marginBottom:`12px`,opacity:.5}}),(0,b.jsxs)(`p`,{children:[`No logs available `,s||a!==`ALL`?`matching current filter`:``,`.`]})]}):(0,b.jsxs)(`div`,{className:`log-stream-list`,children:[x.map((e,t)=>{let n=!!e.traceback,r=d.has(t);return(0,b.jsxs)(`div`,{className:`log-entry log-level-${e.level.toLowerCase()} ${n?`has-traceback`:``}`,children:[(0,b.jsxs)(`div`,{className:`log-entry-main`,children:[(0,b.jsx)(`span`,{className:`log-timestamp`,children:e.timestamp}),(0,b.jsx)(`span`,{className:y(e.level),children:e.level}),(0,b.jsx)(`span`,{className:`log-logger code`,children:e.logger}),(0,b.jsx)(`span`,{className:`log-message`,children:e.message}),n&&(0,b.jsxs)(`button`,{type:`button`,className:`btn-traceback-toggle`,onClick:()=>v(t),title:`Toggle traceback details`,children:[(0,b.jsx)(`i`,{className:`fa-solid ${r?`fa-chevron-down`:`fa-chevron-right`}`}),(0,b.jsx)(`span`,{children:r?`Hide Stack Trace`:`View Stack Trace`})]})]}),n&&r&&(0,b.jsx)(`div`,{className:`log-traceback-wrapper`,children:(0,b.jsx)(`pre`,{className:`traceback-box`,children:e.traceback})})]},`${e.timestamp}-${t}`)}),(0,b.jsx)(`div`,{ref:p})]})})]})})}function ue(e){switch(e){case`symbol`:return`badge-symbol`;case`code`:return`badge-code`;case`file`:return`badge-file`;case`route`:return`badge-route`;case`doc`:return`badge-doc`;default:return`badge-default`}}var de=({repo:e,onSelectResult:t,placeholder:n=`Search files, symbols, routes, or code text...`})=>{let[r,i]=(0,_.useState)(``),[a,o]=(0,_.useState)([]),[s,c]=(0,_.useState)(!1),[l,u]=(0,_.useState)(!1),[d,f]=(0,_.useState)(-1),p=(0,_.useRef)(null),m=(0,_.useRef)(null),h=(0,_.useRef)(null);(0,_.useEffect)(()=>{let t=r.trim();if(!t){o([]),u(!1),f(-1);return}h.current&&h.current.abort();let n=new AbortController;h.current=n,c(!0);let i=setTimeout(async()=>{try{let r=await fetch(`/admin/api/navigator/omni-search?repo=${encodeURIComponent(e)}&q=${encodeURIComponent(t)}&limit=25`,{signal:n.signal});if(r.ok){let e=await r.json();o(e.matches||[]),u(!0),f(-1)}}catch(e){e.name!==`AbortError`&&console.error(`Omni-search error:`,e)}finally{c(!1)}},150);return()=>{clearTimeout(i),n.abort()}},[r,e]),(0,_.useEffect)(()=>{let e=e=>{p.current&&!p.current.contains(e.target)&&u(!1)};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[]);let g=e=>{if(!l||a.length===0){e.key===`ArrowDown`&&a.length>0&&u(!0);return}e.key===`ArrowDown`?(e.preventDefault(),f(e=>ee>0?e-1:a.length-1)):e.key===`Enter`?(e.preventDefault(),d>=0&&d{u(!1),i(``),o([]),t(e)},[t]);return(0,b.jsxs)(`div`,{className:`nav-omni-container`,ref:p,"data-testid":`navigator-omni-search`,children:[(0,b.jsxs)(`div`,{className:`nav-omni-input-wrapper`,children:[(0,b.jsxs)(`svg`,{className:`nav-omni-search-icon`,width:`14`,height:`14`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,b.jsx)(`circle`,{cx:`11`,cy:`11`,r:`8`}),(0,b.jsx)(`line`,{x1:`21`,y1:`21`,x2:`16.65`,y2:`16.65`})]}),(0,b.jsx)(`input`,{ref:m,type:`text`,className:`nav-omni-input`,value:r,onChange:e=>i(e.target.value),onKeyDown:g,onFocus:()=>{a.length>0&&u(!0)},placeholder:n,"aria-label":`Omni-Search`,"aria-autocomplete":`list`,"aria-expanded":l}),s&&(0,b.jsx)(`div`,{className:`nav-omni-spinner`}),r&&(0,b.jsx)(`button`,{type:`button`,className:`nav-omni-clear-btn`,onClick:()=>{i(``),o([]),u(!1),f(-1),m.current?.focus()},title:`Clear search`,"aria-label":`Clear Search`,children:`✕`})]}),l&&(0,b.jsxs)(`div`,{className:`nav-omni-dropdown`,"data-testid":`nav-omni-dropdown`,role:`listbox`,children:[(0,b.jsxs)(`div`,{className:`nav-omni-dropdown-header`,children:[(0,b.jsxs)(`span`,{children:[`Matches (`,a.length,`)`]}),(0,b.jsx)(`span`,{className:`nav-omni-hint`,children:`↑↓ Navigate • Enter Select • Esc Close`})]}),a.length===0&&!s&&(0,b.jsxs)(`div`,{className:`nav-omni-empty`,children:[`No matching files, symbols, or code found for "`,r,`"`]}),(0,b.jsx)(`div`,{className:`nav-omni-list`,children:a.map((e,t)=>{let n=t===d,r=ue(e.type);return(0,b.jsxs)(`div`,{className:`nav-omni-item ${n?`active`:``}`,onClick:()=>v(e),onMouseEnter:()=>f(t),role:`option`,"aria-selected":n,children:[(0,b.jsxs)(`div`,{className:`nav-omni-item-top`,children:[(0,b.jsxs)(`div`,{className:`nav-omni-item-title-group`,children:[(0,b.jsx)(`span`,{className:`nav-omni-badge ${r}`,children:e.type.toUpperCase()}),(0,b.jsx)(`span`,{className:`nav-omni-item-name`,children:e.name}),e.kind&&e.kind!==e.type&&(0,b.jsxs)(`span`,{className:`nav-omni-item-kind`,children:[`(`,e.kind,`)`]})]}),(0,b.jsx)(`span`,{className:`nav-omni-score-pill`,children:e.score_label})]}),(0,b.jsxs)(`div`,{className:`nav-omni-item-bottom`,children:[(0,b.jsxs)(`span`,{className:`nav-omni-item-path`,children:[e.filepath,e.start_line>0&&`:${e.start_line}`]}),e.preview&&e.preview!==e.name&&(0,b.jsx)(`span`,{className:`nav-omni-item-preview truncate`,children:e.preview})]})]},e.id)})})]})]})},fe=({repos:e,selectedRepo:t,onSelectRepo:n,density:r,onChangeDensity:i,searchQuery:a=``,onSearchChange:o,onSelectSearchResult:s,totalFiles:c=0,totalSymbols:l=0,onRefresh:u,loading:d=!1})=>(0,b.jsxs)(`div`,{className:`nav-toolbar`,"data-testid":`navigator-toolbar`,children:[(0,b.jsxs)(`div`,{className:`nav-toolbar-left`,children:[(0,b.jsxs)(`div`,{className:`nav-repo-selector-wrapper`,children:[(0,b.jsx)(`label`,{htmlFor:`nav-repo-select`,className:`nav-repo-label`,children:`Repository:`}),(0,b.jsxs)(`select`,{id:`nav-repo-select`,"aria-label":`Repository`,className:`nav-repo-select`,value:t,onChange:e=>n(e.target.value),disabled:d,children:[(0,b.jsx)(`option`,{value:`__all__`,children:`All Repositories (__all__)`}),e.filter(e=>e.name!==`__all__`).map(e=>(0,b.jsx)(`option`,{value:e.name,children:e.name},e.id??e.name))]})]}),(0,b.jsxs)(`div`,{className:`nav-stats-badges`,children:[(0,b.jsxs)(`span`,{className:`nav-stat-badge`,title:`Total Indexed Files`,children:[(0,b.jsx)(`span`,{className:`stat-num`,children:c}),` files`]}),(0,b.jsxs)(`span`,{className:`nav-stat-badge`,title:`Total Extracted AST Symbols`,children:[(0,b.jsx)(`span`,{className:`stat-num`,children:l}),` symbols`]})]})]}),(0,b.jsx)(`div`,{className:`nav-toolbar-center`,children:s?(0,b.jsx)(de,{repo:t,onSelectResult:s}):o?(0,b.jsxs)(`div`,{className:`nav-global-search`,children:[(0,b.jsx)(`span`,{className:`search-icon-wrapper`,"aria-hidden":`true`,children:(0,b.jsxs)(`svg`,{className:`search-icon`,width:`14`,height:`14`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,b.jsx)(`circle`,{cx:`11`,cy:`11`,r:`8`}),(0,b.jsx)(`line`,{x1:`21`,y1:`21`,x2:`16.65`,y2:`16.65`})]})}),(0,b.jsx)(`input`,{type:`text`,className:`nav-search-input`,placeholder:`Search files (Ctrl+P / /)...`,value:a,onChange:e=>o(e.target.value),"aria-label":`Quick search files`}),a&&(0,b.jsx)(`button`,{type:`button`,className:`nav-search-clear-btn`,onClick:()=>o(``),"aria-label":`Clear search`,children:`✕`})]}):null}),(0,b.jsxs)(`div`,{className:`nav-toolbar-right`,children:[(0,b.jsxs)(`div`,{className:`nav-density-switcher`,role:`group`,"aria-label":`Density mode`,children:[(0,b.jsx)(`span`,{className:`density-label`,children:`Density:`}),(0,b.jsx)(`button`,{type:`button`,className:`density-btn ${r===`compact`?`active`:``}`,onClick:()=>i(`compact`),title:`Compact (20px rows - IDE mode)`,"aria-pressed":r===`compact`,children:`Compact`}),(0,b.jsx)(`button`,{type:`button`,className:`density-btn ${r===`balanced`?`active`:``}`,onClick:()=>i(`balanced`),title:`Balanced (28px rows - Default)`,"aria-pressed":r===`balanced`,children:`Balanced`}),(0,b.jsx)(`button`,{type:`button`,className:`density-btn ${r===`spacious`?`active`:``}`,onClick:()=>i(`spacious`),title:`Spacious (36px rows - Cards)`,"aria-pressed":r===`spacious`,children:`Spacious`})]}),u&&(0,b.jsx)(`button`,{type:`button`,className:`nav-refresh-btn ${d?`spinning`:``}`,onClick:u,title:`Refresh codebase tree`,disabled:d,"aria-label":`Refresh codebase tree`,children:(0,b.jsx)(`svg`,{width:`15`,height:`15`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:(0,b.jsx)(`path`,{d:`M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67`})})})]})]});function pe(e,t){let n=e.toLowerCase();return n.endsWith(`.py`)||t===`python`?{icon:`🐍`,className:`icon-py`}:n.endsWith(`.tsx`)||n.endsWith(`.ts`)||t===`typescript`?{icon:`⚡`,className:`icon-ts`}:n.endsWith(`.jsx`)||n.endsWith(`.js`)||n.endsWith(`.mjs`)||t===`javascript`?{icon:`📜`,className:`icon-js`}:n.endsWith(`.go`)||t===`go`?{icon:`🔷`,className:`icon-go`}:n.endsWith(`.rs`)||t===`rust`?{icon:`🦀`,className:`icon-rs`}:n.endsWith(`.json`)?{icon:`📄`,className:`icon-json`}:n.endsWith(`.md`)||n.endsWith(`.markdown`)?{icon:`📝`,className:`icon-md`}:n.endsWith(`.css`)||n.endsWith(`.scss`)?{icon:`🎨`,className:`icon-css`}:n.endsWith(`.sql`)?{icon:`🗄️`,className:`icon-sql`}:n.endsWith(`.yaml`)||n.endsWith(`.yml`)||n.endsWith(`.toml`)?{icon:`⚙️`,className:`icon-yaml`}:{icon:`📄`,className:`icon-file`}}var me=({nodes:e,selectedPath:t,onSelectFile:n,filterText:r,onFilterChange:i,density:a=`balanced`,loading:o=!1})=>{let[s,c]=(0,_.useState)(``),l=r===void 0?s:r,u=i||c,[d,f]=(0,_.useState)(new Set),[p,m]=(0,_.useState)(-1),h=(0,_.useRef)(null),g=(0,_.useCallback)(e=>{let t=[],n=e=>{if(!(!e||!Array.isArray(e)))for(let r of e)r&&r.is_dir&&(t.push(r.id),Array.isArray(r.children)&&n(r.children))};return n(e),t},[]),v=(0,_.useCallback)((e,t)=>{if(!e||!Array.isArray(e))return{filteredNodes:[],matchedDirIds:new Set};let n=t.toLowerCase().trim(),r=new Set,i=e=>{if(!e)return null;let t=(e.name||``).toLowerCase().includes(n)||(e.path||``).toLowerCase().includes(n);if(e.is_dir){let n=Array.isArray(e.children)?e.children:[],a=n.map(i).filter(e=>e!==null);return a.length>0||t?(r.add(e.id),{...e,children:a.length>0?a:n}):null}return t?e:null};return{filteredNodes:e.map(i).filter(e=>e!==null),matchedDirIds:r}},[]),{filteredNodes:y,matchedDirIds:x}=(0,_.useMemo)(()=>l.trim()?v(e,l):{filteredNodes:Array.isArray(e)?e:[],matchedDirIds:new Set},[e,l,v]);(0,_.useEffect)(()=>{l.trim()&&f(e=>{let t=new Set(e);return x.forEach(e=>t.add(e)),t})},[l,x]),(0,_.useEffect)(()=>{if(t){let e=t.split(`/`),n=[];for(let t=1;t0&&f(e=>{let t=new Set(e);return n.forEach(e=>t.add(e)),t})}},[t]);let S=(0,_.useCallback)(e=>{f(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},[]),C=(0,_.useCallback)(()=>{let t=g(e);f(new Set(t))},[e,g]),w=(0,_.useCallback)(()=>{f(new Set)},[]),T=(0,_.useMemo)(()=>{let e=[],t=!!l.trim(),n=(r,i)=>{if(!(!r||!Array.isArray(r)))for(let a of r){if(!a)continue;let r=!!(a.is_dir&&Array.isArray(a.children)&&a.children.length>0),o=d.has(a.id)||t&&x.has(a.id);e.push({node:a,depth:i,isExpanded:o,hasChildren:r}),a.is_dir&&o&&Array.isArray(a.children)&&n(a.children,i+1)}};return n(y,0),e},[y,d,l,x]);return(0,b.jsxs)(`div`,{className:`nav-tree-pane density-${a}`,"data-testid":`navigator-tree-container`,tabIndex:0,ref:h,onKeyDown:e=>{if(T.length!==0){if(e.key===`ArrowDown`)e.preventDefault(),m(e=>ee>0?e-1:T.length-1);else if(e.key===`ArrowRight`){if(e.preventDefault(),p>=0&&p=0&&p=0&&pu(e.target.value)}),l&&(0,b.jsx)(`button`,{type:`button`,className:`nav-tree-clear-btn`,onClick:()=>u(``),"aria-label":`Clear filter`,children:`✕`})]})]}),(0,b.jsx)(`div`,{className:`nav-tree-content`,children:o?(0,b.jsxs)(`div`,{className:`nav-loading-wrapper`,"data-testid":`tree-loading-spinner`,children:[(0,b.jsx)(`div`,{className:`nav-spinner`}),(0,b.jsx)(`span`,{children:`Loading codebase tree...`})]}):T.length===0?(0,b.jsxs)(`div`,{className:`nav-empty-state`,children:[(0,b.jsx)(`p`,{children:`No matching files found`}),l&&(0,b.jsx)(`button`,{type:`button`,className:`nav-clear-filter-btn`,onClick:()=>u(``),children:`Clear filter`})]}):(0,b.jsx)(`div`,{className:`nav-tree-list`,role:`tree`,children:T.map((e,r)=>{let{node:i,depth:a,isExpanded:o}=e,s=!i.is_dir&&i.path===t,c=r===p,{icon:l,className:u}=i.is_dir?{icon:o?`📂`:`📁`,className:`icon-dir`}:pe(i.name,i.language);return(0,b.jsxs)(`div`,{role:`treeitem`,"aria-expanded":i.is_dir?o:void 0,"aria-selected":s,className:`nav-tree-item ${i.is_dir?`dir-item`:`file-item`} ${s?`selected`:``} ${c?`focused`:``}`,style:{paddingLeft:`${a*14+10}px`},onClick:()=>{m(r),i.is_dir?S(i.id):n(i)},children:[(0,b.jsx)(`span`,{className:`tree-chevron`,children:i.is_dir?(0,b.jsx)(`span`,{className:`chevron-arrow ${o?`open`:``}`,children:`▶`}):(0,b.jsx)(`span`,{className:`chevron-spacer`})}),(0,b.jsx)(`span`,{className:`tree-icon ${u}`,"aria-hidden":`true`,children:l}),(0,b.jsx)(`span`,{className:`tree-label`,title:i.path,children:i.name}),(0,b.jsxs)(`div`,{className:`tree-badges`,children:[i.symbol_count>0&&(0,b.jsxs)(`span`,{className:`badge-symbols`,title:`${i.symbol_count} AST Symbols`,children:[i.symbol_count,` sym`]}),i.route_count>0&&(0,b.jsxs)(`span`,{className:`badge-routes`,title:`${i.route_count} API Routes`,children:[i.route_count,` rts`]})]})]},i.id)})})})]})};function he(e){switch(e.toUpperCase()){case`GET`:return`method-get`;case`POST`:return`method-post`;case`PUT`:return`method-put`;case`DELETE`:return`method-delete`;case`PATCH`:return`method-patch`;default:return`method-other`}}function ge(e){switch(e.toLowerCase()){case`class`:return`kind-class`;case`function`:case`method`:case`async function`:return`kind-function`;case`variable`:case`constant`:return`kind-variable`;case`interface`:case`type`:return`kind-type`;default:return`kind-default`}}var _e=({outline:e,selectedSymbolId:t,onSelectSymbol:n,onReadDoc:r,density:i=`balanced`,loading:a=!1})=>{let[o,s]=(0,_.useState)(`all`),[c,l]=(0,_.useState)(``),u=(0,_.useMemo)(()=>e?.symbols||[],[e?.symbols]),d=(0,_.useMemo)(()=>{let e=0,t=0,n=0;for(let r of u){r.route&&e++;let i=r.kind.toLowerCase();i===`class`||i===`interface`?t++:(i.includes(`func`)||i===`method`)&&(r.route||n++)}return{all:u.length,routes:e,classes:t,functions:n}},[u]),f=(0,_.useMemo)(()=>u.filter(e=>{if(o===`routes`){if(!e.route)return!1}else if(o===`classes`){let t=e.kind.toLowerCase();if(t!==`class`&&t!==`interface`)return!1}else if(o===`functions`){let t=e.kind.toLowerCase();if(!t.includes(`func`)&&t!==`method`||e.route)return!1}if(c.trim()){let t=c.toLowerCase(),n=e.name.toLowerCase().includes(t),r=(e.signature||``).toLowerCase().includes(t),i=e.route?e.route.path_pattern.toLowerCase().includes(t)||e.route.http_method.toLowerCase().includes(t):!1;if(!n&&!r&&!i)return!1}return!0}),[u,o,c]),p=e?.filepath?e.filepath.split(`/`).pop():``;return(0,b.jsxs)(`div`,{className:`nav-outline-pane density-${i}`,"data-testid":`navigator-outline-container`,children:[(0,b.jsxs)(`div`,{className:`nav-outline-header`,children:[(0,b.jsxs)(`div`,{className:`nav-outline-title-row`,children:[(0,b.jsxs)(`div`,{className:`nav-outline-title`,children:[(0,b.jsxs)(`svg`,{width:`15`,height:`15`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,b.jsx)(`line`,{x1:`8`,y1:`6`,x2:`21`,y2:`6`}),(0,b.jsx)(`line`,{x1:`8`,y1:`12`,x2:`21`,y2:`12`}),(0,b.jsx)(`line`,{x1:`8`,y1:`18`,x2:`21`,y2:`18`}),(0,b.jsx)(`line`,{x1:`3`,y1:`6`,x2:`3.01`,y2:`6`}),(0,b.jsx)(`line`,{x1:`3`,y1:`12`,x2:`3.01`,y2:`12`}),(0,b.jsx)(`line`,{x1:`3`,y1:`18`,x2:`3.01`,y2:`18`})]}),(0,b.jsx)(`span`,{children:`Symbols & Routes`})]}),e&&(0,b.jsx)(`div`,{className:`nav-file-badge`,title:e.filepath,children:(0,b.jsx)(`span`,{className:`file-name`,children:p})})]}),e&&(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`div`,{className:`nav-outline-search-bar`,children:[(0,b.jsx)(`span`,{className:`search-icon-wrapper`,"aria-hidden":`true`,children:(0,b.jsxs)(`svg`,{className:`search-icon`,width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,children:[(0,b.jsx)(`circle`,{cx:`11`,cy:`11`,r:`8`}),(0,b.jsx)(`line`,{x1:`21`,y1:`21`,x2:`16.65`,y2:`16.65`})]})}),(0,b.jsx)(`input`,{type:`text`,className:`nav-outline-search-input`,placeholder:`Filter symbols...`,value:c,onChange:e=>l(e.target.value)}),c&&(0,b.jsx)(`button`,{type:`button`,className:`nav-outline-clear-btn`,onClick:()=>l(``),"aria-label":`Clear symbol search`,children:`✕`})]}),(0,b.jsxs)(`div`,{className:`nav-category-chips`,role:`tablist`,"aria-label":`Symbol Category Filter`,children:[(0,b.jsxs)(`button`,{type:`button`,role:`button`,className:`category-chip ${o===`all`?`active`:``}`,onClick:()=>s(`all`),children:[`All `,(0,b.jsx)(`span`,{className:`chip-count`,children:d.all})]}),(0,b.jsxs)(`button`,{type:`button`,role:`button`,className:`category-chip ${o===`routes`?`active`:``}`,onClick:()=>s(`routes`),children:[`Routes `,(0,b.jsx)(`span`,{className:`chip-count`,children:d.routes})]}),(0,b.jsxs)(`button`,{type:`button`,role:`button`,className:`category-chip ${o===`classes`?`active`:``}`,onClick:()=>s(`classes`),children:[`Classes `,(0,b.jsx)(`span`,{className:`chip-count`,children:d.classes})]}),(0,b.jsxs)(`button`,{type:`button`,role:`button`,className:`category-chip ${o===`functions`?`active`:``}`,onClick:()=>s(`functions`),children:[`Functions `,(0,b.jsx)(`span`,{className:`chip-count`,children:d.functions})]})]})]})]}),(0,b.jsx)(`div`,{className:`nav-outline-content`,children:a?(0,b.jsxs)(`div`,{className:`nav-outline-skeleton`,"data-testid":`outline-loading-skeleton`,children:[(0,b.jsx)(`div`,{className:`skeleton-item shimmer`}),(0,b.jsx)(`div`,{className:`skeleton-item shimmer`}),(0,b.jsx)(`div`,{className:`skeleton-item shimmer`}),(0,b.jsx)(`div`,{className:`skeleton-item shimmer`})]}):e?u.length===0?(0,b.jsxs)(`div`,{className:`nav-empty-state doc-file-state`,"data-testid":`outline-doc-state`,children:[(0,b.jsx)(`div`,{className:`empty-icon`,children:`📝`}),(0,b.jsx)(`h4`,{children:`Document File Selected`}),(0,b.jsx)(`p`,{style:{fontSize:`0.82rem`,color:`var(--text-muted)`,margin:`6px 0 12px`},children:`This file contains documentation or text content with no code AST symbols.`}),r&&(0,b.jsx)(`button`,{type:`button`,className:`btn btn-sm btn-primary read-doc-action-btn`,onClick:r,style:{padding:`6px 14px`,fontSize:`0.8rem`},children:`📖 Read Full Document`})]}):f.length===0?(0,b.jsxs)(`div`,{className:`nav-empty-state`,children:[(0,b.jsx)(`h4`,{children:`No Matching Symbols`}),(0,b.jsx)(`p`,{children:`No symbols match the current category and search filters.`}),(0,b.jsx)(`button`,{type:`button`,className:`nav-clear-filter-btn`,onClick:()=>{s(`all`),l(``)},children:`Reset Filters`})]}):(0,b.jsx)(`div`,{className:`nav-symbol-list`,role:`list`,children:f.map(e=>{let r=e.id===t,i=ge(e.kind);return(0,b.jsxs)(`div`,{"data-testid":`symbol-item-${e.id}`,className:`nav-symbol-item ${r?`active`:``}`,onClick:()=>n(e),role:`listitem`,tabIndex:0,onKeyDown:t=>{(t.key===`Enter`||t.key===` `)&&(t.preventDefault(),n(e))},children:[(0,b.jsxs)(`div`,{className:`symbol-header-row`,children:[(0,b.jsxs)(`div`,{className:`symbol-name-col`,children:[(0,b.jsx)(`span`,{className:`symbol-kind-badge ${i}`,children:e.kind}),(0,b.jsx)(`span`,{className:`symbol-name-text`,title:e.full_symbol||e.name,children:e.name})]}),(0,b.jsx)(`div`,{className:`symbol-meta-col`,children:(0,b.jsxs)(`span`,{className:`symbol-line-badge`,children:[`L`,e.start_line,e.end_line&&e.end_line!==e.start_line?` - L${e.end_line}`:``]})})]}),e.route&&(0,b.jsxs)(`div`,{className:`symbol-route-row`,children:[(0,b.jsx)(`span`,{className:`route-method-badge ${he(e.route.http_method)}`,children:e.route.http_method}),(0,b.jsx)(`span`,{className:`route-path-text`,title:e.route.path_pattern,children:e.route.path_pattern}),e.route.framework&&(0,b.jsx)(`span`,{className:`route-framework-tag`,children:e.route.framework})]}),e.signature&&(0,b.jsx)(`div`,{className:`symbol-signature-snippet`,children:(0,b.jsx)(`code`,{children:e.signature})})]},e.id)})}):(0,b.jsxs)(`div`,{className:`nav-empty-state`,children:[(0,b.jsx)(`div`,{className:`empty-icon`,children:`📂`}),(0,b.jsx)(`h4`,{children:`No File Selected`}),(0,b.jsx)(`p`,{children:`Select a file from the tree to inspect its symbols and routes.`})]})})]})},ve=({filepath:e,content:t,totalLines:n,sizeBytes:r,loading:i=!1,error:a=null,onRefresh:o})=>{let s=e.toLowerCase().endsWith(`.md`)||e.toLowerCase().endsWith(`.markdown`),[c,l]=(0,_.useState)(s?`rendered`:`raw`),[u,d]=(0,_.useState)(!1),f=async()=>{if(t)try{navigator?.clipboard?.writeText&&await navigator.clipboard.writeText(t),d(!0),setTimeout(()=>d(!1),2e3)}catch{}},p=e=>e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(1)} KB`:`${(e/1048576).toFixed(1)} MB`,m=e?e.split(`/`).pop()||e:``,h=e=>{let t=e.split(` -`),n=[],r=!1,i=``,a=[];return t.forEach((e,t)=>{if(e.trim().startsWith("```")){r?(n.push((0,b.jsx)(`pre`,{className:`doc-code-block ${i}`.trim(),children:(0,b.jsx)(`code`,{children:a.join(` -`)})},`code-${t}`)),a=[],r=!1):(r=!0,i=e.trim().slice(3));return}if(r){a.push(e);return}if(e.startsWith(`# `))n.push((0,b.jsx)(`h1`,{className:`doc-h1`,children:e.slice(2)},`h1-${t}`));else if(e.startsWith(`## `))n.push((0,b.jsx)(`h2`,{className:`doc-h2`,children:e.slice(3)},`h2-${t}`));else if(e.startsWith(`### `))n.push((0,b.jsx)(`h3`,{className:`doc-h3`,children:e.slice(4)},`h3-${t}`));else if(e.startsWith(`#### `))n.push((0,b.jsx)(`h4`,{className:`doc-h4`,children:e.slice(5)},`h4-${t}`));else if(e.trim()===`---`||e.trim()===`***`)n.push((0,b.jsx)(`hr`,{className:`doc-divider`},`hr-${t}`));else if(e.startsWith(`> `))n.push((0,b.jsx)(`blockquote`,{className:`doc-quote`,children:e.slice(2)},`quote-${t}`));else if(e.trim().startsWith(`- `)||e.trim().startsWith(`* `))n.push((0,b.jsx)(`li`,{className:`doc-list-item`,children:g(e.trim().slice(2))},`li-${t}`));else if(/^\d+\.\s/.test(e.trim())){let r=e.trim().replace(/^\d+\.\s/,``);n.push((0,b.jsx)(`li`,{className:`doc-numbered-item`,children:g(r)},`oli-${t}`))}else e.trim()===``?n.push((0,b.jsx)(`div`,{className:`doc-blank-line`},`blank-${t}`)):n.push((0,b.jsx)(`p`,{className:`doc-paragraph`,children:g(e)},`p-${t}`))}),r&&a.length>0&&n.push((0,b.jsx)(`pre`,{className:`doc-code-block`,children:(0,b.jsx)(`code`,{children:a.join(` -`)})},`code-tail`)),n},g=e=>e.split(/(`[^`]+`)/).map((e,t)=>e.startsWith("`")&&e.endsWith("`")&&e.length>=2?(0,b.jsx)(`code`,{className:`doc-inline-code`,children:e.slice(1,-1)},t):e.split(/(\*\*[^*]+\*\*)/).map((e,n)=>e.startsWith(`**`)&&e.endsWith(`**`)&&e.length>=4?(0,b.jsx)(`strong`,{children:e.slice(2,-2)},`${t}-${n}`):e));return(0,b.jsxs)(`div`,{className:`nav-doc-reader`,"data-testid":`nav-doc-reader`,children:[(0,b.jsxs)(`div`,{className:`doc-reader-header`,children:[(0,b.jsxs)(`div`,{className:`doc-reader-meta`,children:[(0,b.jsx)(`span`,{className:`doc-icon`,children:s?`📝`:`📄`}),(0,b.jsxs)(`div`,{className:`doc-title-wrapper`,children:[(0,b.jsx)(`h3`,{className:`doc-filename`,title:e,children:m}),(0,b.jsxs)(`div`,{className:`doc-meta-badges`,children:[(0,b.jsx)(`span`,{className:`doc-badge badge-path`,children:e}),(0,b.jsxs)(`span`,{className:`doc-badge badge-lines`,children:[n,` lines`]}),(0,b.jsx)(`span`,{className:`doc-badge badge-size`,children:p(r)})]})]})]}),(0,b.jsxs)(`div`,{className:`doc-reader-actions`,children:[s&&(0,b.jsxs)(`div`,{className:`doc-view-toggle`,role:`group`,"aria-label":`View Mode`,children:[(0,b.jsx)(`button`,{type:`button`,className:`toggle-btn ${c===`rendered`?`active`:``}`,onClick:()=>l(`rendered`),children:`Rendered`}),(0,b.jsx)(`button`,{type:`button`,className:`toggle-btn ${c===`raw`?`active`:``}`,onClick:()=>l(`raw`),children:`Raw Source`})]}),(0,b.jsx)(`button`,{type:`button`,className:`doc-copy-btn`,onClick:f,title:`Copy document content`,"aria-label":`Copy Document Content`,children:u?`✓ Copied!`:`Copy Content`})]})]}),(0,b.jsx)(`div`,{className:`doc-reader-body`,children:i?(0,b.jsxs)(`div`,{className:`doc-reader-loading`,"data-testid":`doc-loading`,children:[(0,b.jsx)(`div`,{className:`skeleton-header shimmer`}),(0,b.jsx)(`div`,{className:`skeleton-block shimmer`}),(0,b.jsx)(`div`,{className:`skeleton-block shimmer`})]}):a?(0,b.jsxs)(`div`,{className:`doc-reader-error`,role:`alert`,children:[(0,b.jsxs)(`p`,{children:[`Failed to load file content: `,a]}),o&&(0,b.jsx)(`button`,{type:`button`,className:`btn btn-sm btn-primary`,onClick:o,children:`Retry`})]}):c===`rendered`&&s?(0,b.jsx)(`div`,{className:`doc-rendered-content`,"data-testid":`doc-rendered-content`,children:h(t)}):(0,b.jsx)(`div`,{className:`doc-raw-container`,"data-testid":`doc-raw-content`,children:(0,b.jsx)(`pre`,{className:`doc-raw-pre`,children:(0,b.jsx)(`table`,{className:`doc-lines-table`,children:(0,b.jsx)(`tbody`,{children:t.split(` -`).map((e,t)=>(0,b.jsxs)(`tr`,{className:`doc-line-row`,children:[(0,b.jsx)(`td`,{className:`doc-line-num`,"data-line-number":t+1,children:t+1}),(0,b.jsx)(`td`,{className:`doc-line-code`,children:(0,b.jsx)(`code`,{children:e||` `})})]},t))})})})})})]})},ye=({filepath:e,content:t,totalLines:n,sizeBytes:r,targetStartLine:i,targetEndLine:a,impact:o,loading:s=!1,error:c=null,onRefresh:l,onSelectCaller:u,onSelectCallee:d,density:f=`balanced`})=>{let[p,m]=(0,_.useState)(!1),[h,g]=(0,_.useState)(!1),[v,y]=(0,_.useState)(!1),x=(0,_.useRef)(null),S=(0,_.useMemo)(()=>t?t.split(` -`):[],[t]);(0,_.useEffect)(()=>{i&&x.current&&x.current.scrollIntoView({behavior:`smooth`,block:`center`})},[i,e]);let C=async()=>{if(t)try{navigator?.clipboard?.writeText&&(await navigator.clipboard.writeText(t),m(!0),setTimeout(()=>m(!1),2e3))}catch{}},w=async()=>{if(!e)return;let t=`${e}${i?`#L${i}${a&&a!==i?`-L${a}`:``}`:``}`;try{navigator?.clipboard?.writeText&&(await navigator.clipboard.writeText(t),g(!0),setTimeout(()=>g(!1),2e3))}catch{}},T=e=>e==null?``:e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(1)} KB`:`${(e/1048576).toFixed(1)} MB`,E=o?.callers||[],D=o?.callees||[],O=E.length+D.length;return(0,b.jsxs)(`div`,{className:`nav-code-viewer-container density-${f}`,"data-testid":`navigator-code-viewer`,children:[(0,b.jsxs)(`div`,{className:`nav-code-viewer-header`,children:[(0,b.jsxs)(`div`,{className:`nav-code-viewer-meta`,children:[(0,b.jsxs)(`svg`,{width:`15`,height:`15`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,className:`nav-code-file-icon`,children:[(0,b.jsx)(`path`,{d:`M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z`}),(0,b.jsx)(`polyline`,{points:`14 2 14 8 20 8`}),(0,b.jsx)(`line`,{x1:`16`,y1:`13`,x2:`8`,y2:`13`}),(0,b.jsx)(`line`,{x1:`16`,y1:`17`,x2:`8`,y2:`17`})]}),(0,b.jsx)(`span`,{className:`nav-code-filename`,title:e,children:e}),(0,b.jsxs)(`span`,{className:`nav-code-stat-chip`,children:[n||S.length,` lines`]}),r!==void 0&&r>0&&(0,b.jsx)(`span`,{className:`nav-code-stat-chip`,children:T(r)})]}),(0,b.jsxs)(`div`,{className:`nav-code-viewer-actions`,children:[o?.symbol&&(0,b.jsxs)(`button`,{type:`button`,className:`nav-code-impact-toggle-btn nav-code-drawer-toggle ${v?`active`:``}`,onClick:()=>y(e=>!e),"aria-label":`Callers & Impact`,children:[(0,b.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,children:[(0,b.jsx)(`circle`,{cx:`12`,cy:`12`,r:`10`}),(0,b.jsx)(`path`,{d:`M12 16v-4M12 8h.01`})]}),(0,b.jsxs)(`span`,{children:[`Callers & Impact (`,O,`)`]})]}),(0,b.jsx)(`button`,{type:`button`,className:`nav-code-action-btn`,onClick:w,title:`Copy path and line permalink`,"aria-label":`Copy Permalink`,children:h?`Link Copied!`:`Copy Link`}),(0,b.jsx)(`button`,{type:`button`,className:`nav-code-action-btn`,onClick:C,title:`Copy entire file contents`,"aria-label":`Copy File Code`,children:p?`Copied!`:`Copy Code`}),l&&(0,b.jsx)(`button`,{type:`button`,className:`nav-code-action-btn`,onClick:l,title:`Refresh file`,"aria-label":`Refresh File`,children:`↻`})]})]}),c&&(0,b.jsx)(`div`,{className:`nav-code-error-banner`,role:`alert`,children:(0,b.jsxs)(`span`,{children:[`Failed to load file: `,c]})}),s&&(0,b.jsxs)(`div`,{className:`nav-code-loading-overlay`,children:[(0,b.jsx)(`div`,{className:`nav-omni-spinner`}),(0,b.jsx)(`span`,{children:`Loading source code...`})]}),(0,b.jsx)(`div`,{className:`nav-code-body`,tabIndex:0,children:S.length===0&&!s&&!c?(0,b.jsx)(`div`,{className:`nav-code-empty-state`,style:{padding:`24px`,color:`#64748b`,textAlign:`center`},children:`File is empty.`}):(0,b.jsx)(`div`,{className:`nav-code-lines-wrapper`,children:S.map((e,t)=>{let n=t+1;return(0,b.jsxs)(`div`,{ref:i!==void 0&&n===i?x:void 0,className:`nav-code-line-row ${i!==void 0&&n>=i&&n<=(a||i)?`nav-code-line-target`:``}`,"data-testid":`code-line-${n}`,children:[(0,b.jsx)(`div`,{className:`nav-code-line-number select-none`,children:n}),(0,b.jsx)(`div`,{className:`nav-code-line-content font-mono`,children:e||` `})]},n)})})}),v&&o?.symbol&&(0,b.jsxs)(`div`,{className:`nav-code-impact-drawer`,"data-testid":`code-impact-drawer`,children:[(0,b.jsxs)(`div`,{className:`nav-impact-drawer-header`,children:[(0,b.jsxs)(`div`,{className:`nav-impact-drawer-title`,children:[(0,b.jsx)(`span`,{className:`outline-kind-badge ${ge(o.symbol.kind)}`,children:o.symbol.kind}),(0,b.jsx)(`strong`,{className:`nav-impact-symbol-name`,children:o.symbol.name}),(0,b.jsxs)(`span`,{className:`nav-impact-symbol-lines`,children:[`Lines `,o.symbol.start_line,`–`,o.symbol.end_line]}),o.route&&(0,b.jsxs)(`span`,{className:`outline-method-badge ${he(o.route.http_method)}`,children:[o.route.http_method,` `,o.route.path_pattern]})]}),(0,b.jsx)(`button`,{type:`button`,className:`nav-impact-close-btn`,onClick:()=>y(!1),"aria-label":`Close Impact Drawer`,children:`✕`})]}),(0,b.jsxs)(`div`,{className:`nav-impact-drawer-body`,children:[(0,b.jsxs)(`div`,{className:`nav-impact-column`,children:[(0,b.jsxs)(`div`,{className:`nav-impact-column-title`,children:[`Incoming Callers (`,E.length,`)`]}),E.length===0?(0,b.jsx)(`div`,{className:`nav-impact-empty`,children:`No incoming callers detected`}):(0,b.jsx)(`div`,{className:`nav-impact-chips-list`,children:E.map((t,n)=>(0,b.jsxs)(`button`,{type:`button`,className:`nav-impact-chip caller`,onClick:()=>u&&u(t.source_filepath||e,t.source_symbol,t.source_symbol_id||void 0),children:[(0,b.jsx)(`span`,{className:`chip-name`,children:t.source_symbol||`anonymous`}),(0,b.jsx)(`span`,{className:`chip-path`,children:t.source_filepath}),t.line_number&&(0,b.jsxs)(`span`,{className:`chip-line`,children:[`:`,t.line_number]})]},t.id||n))})]}),(0,b.jsxs)(`div`,{className:`nav-impact-column`,children:[(0,b.jsxs)(`div`,{className:`nav-impact-column-title`,children:[`Outgoing Callees (`,D.length,`)`]}),D.length===0?(0,b.jsx)(`div`,{className:`nav-impact-empty`,children:`No outgoing callees detected`}):(0,b.jsx)(`div`,{className:`nav-impact-chips-list`,children:D.map((e,t)=>(0,b.jsxs)(`button`,{type:`button`,className:`nav-impact-chip callee`,onClick:()=>d&&d(e.target_filepath,e.target_symbol),children:[(0,b.jsx)(`span`,{className:`chip-name`,children:e.target_symbol}),e.target_filepath&&(0,b.jsx)(`span`,{className:`chip-path`,children:e.target_filepath}),e.line_number&&(0,b.jsxs)(`span`,{className:`chip-line`,children:[`:`,e.line_number]})]},e.id||t))})]})]})]})]})},be=({impact:e,fileContent:t,loadingContent:n=!1,contentError:r=null,targetStartLine:i,targetEndLine:a,activeInspectorTab:o,onChangeInspectorTab:s,onSelectCaller:c,onSelectCallee:l,density:u=`balanced`,loading:d=!1,onRefreshContent:f})=>{let[p,m]=(0,_.useState)(`intelligence`),[h,g]=(0,_.useState)(!1),v=o===void 0?p:o,y=s||m,x=async()=>{if(!e?.symbol)return;let{filepath:t,start_line:n,end_line:r}=e.symbol,i=`${t}#L${n}${r&&r!==n?`-L${r}`:``}`;try{navigator?.clipboard?.writeText&&await navigator.clipboard.writeText(i),g(!0),setTimeout(()=>g(!1),2e3)}catch{}},S=e?.symbol,C=e?.route,w=e?.callers||[],T=e?.callees||[],E=e?.imports||[],D=!S&&(t||n||r)||S&&v===`reader`;return(0,b.jsxs)(`div`,{className:`nav-inspector-pane density-${u}`,"data-testid":`navigator-inspector-container`,children:[(0,b.jsxs)(`div`,{className:`nav-inspector-header`,children:[(0,b.jsxs)(`div`,{className:`nav-inspector-title`,children:[(0,b.jsxs)(`svg`,{width:`15`,height:`15`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,b.jsx)(`circle`,{cx:`12`,cy:`12`,r:`10`}),(0,b.jsx)(`path`,{d:`M12 16v-4M12 8h.01`})]}),(0,b.jsx)(`span`,{children:D&&!S?`Document & File Reader`:`Code Intelligence & Impact`})]}),(0,b.jsxs)(`div`,{className:`nav-inspector-header-actions`,children:[S&&t&&(0,b.jsxs)(`div`,{className:`nav-inspector-tabs`,role:`tablist`,children:[(0,b.jsx)(`button`,{type:`button`,className:`inspector-tab-btn ${v===`intelligence`?`active`:``}`,onClick:()=>y(`intelligence`),children:`Symbol Intelligence`}),(0,b.jsx)(`button`,{type:`button`,className:`inspector-tab-btn ${v===`reader`?`active`:``}`,onClick:()=>y(`reader`),children:`Read Full File`})]}),S&&v===`intelligence`&&(0,b.jsx)(`button`,{type:`button`,className:`nav-copy-permalink-btn`,onClick:x,title:`Copy file path & line range permalink`,"aria-label":`Copy Permalink`,children:h?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,children:(0,b.jsx)(`polyline`,{points:`20 6 9 17 4 12`})}),(0,b.jsx)(`span`,{children:`Copied!`})]}):(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,children:[(0,b.jsx)(`path`,{d:`M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71`}),(0,b.jsx)(`path`,{d:`M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71`})]}),(0,b.jsx)(`span`,{children:`Copy Permalink`})]})})]})]}),(0,b.jsx)(`div`,{className:`nav-inspector-content`,children:D&&t?t.filepath.toLowerCase().endsWith(`.md`)||t.filepath.toLowerCase().endsWith(`.markdown`)?(0,b.jsx)(ve,{filepath:t.filepath,content:t.content,totalLines:t.total_lines,sizeBytes:t.size_bytes,loading:n,error:r,onRefresh:f}):(0,b.jsx)(ye,{filepath:t.filepath,content:t.content,totalLines:t.total_lines,sizeBytes:t.size_bytes,targetStartLine:i||S?.start_line,targetEndLine:a||S?.end_line,impact:e,loading:n,error:r,onRefresh:f,onSelectCaller:c,onSelectCallee:l,density:u}):D&&n?(0,b.jsxs)(`div`,{className:`nav-inspector-skeleton`,"data-testid":`inspector-loading-skeleton`,children:[(0,b.jsx)(`div`,{className:`skeleton-header shimmer`}),(0,b.jsx)(`div`,{className:`skeleton-metrics shimmer`}),(0,b.jsx)(`div`,{className:`skeleton-block shimmer`})]}):d?(0,b.jsxs)(`div`,{className:`nav-inspector-skeleton`,"data-testid":`inspector-loading-skeleton`,children:[(0,b.jsx)(`div`,{className:`skeleton-header shimmer`}),(0,b.jsx)(`div`,{className:`skeleton-metrics shimmer`}),(0,b.jsx)(`div`,{className:`skeleton-block shimmer`}),(0,b.jsx)(`div`,{className:`skeleton-block shimmer`})]}):!e||!S?(0,b.jsxs)(`div`,{className:`nav-empty-state`,children:[(0,b.jsx)(`div`,{className:`empty-icon`,children:`🔍`}),(0,b.jsx)(`h4`,{children:`No File or Symbol Selected`}),(0,b.jsx)(`p`,{children:`Select a file from the tree to read its documentation or select a symbol to inspect callers and dependencies.`})]}):(0,b.jsxs)(`div`,{className:`nav-inspector-body`,children:[(0,b.jsxs)(`div`,{className:`inspector-card symbol-summary-card`,children:[(0,b.jsxs)(`div`,{className:`summary-title-row`,children:[(0,b.jsx)(`span`,{className:`symbol-kind-badge ${ge(S.kind)}`,children:S.kind}),(0,b.jsx)(`h3`,{className:`summary-name`,title:S.full_symbol||S.name,children:S.name})]}),(0,b.jsxs)(`div`,{className:`summary-location-row`,children:[(0,b.jsxs)(`span`,{className:`summary-file-path`,children:[(0,b.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,children:[(0,b.jsx)(`path`,{d:`M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z`}),(0,b.jsx)(`polyline`,{points:`13 2 13 9 20 9`})]}),S.filepath]}),(0,b.jsxs)(`span`,{className:`summary-line-range`,children:[`L`,S.start_line,` - L`,S.end_line]})]})]}),(0,b.jsxs)(`div`,{className:`inspector-metrics-grid`,children:[(0,b.jsxs)(`div`,{className:`metric-box`,children:[(0,b.jsx)(`span`,{className:`metric-label`,children:`Incoming Callers`}),(0,b.jsx)(`span`,{className:`metric-value`,"data-testid":`metric-callers`,children:w.length})]}),(0,b.jsxs)(`div`,{className:`metric-box`,children:[(0,b.jsx)(`span`,{className:`metric-label`,children:`Outgoing Callees`}),(0,b.jsx)(`span`,{className:`metric-value`,"data-testid":`metric-callees`,children:T.length})]}),(0,b.jsxs)(`div`,{className:`metric-box`,children:[(0,b.jsx)(`span`,{className:`metric-label`,children:`Total Imports`}),(0,b.jsx)(`span`,{className:`metric-value`,"data-testid":`metric-imports`,children:E.length})]}),(0,b.jsxs)(`div`,{className:`metric-box`,children:[(0,b.jsx)(`span`,{className:`metric-label`,children:`Language`}),(0,b.jsx)(`span`,{className:`metric-value metric-lang`,"data-testid":`metric-scope`,children:S.language||`code`})]})]}),C&&(0,b.jsxs)(`div`,{className:`inspector-card route-card`,children:[(0,b.jsxs)(`div`,{className:`card-section-title`,children:[(0,b.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,children:[(0,b.jsx)(`circle`,{cx:`12`,cy:`12`,r:`10`}),(0,b.jsx)(`line`,{x1:`2`,y1:`12`,x2:`22`,y2:`12`}),(0,b.jsx)(`path`,{d:`M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z`})]}),(0,b.jsx)(`span`,{children:`API Route Mapping`})]}),(0,b.jsxs)(`div`,{className:`route-detail-row`,children:[(0,b.jsx)(`span`,{className:`route-method-badge ${he(C.http_method)}`,children:C.http_method}),(0,b.jsx)(`span`,{className:`route-path-code`,children:C.path_pattern}),C.framework&&(0,b.jsx)(`span`,{className:`route-framework-tag`,children:C.framework})]})]}),S.signature&&(0,b.jsxs)(`div`,{className:`inspector-card code-preview-card`,children:[(0,b.jsxs)(`div`,{className:`card-section-title`,children:[(0,b.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,children:[(0,b.jsx)(`polyline`,{points:`16 18 22 12 16 6`}),(0,b.jsx)(`polyline`,{points:`8 6 2 12 8 18`})]}),(0,b.jsx)(`span`,{children:`Signature`})]}),(0,b.jsx)(`pre`,{className:`signature-code-block`,children:(0,b.jsx)(`code`,{children:S.signature})})]}),S.docstring&&(0,b.jsxs)(`div`,{className:`inspector-card docstring-card`,children:[(0,b.jsxs)(`div`,{className:`card-section-title`,children:[(0,b.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,children:[(0,b.jsx)(`path`,{d:`M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z`}),(0,b.jsx)(`polyline`,{points:`14 2 14 8 20 8`}),(0,b.jsx)(`line`,{x1:`16`,y1:`13`,x2:`8`,y2:`13`}),(0,b.jsx)(`line`,{x1:`16`,y1:`17`,x2:`8`,y2:`17`}),(0,b.jsx)(`polyline`,{points:`10 9 9 9 8 9`})]}),(0,b.jsx)(`span`,{children:`Documentation`})]}),(0,b.jsx)(`p`,{className:`docstring-text`,children:S.docstring})]}),(0,b.jsxs)(`div`,{className:`inspector-card relations-card callers-section`,children:[(0,b.jsxs)(`div`,{className:`card-section-title`,children:[(0,b.jsx)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,children:(0,b.jsx)(`path`,{d:`M12 19V5M5 12l7-7 7 7`})}),(0,b.jsxs)(`span`,{children:[`Incoming Callers (`,w.length,`)`]})]}),w.length===0?(0,b.jsx)(`div`,{className:`nav-no-items-text`,children:`No incoming callers found in this repository.`}):(0,b.jsx)(`div`,{className:`relation-list`,children:w.map((e,t)=>(0,b.jsxs)(`div`,{"data-testid":`caller-item-${e.id??t}`,className:`relation-item caller-item`,onClick:()=>{e.source_filepath&&c&&c(e.source_filepath,e.source_symbol,e.source_symbol_id??void 0)},role:`button`,tabIndex:0,title:`Jump to ${e.source_symbol||`caller`} in ${e.source_filepath||``}`,children:[(0,b.jsxs)(`div`,{className:`relation-top`,children:[(0,b.jsx)(`span`,{className:`rel-symbol-name`,children:e.source_symbol||`Unknown Caller`}),(0,b.jsx)(`span`,{className:`rel-jump-hint`,"aria-hidden":`true`,children:`Jump ↗`})]}),(0,b.jsxs)(`div`,{className:`relation-bottom`,children:[e.source_filepath&&(0,b.jsx)(`span`,{className:`rel-filepath`,children:e.source_filepath}),e.all_lines?(0,b.jsxs)(`span`,{className:`rel-line`,title:`Lines: ${e.all_lines}`,children:[`L`,e.all_lines.includes(`,`)?e.all_lines.split(`,`).slice(0,3).join(`, L`)+(e.all_lines.split(`,`).length>3?`...`:``):e.all_lines]}):e.line_number?(0,b.jsxs)(`span`,{className:`rel-line`,children:[`L`,e.line_number]}):null,e.call_count&&e.call_count>1?(0,b.jsxs)(`span`,{className:`rel-count-badge`,children:[e.call_count,` calls`]}):null,e.relationship_type&&(0,b.jsx)(`span`,{className:`rel-type-tag`,children:e.relationship_type})]})]},e.id??t))})]}),(0,b.jsxs)(`div`,{className:`inspector-card relations-card dependencies-section`,children:[(0,b.jsxs)(`div`,{className:`card-section-title`,children:[(0,b.jsx)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,children:(0,b.jsx)(`path`,{d:`M12 5v14M5 12l7 7 7-7`})}),(0,b.jsxs)(`span`,{children:[`Outgoing Dependencies (`,T.length+E.length,`)`]})]}),T.length===0&&E.length===0?(0,b.jsx)(`div`,{className:`nav-no-items-text`,children:`No outgoing calls or imports.`}):(0,b.jsxs)(`div`,{className:`relation-list`,children:[T.map((e,t)=>(0,b.jsxs)(`div`,{"data-testid":`callee-item-${e.id??t}`,className:`relation-item callee-item ${e.target_filepath?`is-clickable`:``}`,onClick:()=>{e.target_filepath&&l&&l(e.target_filepath,e.target_symbol)},role:e.target_filepath?`button`:void 0,tabIndex:e.target_filepath?0:void 0,title:e.target_filepath?`Jump to ${e.target_symbol} in ${e.target_filepath}`:void 0,onKeyDown:t=>{e.target_filepath&&l&&(t.key===`Enter`||t.key===` `)&&(t.preventDefault(),l(e.target_filepath,e.target_symbol))},children:[(0,b.jsxs)(`div`,{className:`relation-top`,children:[(0,b.jsx)(`span`,{className:`rel-symbol-name`,children:e.target_symbol}),(0,b.jsxs)(`div`,{className:`rel-top-right`,children:[(0,b.jsx)(`span`,{className:`rel-type-badge`,children:e.relationship_type||`CALLS`}),e.target_filepath&&(0,b.jsx)(`span`,{className:`rel-jump-hint`,"aria-hidden":`true`,children:`Jump ↗`})]})]}),(0,b.jsxs)(`div`,{className:`relation-bottom`,children:[e.target_filepath&&(0,b.jsx)(`span`,{className:`rel-filepath`,children:e.target_filepath}),e.all_lines?(0,b.jsxs)(`span`,{className:`rel-line`,title:`Lines: ${e.all_lines}`,children:[`L`,e.all_lines.includes(`,`)?e.all_lines.split(`,`).slice(0,3).join(`, L`)+(e.all_lines.split(`,`).length>3?`...`:``):e.all_lines]}):e.line_number?(0,b.jsxs)(`span`,{className:`rel-line`,children:[`L`,e.line_number]}):null,e.call_count&&e.call_count>1?(0,b.jsxs)(`span`,{className:`rel-count-badge`,children:[e.call_count,` calls`]}):null]})]},e.id??`callee-${t}`)),E.map((e,t)=>(0,b.jsxs)(`div`,{className:`relation-item import-item`,children:[(0,b.jsxs)(`div`,{className:`relation-top`,children:[(0,b.jsx)(`span`,{className:`rel-symbol-name`,children:e.target_symbol}),(0,b.jsx)(`span`,{className:`rel-type-badge import-badge`,children:`IMPORTS`})]}),(0,b.jsxs)(`div`,{className:`relation-bottom`,children:[e.all_lines?(0,b.jsxs)(`span`,{className:`rel-line`,title:`Lines: ${e.all_lines}`,children:[`L`,e.all_lines.includes(`,`)?e.all_lines.split(`,`).slice(0,3).join(`, L`)+(e.all_lines.split(`,`).length>3?`...`:``):e.all_lines]}):e.line_number?(0,b.jsxs)(`span`,{className:`rel-line`,children:[`L`,e.line_number]}):null,e.import_count&&e.import_count>1?(0,b.jsxs)(`span`,{className:`rel-count-badge`,children:[e.import_count,` imports`]}):null]})]},e.id??`import-${t}`))]})]})]})})]})},xe=`contextcortex_navigator_density`,Se=({initialRepo:e=`__all__`,initialPath:t,initialSymbolId:n})=>{let[r,i]=(0,_.useState)(()=>{let e=localStorage.getItem(xe);return e===`compact`||e===`balanced`||e===`spacious`?e:`balanced`}),a=e=>{i(e),localStorage.setItem(xe,e)},[o,s]=(0,_.useState)([]),[c,l]=(0,_.useState)(e),[u,d]=(0,_.useState)(null),[f,p]=(0,_.useState)(!1),[m,h]=(0,_.useState)(``),[g,v]=(0,_.useState)(t||null),[y,x]=(0,_.useState)(null),[S,C]=(0,_.useState)(!1),[w,T]=(0,_.useState)(n||null),[E,D]=(0,_.useState)(null),[O,k]=(0,_.useState)(!1),[ee,A]=(0,_.useState)(null),[te,ne]=(0,_.useState)(!1),[re,j]=(0,_.useState)(null),[M,N]=(0,_.useState)(`intelligence`),[P,ie]=(0,_.useState)(void 0),[ae,F]=(0,_.useState)(void 0),[I,L]=(0,_.useState)(null);(0,_.useEffect)(()=>{(async()=>{try{let e=await fetch(`/admin/api/repositories`);if(e.ok){let t=await e.json();s(Array.isArray(t)?t:[])}else{let e=await fetch(`/admin/api/repos`);if(e.ok){let t=await e.json();s(Array.isArray(t)?t:[])}}}catch(e){console.error(`Error fetching repositories:`,e)}})()},[]);let oe=(0,_.useCallback)(async e=>{p(!0),L(null);try{let t=await fetch(`/admin/api/navigator/tree?repo=${encodeURIComponent(e)}`);if(!t.ok)throw Error(`Failed to load tree: ${t.status} ${t.statusText}`);let n=await t.json();d(n)}catch(e){console.error(`Error fetching codebase tree:`,e),L(e.message||`Failed to load codebase tree`)}finally{p(!1)}},[]);(0,_.useEffect)(()=>{oe(c)},[c,oe]);let se=(0,_.useCallback)(async(e,t)=>{k(!0);try{let n=await fetch(`/admin/api/navigator/symbol-impact?repo=${encodeURIComponent(e)}&symbol_id=${t}`);if(!n.ok)throw Error(`Failed to load symbol impact: ${n.status}`);let r=await n.json();D(r)}catch(e){console.error(`Error fetching symbol impact:`,e),D(null)}finally{k(!1)}},[]),ce=(0,_.useCallback)(async(e,t)=>{ne(!0),j(null);try{let n=await fetch(`/admin/api/files/read?path=${encodeURIComponent(t)}&repo=${encodeURIComponent(e)}`);if(!n.ok)throw Error(`Failed to read file: ${n.statusText}`);let r=await n.json();A(r)}catch(e){console.error(`Error fetching file content:`,e),j(e.message||`Failed to read file content`),A(null)}finally{ne(!1)}},[]),le=(0,_.useCallback)(async(e,t,n,r)=>{C(!0);try{let i=await fetch(`/admin/api/navigator/file-outline?repo=${encodeURIComponent(e)}&filepath=${encodeURIComponent(t)}`);if(!i.ok)throw Error(`Failed to load file outline: ${i.status}`);let a=await i.json();if(x(a),a.symbols&&a.symbols.length>0){let t;typeof n==`number`?t=a.symbols.find(e=>e.id===n):typeof n==`string`&&(t=a.symbols.find(e=>e.name===n||e.full_symbol===n));let i=t||a.symbols[0];T(i.id),N(r||`intelligence`),await se(e,i.id)}else T(null),D(null),N(`reader`)}catch(e){console.error(`Error fetching outline:`,e),x(null),T(null),D(null),N(`reader`)}finally{C(!1)}},[se]);return(0,b.jsxs)(`div`,{className:`code-navigator-container density-${r}`,"data-testid":`code-navigator-container`,children:[(0,b.jsx)(fe,{repos:o,selectedRepo:c,onSelectRepo:e=>{l(e),v(null),x(null),T(null),D(null),A(null),ie(void 0),F(void 0)},density:r,onChangeDensity:a,searchQuery:m,onSearchChange:h,onSelectSearchResult:e=>{let t=e.repo&&e.repo!==`__all__`?e.repo:c;e.repo&&e.repo!==c&&c!==`__all__`&&l(e.repo),v(e.filepath),ie(e.start_line),F(e.end_line),ce(t,e.filepath),e.type===`symbol`&&e.symbol_id?(T(e.symbol_id),le(t,e.filepath,e.symbol_id,`reader`),se(t,e.symbol_id),N(`reader`)):(le(t,e.filepath,void 0,`reader`),N(`reader`))},totalFiles:u?.total_files??0,totalSymbols:u?.total_symbols??0,onRefresh:()=>oe(c),loading:f}),I&&(0,b.jsxs)(`div`,{className:`nav-error-banner`,role:`alert`,children:[(0,b.jsxs)(`svg`,{width:`15`,height:`15`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,children:[(0,b.jsx)(`circle`,{cx:`12`,cy:`12`,r:`10`}),(0,b.jsx)(`line`,{x1:`12`,y1:`8`,x2:`12`,y2:`12`}),(0,b.jsx)(`line`,{x1:`12`,y1:`16`,x2:`12.01`,y2:`16`})]}),(0,b.jsx)(`span`,{children:I}),(0,b.jsx)(`button`,{type:`button`,onClick:()=>L(null),className:`error-close-btn`,children:`✕`})]}),(0,b.jsxs)(`div`,{className:`nav-panes-layout`,children:[(0,b.jsx)(`section`,{className:`nav-pane-column nav-pane-tree`,"aria-label":`File Tree`,children:(0,b.jsx)(me,{nodes:u?.tree??[],selectedPath:g,onSelectFile:e=>{e.is_dir||(v(e.path),ie(void 0),F(void 0),le(c,e.path),ce(c,e.path))},filterText:m,onFilterChange:h,density:r,loading:f})}),(0,b.jsx)(`section`,{className:`nav-pane-column nav-pane-outline`,"aria-label":`Symbol and Route Outline`,children:(0,b.jsx)(_e,{outline:y,selectedSymbolId:w,onSelectSymbol:e=>{T(e.id),ie(e.start_line),F(e.end_line),N(`intelligence`),se(c,e.id)},onReadDoc:()=>{N(`reader`)},density:r,loading:S})}),(0,b.jsx)(`section`,{className:`nav-pane-column nav-pane-inspector`,"aria-label":`Code Intelligence and Impact`,children:(0,b.jsx)(be,{impact:E,fileContent:ee,loadingContent:te,contentError:re,targetStartLine:P,targetEndLine:ae,activeInspectorTab:M,onChangeInspectorTab:N,onSelectCaller:(e,t,n)=>{v(e),ie(void 0),F(void 0),le(c,e,n??t),ce(c,e)},onSelectCallee:(e,t)=>{e&&(v(e),ie(void 0),F(void 0),le(c,e,t),ce(c,e))},density:r,loading:O,onRefreshContent:()=>g&&ce(c,g)})})]})]})};function Ce({data:e,onConfirm:t,onCancel:n,isIngesting:r=!1}){let[i,a]=(0,_.useState)(`pages`),[o,s]=(0,_.useState)(0),c=e.total_pages||e.pages.length||1,l=e.pages[o]||{page_number:1,text:``,char_count:0,ocr_applied:!1};return(0,b.jsx)(`div`,{className:`modal-backdrop`,"data-testid":`pdf-preview-modal-backdrop`,children:(0,b.jsxs)(`div`,{className:`glass-card modal-card`,style:{maxWidth:`850px`,width:`95%`,maxHeight:`90vh`,display:`flex`,flexDirection:`column`},"data-testid":`pdf-preview-modal`,children:[(0,b.jsxs)(`div`,{className:`modal-header`,style:{marginBottom:`12px`},children:[(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`h2`,{style:{display:`flex`,alignItems:`center`,gap:`8px`,margin:0,fontSize:`1.25rem`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-pdf text-red-500 mr-2`,style:{color:`#ef4444`}}),(0,b.jsx)(`span`,{children:`PDF Extraction Preview`})]}),(0,b.jsx)(`div`,{style:{fontSize:`0.85rem`,color:`var(--text-muted)`,marginTop:`4px`},children:(0,b.jsx)(`code`,{children:e.filename})})]}),(0,b.jsx)(`button`,{type:`button`,className:`btn-close`,onClick:n,"aria-label":`Close modal`,children:`×`})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,justifyContent:`space-between`,flexWrap:`wrap`,gap:`10px`,padding:`10px 14px`,background:`rgba(255, 255, 255, 0.04)`,borderRadius:`6px`,marginBottom:`14px`,border:`1px solid var(--border-card)`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`14px`,flexWrap:`wrap`,alignItems:`center`,fontSize:`0.85rem`},children:[(0,b.jsxs)(`span`,{children:[(0,b.jsx)(`strong`,{children:`Total Pages:`}),` `,(0,b.jsx)(`span`,{className:`badge badge-primary`,"data-testid":`total-pages-badge`,children:e.total_pages})]}),(0,b.jsxs)(`span`,{children:[(0,b.jsx)(`strong`,{children:`Total Characters:`}),` `,(0,b.jsx)(`span`,{className:`badge badge-primary`,"data-testid":`total-chars-badge`,children:e.total_characters.toLocaleString()})]}),(0,b.jsxs)(`span`,{children:[(0,b.jsx)(`strong`,{children:`OCR Applied:`}),` `,(0,b.jsxs)(`span`,{className:`badge ${e.ocr_pages_count>0?`badge-warning`:`badge-secondary`}`,"data-testid":`ocr-count-badge`,children:[e.ocr_pages_count,` `,e.ocr_pages_count===1?`page`:`pages`]})]})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`6px`},children:[(0,b.jsxs)(`button`,{type:`button`,className:`btn ${i===`pages`?`btn-primary`:`btn-secondary`}`,style:{padding:`4px 10px`,fontSize:`0.82rem`},onClick:()=>a(`pages`),"data-testid":`tab-pages-btn`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-lines`,style:{marginRight:`5px`}}),` Page Text`]}),(0,b.jsxs)(`button`,{type:`button`,className:`btn ${i===`chunks`?`btn-primary`:`btn-secondary`}`,style:{padding:`4px 10px`,fontSize:`0.82rem`},onClick:()=>a(`chunks`),"data-testid":`tab-chunks-btn`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-layer-group`,style:{marginRight:`5px`}}),` Sample Chunks (`,e.sample_chunks?.length||0,`)`]})]})]}),(0,b.jsx)(`div`,{style:{flex:1,overflowY:`auto`,minHeight:`320px`,display:`flex`,flexDirection:`column`},children:i===`pages`?(0,b.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,justifyContent:`space-between`,flexWrap:`wrap`,gap:`8px`,marginBottom:`10px`,padding:`6px 10px`,background:`rgba(0, 0, 0, 0.2)`,borderRadius:`6px`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`},children:[(0,b.jsxs)(`button`,{type:`button`,className:`btn btn-secondary`,style:{padding:`3px 8px`,fontSize:`0.8rem`},onClick:()=>{o>0&&s(e=>e-1)},disabled:o<=0,"aria-label":`Previous Page`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-chevron-left`}),` Prev`]}),(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,fontSize:`0.85rem`},children:[(0,b.jsx)(`span`,{children:`Page`}),(0,b.jsx)(`select`,{value:o,onChange:t=>{let n=parseInt(t.target.value,10);!isNaN(n)&&n>=0&&n(0,b.jsxs)(`option`,{value:t,children:[e.page_number,` `,e.ocr_applied?`(OCR)`:``]},e.page_number))}),(0,b.jsxs)(`span`,{children:[`of `,c]})]}),(0,b.jsxs)(`button`,{type:`button`,className:`btn btn-secondary`,style:{padding:`3px 8px`,fontSize:`0.8rem`},onClick:()=>{oe+1)},disabled:o>=e.pages.length-1,"aria-label":`Next Page`,children:[`Next `,(0,b.jsx)(`i`,{className:`fa-solid fa-chevron-right`})]})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`},children:[l.ocr_applied?(0,b.jsxs)(`span`,{className:`badge badge-warning`,style:{display:`inline-flex`,alignItems:`center`,gap:`4px`},"data-testid":`page-ocr-applied-badge`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-eye`}),` OCR Fallback`]}):(0,b.jsxs)(`span`,{className:`badge badge-secondary`,style:{display:`inline-flex`,alignItems:`center`,gap:`4px`},"data-testid":`page-digital-badge`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-font`}),` Digital`]}),(0,b.jsxs)(`span`,{className:`text-muted`,style:{fontSize:`0.8rem`},children:[l.char_count.toLocaleString(),` chars`]})]})]}),(0,b.jsx)(`div`,{style:{flex:1,minHeight:`260px`,position:`relative`},children:l.text?(0,b.jsx)(`pre`,{className:`search-hit-code`,style:{maxHeight:`400px`,overflowY:`auto`,margin:0,whiteSpace:`pre-wrap`,wordBreak:`break-word`,fontSize:`0.85rem`,lineHeight:`1.5`,padding:`12px`,background:`rgba(0, 0, 0, 0.3)`,borderRadius:`6px`},"data-testid":`page-text-content`,children:l.text}):(0,b.jsxs)(`div`,{className:`empty-state`,style:{padding:`30px`,textAlign:`center`,color:`var(--text-muted)`},"data-testid":`page-empty-text`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-circle-question`,style:{fontSize:`1.5rem`,marginBottom:`8px`,display:`block`}}),`No text extracted from Page `,l.page_number,`.`]})})]}):(0,b.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`10px`},"data-testid":`chunks-container`,children:[(0,b.jsx)(`div`,{style:{fontSize:`0.82rem`,color:`var(--text-muted)`,marginBottom:`4px`},children:`Simulated chunking for vector ingestion (approx. 1000 chars / chunk with 200 char overlap):`}),!e.sample_chunks||e.sample_chunks.length===0?(0,b.jsx)(`div`,{className:`empty-state`,style:{padding:`24px`},children:`No vector chunks generated for this document.`}):e.sample_chunks.map(e=>(0,b.jsxs)(`div`,{style:{border:`1px solid var(--border-card)`,borderRadius:`6px`,background:`rgba(0, 0, 0, 0.25)`,padding:`10px 12px`},"data-testid":`chunk-card-${e.chunk_index}`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,fontSize:`0.8rem`,marginBottom:`6px`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`8px`,alignItems:`center`},children:[(0,b.jsxs)(`span`,{className:`badge badge-primary`,children:[`Chunk #`,e.chunk_index+1]}),(0,b.jsxs)(`span`,{className:`text-muted`,children:[e.heading,` (Page `,e.page_number,`)`]})]}),(0,b.jsxs)(`span`,{className:`badge badge-secondary`,children:[e.char_count,` chars`]})]}),(0,b.jsx)(`pre`,{style:{margin:0,whiteSpace:`pre-wrap`,wordBreak:`break-word`,fontFamily:`var(--font-family-mono)`,fontSize:`0.8rem`,color:`var(--text)`,lineHeight:`1.4`,background:`rgba(0, 0, 0, 0.2)`,padding:`8px`,borderRadius:`4px`},children:e.preview})]},e.chunk_index))]})}),(0,b.jsxs)(`div`,{className:`modal-footer`,style:{marginTop:`16px`,paddingTop:`12px`,borderTop:`1px solid var(--border-card)`,display:`flex`,justifyContent:`flex-end`,gap:`10px`},children:[(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:n,disabled:r,children:`Cancel`}),(0,b.jsxs)(`button`,{type:`button`,className:`btn btn-primary`,onClick:t,disabled:r,"data-testid":`confirm-ingest-btn`,children:[(0,b.jsx)(`i`,{className:`fa-solid ${r?`fa-spinner fa-spin`:`fa-database`}`,style:{marginRight:`6px`}}),r?`Ingesting...`:`Confirm & Ingest to Vector DB`]})]})]})})}function we({refreshStats:e}){let t=S(),[n,r]=(0,_.useState)(null),[i,a]=(0,_.useState)(``),[o,s]=(0,_.useState)(!1),[c,l]=(0,_.useState)(!1),[u,d]=(0,_.useState)(``),[f,p]=(0,_.useState)(``),[m,h]=(0,_.useState)(`local_storage`),[g,v]=(0,_.useState)(``),[y,x]=(0,_.useState)(null),[C,w]=(0,_.useState)(!1),[T,E]=(0,_.useState)(!1),[D,O]=(0,_.useState)(!1),[k,ee]=(0,_.useState)(null),[A,te]=(0,_.useState)(!1),[ne,re]=(0,_.useState)(!1),[j,M]=(0,_.useState)(null),[N,P]=(0,_.useState)(``),[ie,ae]=(0,_.useState)(``),[F,I]=(0,_.useState)(!1),[L,oe]=(0,_.useState)(!1),[se,ce]=(0,_.useState)(!1),[le,ue]=(0,_.useState)(null),[de,fe]=(0,_.useState)(null),[pe,me]=(0,_.useState)(``),[he,ge]=(0,_.useState)(`local_storage`),[_e,ve]=(0,_.useState)(``),[ye,be]=(0,_.useState)(!1),xe=e=>{if(!e||e===0)return`0 B`;let t=1024,n=[`B`,`KB`,`MB`,`GB`],r=Math.floor(Math.log(e)/Math.log(t));return parseFloat((e/t**r).toFixed(1))+` `+n[r]},Se=e=>{if(!e)return`-`;let t=e<1e11?e*1e3:e;return new Date(t).toLocaleString()},we=(0,_.useCallback)(async(e=i)=>{s(!0);try{let t=e?`/admin/api/storage/tree?folder=${encodeURIComponent(e)}`:`/admin/api/storage/tree`,n=await fetch(t);if(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.error||`Failed to load storage tree`)}let i=await n.json();r(i),a(i.current_folder||``)}catch(e){t.error(`Error loading storage: `+e.message),console.error(`Error loading storage tree:`,e)}finally{s(!1)}},[i,t]);(0,_.useEffect)(()=>{we(``)},[]);let Te=e=>{we(e)},Ee=()=>{if(!i)return;let e=i.split(`/`).filter(Boolean);e.pop(),Te(e.join(`/`))},De=e=>{let t=e===void 0?i:e;d(t?`${t}/`:``),p(``),h(`local_storage`),v(``),x(null),l(!0)},Oe=e=>{if(e.preventDefault(),w(!1),e.dataTransfer.files&&e.dataTransfer.files.length>0){let t=e.dataTransfer.files[0];Ne(t)}},ke=e=>{e.target.files&&e.target.files.length>0&&Ne(e.target.files[0])},Ae=async(e,n,r,a)=>{ce(!0);try{let t=new FormData;t.append(`file`,e),t.append(`ocr_fallback`,`true`);let o=await fetch(`/admin/api/storage/pdf/preview`,{method:`POST`,body:t}),s=await o.json();if(!o.ok)throw Error(s.error||`Failed to generate PDF preview`);fe(e),me(n||(i?`${i}/${e.name}`:e.name)),ge(r||m||`local_storage`),ve(a||f||``),ue(s),l(!1),oe(!0)}catch(e){t.error(`PDF preview error: ${e.message}`)}finally{ce(!1)}},je=async()=>{if(de){be(!0);try{let n=new FormData;n.append(`file`,de),n.append(`path`,pe||de.name),n.append(`repo`,he||`local_storage`),_e&&n.append(`category`,_e);let r=await fetch(`/admin/api/storage/upload`,{method:`POST`,body:n}),a=await r.json();if(!r.ok)throw Error(a.error||`Failed to ingest PDF`);oe(!1),fe(null),ue(null);let o=a.chunks_indexed??0;t.success(`PDF uploaded and indexed (${o} chunks)`),we(i),e&&e()}catch(e){t.error(`PDF ingestion error: ${e.message}`)}finally{be(!1)}}},Me=()=>{oe(!1),fe(null),ue(null)},Ne=e=>{x(e);let t=i?`${i}/${e.name}`:e.name;if(d(t),e.name.toLowerCase().endsWith(`.pdf`)||e.type===`application/pdf`){v(``),Ae(e,t,m,f);return}let n=new FileReader;n.onload=e=>{typeof e.target?.result==`string`&&v(e.target.result)},n.readAsText(e)},Pe=async n=>{if(n.preventDefault(),!u.trim()){t.error(`File path is required`);return}if(y&&y.name.toLowerCase().endsWith(`.pdf`)){await Ae(y,u.trim(),m.trim(),f.trim());return}E(!0);try{let n;if(y&&!g){let e=new FormData;e.append(`file`,y),e.append(`path`,u.trim()),e.append(`repo`,m.trim()||`local_storage`),f.trim()&&e.append(`category`,f.trim()),n=await fetch(`/admin/api/storage/upload`,{method:`POST`,body:e})}else n=await fetch(`/admin/api/storage/upload`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({path:u.trim(),content:g,repo:m.trim()||`local_storage`,category:f.trim()||null})});let r=await n.json();if(!n.ok)throw Error(r.error||`Failed to upload file`);l(!1);let a=r.chunks_indexed??0;t.success(`File uploaded and indexed (${a} chunks)`),we(i),e&&e()}catch(e){t.error(`Upload error: ${e.message}`)}finally{E(!1)}},Fe=async e=>{te(!0),O(!0);try{let t=await fetch(`/admin/api/storage/file?path=${encodeURIComponent(e.rel_path)}`),n=await t.json();if(!t.ok)throw Error(n.error||`Failed to read file`);ee(n)}catch(e){t.error(`Preview error: ${e.message}`),O(!1)}finally{te(!1)}},Ie=async e=>{if(M(e),ae(``),re(!0),e.rel_path.toLowerCase().endsWith(`.pdf`)){P(``);return}try{let t=await fetch(`/admin/api/storage/file?path=${encodeURIComponent(e.rel_path)}`),n=await t.json();t.ok&&n.content!==void 0?P(n.content):P(``)}catch{P(``)}},Le=async n=>{if(n.preventDefault(),j){if(j.rel_path.toLowerCase().endsWith(`.pdf`)){t.error(`PDF files cannot be edited directly via text editor. Upload a new PDF file instead.`);return}I(!0);try{let n=await fetch(`/admin/api/storage/file`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({path:j.rel_path,content:N,repo:`local_storage`,category:ie.trim()||``})}),r=await n.json();if(!n.ok)throw Error(r.error||`Failed to update file`);re(!1);let a=r.chunks_indexed??0;t.success(`File updated and indexed (${a} chunks)`),we(i),e&&e()}catch(e){t.error(`Replace error: ${e.message}`)}finally{I(!1)}}},Re=async n=>{if(window.confirm(`Are you sure you want to delete '${n.name}' from local storage and purge its vector embeddings?`))try{let r=await fetch(`/admin/api/storage/file?path=${encodeURIComponent(n.rel_path)}`,{method:`DELETE`}),a=await r.json();if(!r.ok)throw Error(a.error||`Failed to delete file`);t.success(`File deleted successfully: ${n.name}`),we(i),e&&e()}catch(e){t.error(`Delete error: ${e.message}`)}},ze=i?i.split(`/`).filter(Boolean):[];return(0,b.jsxs)(`div`,{className:`tab-content active`,children:[(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`div`,{className:`card-header-btn`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-hard-drive`}),` Local Storage Explorer`]}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Upload, browse, inspect, replace, and delete managed documents in ContextCortex local storage with real-time vector indexing.`})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`10px`},children:[(0,b.jsxs)(`button`,{className:`btn btn-secondary`,onClick:()=>we(i),title:`Refresh directory`,disabled:o,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-arrows-rotate ${o?`fa-spin`:``}`}),` Refresh`]}),(0,b.jsxs)(`button`,{className:`btn btn-primary`,onClick:()=>De(),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-upload`}),` Upload File`]})]})]}),(0,b.jsxs)(`div`,{className:`browser-breadcrumbs`,style:{display:`flex`,alignItems:`center`,justifyContent:`space-between`,flexWrap:`wrap`,gap:`10px`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,flexWrap:`wrap`},children:[(0,b.jsx)(`span`,{className:`label`,style:{fontWeight:600},children:`Location:`}),(0,b.jsxs)(`button`,{className:`btn-icon`,style:{padding:`2px 6px`,fontSize:`0.85rem`,color:i===``?`var(--primary)`:`var(--text)`},onClick:()=>Te(``),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-house`}),` root`]}),ze.map((e,t)=>{let n=ze.slice(0,t+1).join(`/`),r=t===ze.length-1;return(0,b.jsxs)(`span`,{style:{display:`inline-flex`,alignItems:`center`,gap:`6px`},children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`/`}),(0,b.jsx)(`button`,{className:`btn-icon`,style:{padding:`2px 6px`,fontSize:`0.85rem`,fontWeight:r?`bold`:`normal`,color:r?`var(--primary)`:`var(--text)`},onClick:()=>Te(n),children:e})]},n)})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`8px`,fontSize:`0.8rem`,color:`var(--text-muted)`},children:[(0,b.jsxs)(`span`,{children:[`Directories: `,(0,b.jsx)(`strong`,{children:n?.directories.length||0})]}),(0,b.jsx)(`span`,{children:`•`}),(0,b.jsxs)(`span`,{children:[`Files: `,(0,b.jsx)(`strong`,{children:n?.files.length||0})]})]})]}),i!==``&&(0,b.jsx)(`div`,{style:{marginTop:`8px`,marginBottom:`8px`},children:(0,b.jsxs)(`button`,{className:`btn btn-secondary`,style:{padding:`4px 10px`,fontSize:`0.82rem`},onClick:Ee,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-level-up-alt`}),` .. (Parent Directory)`]})}),(0,b.jsx)(`div`,{className:`table-container desktop-table-view`,children:(0,b.jsxs)(`table`,{children:[(0,b.jsx)(`thead`,{children:(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`th`,{style:{width:`40%`},children:`Name`}),(0,b.jsx)(`th`,{children:`Path`}),(0,b.jsx)(`th`,{children:`Size`}),(0,b.jsx)(`th`,{children:`Modified`}),(0,b.jsx)(`th`,{style:{textAlign:`right`},children:`Actions`})]})}),(0,b.jsx)(`tbody`,{children:!n||n.directories.length===0&&n.files.length===0?(0,b.jsx)(`tr`,{children:(0,b.jsx)(`td`,{colSpan:5,className:`empty-state`,children:`No files or subdirectories found in this storage directory. Click "Upload File" to add documents.`})}):(0,b.jsxs)(b.Fragment,{children:[n.directories.map(e=>(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`td`,{children:(0,b.jsxs)(`button`,{className:`btn-icon`,style:{display:`inline-flex`,alignItems:`center`,gap:`8px`,textAlign:`left`,color:`var(--text)`},onClick:()=>Te(e.rel_path),"aria-label":`open ${e.name}`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-folder`,style:{color:`#fbbf24`,fontSize:`1.05rem`}}),(0,b.jsx)(`strong`,{children:e.name})]})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`code`,{children:e.rel_path})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`span`,{className:`text-muted`,children:`-`})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`span`,{className:`text-muted`,children:`-`})}),(0,b.jsx)(`td`,{style:{textAlign:`right`},children:(0,b.jsxs)(`button`,{className:`btn btn-secondary`,style:{padding:`3px 8px`,fontSize:`0.8rem`},onClick:()=>Te(e.rel_path),title:`Open ${e.name}`,"aria-label":`open ${e.name}`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-folder-open`}),` Open`]})})]},e.rel_path)),n.files.map(e=>{let t=e.name.toLowerCase().endsWith(`.pdf`);return(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`td`,{children:(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`},children:[t?(0,b.jsx)(`i`,{className:`fa-solid fa-file-pdf text-red-500 mr-2`,style:{color:`#ef4444`,fontSize:`1.05rem`}}):(0,b.jsx)(`i`,{className:`fa-solid fa-file-lines`,style:{color:`var(--primary)`,fontSize:`1.05rem`}}),(0,b.jsx)(`span`,{children:e.name})]})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`code`,{children:e.rel_path})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`span`,{className:`badge badge-primary`,children:xe(e.size_bytes)})}),(0,b.jsx)(`td`,{style:{fontSize:`0.85rem`},children:Se(e.mtime)}),(0,b.jsx)(`td`,{style:{textAlign:`right`},children:(0,b.jsxs)(`div`,{style:{display:`inline-flex`,gap:`6px`},children:[(0,b.jsx)(`button`,{className:`btn-icon`,onClick:()=>Fe(e),title:`Preview File`,"aria-label":`Preview File`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-eye`})}),(0,b.jsx)(`button`,{className:`btn-icon`,onClick:()=>Ie(e),title:`Replace File`,"aria-label":`Replace File`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-file-pen`})}),(0,b.jsx)(`button`,{className:`btn-icon btn-delete`,onClick:()=>Re(e),title:`Delete File`,"aria-label":`Delete File`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-trash-can`})})]})})]},e.rel_path)})]})})]})}),(0,b.jsx)(`div`,{className:`mobile-card-list`,children:!n||n.directories.length===0&&n.files.length===0?(0,b.jsx)(`div`,{className:`empty-state`,children:`No files or subdirectories found.`}):(0,b.jsxs)(b.Fragment,{children:[n.directories.map(e=>(0,b.jsxs)(`div`,{className:`data-mobile-card`,children:[(0,b.jsxs)(`div`,{className:`data-mobile-card-header`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-folder`,style:{color:`#fbbf24`}}),(0,b.jsx)(`strong`,{children:e.name})]}),(0,b.jsx)(`span`,{className:`badge badge-warning`,children:`Directory`})]}),(0,b.jsx)(`div`,{className:`data-mobile-card-body`,children:(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Path: `}),(0,b.jsx)(`code`,{children:e.rel_path})]})}),(0,b.jsx)(`div`,{className:`data-mobile-card-actions`,children:(0,b.jsxs)(`button`,{className:`btn btn-secondary`,onClick:()=>Te(e.rel_path),"aria-label":`open ${e.name}`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-folder-open`}),` Open Folder`]})})]},`m-${e.rel_path}`)),n.files.map(e=>{let t=e.name.toLowerCase().endsWith(`.pdf`);return(0,b.jsxs)(`div`,{className:`data-mobile-card`,children:[(0,b.jsxs)(`div`,{className:`data-mobile-card-header`,children:[(0,b.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`},children:[t?(0,b.jsx)(`i`,{className:`fa-solid fa-file-pdf text-red-500 mr-2`,style:{color:`#ef4444`}}):(0,b.jsx)(`i`,{className:`fa-solid fa-file-lines`,style:{color:`var(--primary)`}}),(0,b.jsx)(`strong`,{children:e.name})]}),(0,b.jsx)(`span`,{className:`badge badge-primary`,children:xe(e.size_bytes)})]}),(0,b.jsxs)(`div`,{className:`data-mobile-card-body`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Path: `}),(0,b.jsx)(`code`,{children:e.rel_path})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Modified: `}),(0,b.jsx)(`span`,{children:Se(e.mtime)})]})]}),(0,b.jsxs)(`div`,{className:`data-mobile-card-actions`,children:[(0,b.jsxs)(`button`,{className:`btn btn-secondary`,onClick:()=>Fe(e),title:`Preview File`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-eye`}),` Preview`]}),(0,b.jsxs)(`button`,{className:`btn btn-secondary`,onClick:()=>Ie(e),title:`Replace File`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-pen`}),` Replace`]}),(0,b.jsxs)(`button`,{className:`btn btn-secondary btn-delete`,onClick:()=>Re(e),title:`Delete File`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-trash-can`}),` Delete`]})]})]},`m-${e.rel_path}`)})]})})]}),c&&(0,b.jsx)(`div`,{className:`modal-backdrop`,children:(0,b.jsxs)(`div`,{className:`glass-card modal-card`,style:{maxWidth:`640px`},children:[(0,b.jsxs)(`div`,{className:`modal-header`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-cloud-arrow-up`}),` Upload to Local Storage`]}),(0,b.jsx)(`button`,{className:`btn-close`,onClick:()=>l(!1),children:`×`})]}),(0,b.jsxs)(`form`,{onSubmit:Pe,children:[(0,b.jsxs)(`div`,{style:{border:C?`2px dashed var(--primary)`:`2px dashed rgba(255, 255, 255, 0.15)`,borderRadius:`8px`,padding:`20px`,textAlign:`center`,background:C?`rgba(59, 130, 246, 0.08)`:`rgba(0, 0, 0, 0.15)`,marginBottom:`16px`,cursor:`pointer`,transition:`all 0.2s ease`},onDragOver:e=>{e.preventDefault(),w(!0)},onDragLeave:()=>w(!1),onDrop:Oe,onClick:()=>document.getElementById(`storage-file-input`)?.click(),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-arrow-up`,style:{fontSize:`2rem`,color:`var(--primary)`,marginBottom:`8px`}}),(0,b.jsx)(`p`,{style:{margin:`4px 0`,fontSize:`0.9rem`},children:y?`Selected: ${y.name} (${xe(y.size)})`:`Drag & drop a file here, or click to browse`}),(0,b.jsx)(`span`,{className:`text-muted`,style:{fontSize:`0.75rem`},children:`Supports Markdown, code, JSON, YAML, plain text, PDF (up to 50MB)`}),se&&(0,b.jsxs)(`div`,{style:{marginTop:`8px`,fontSize:`0.85rem`,color:`var(--primary)`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`,style:{marginRight:`6px`}}),`Extracting and analyzing PDF preview...`]}),(0,b.jsx)(`input`,{id:`storage-file-input`,type:`file`,accept:`.md,.markdown,.txt,.json,.yaml,.yml,.sql,.html,.css,.pdf`,style:{display:`none`},onChange:ke})]}),(0,b.jsxs)(`div`,{className:`form-row`,children:[(0,b.jsxs)(`div`,{className:`form-group`,style:{flex:2},children:[(0,b.jsx)(`label`,{htmlFor:`upload-path`,children:`Relative File Path (e.g. docs/guide.md)`}),(0,b.jsx)(`input`,{id:`upload-path`,type:`text`,required:!0,placeholder:`folder/document.md`,value:u,onChange:e=>d(e.target.value)})]}),(0,b.jsxs)(`div`,{className:`form-group`,style:{flex:1},children:[(0,b.jsx)(`label`,{htmlFor:`upload-category`,children:`Category Override`}),(0,b.jsx)(`input`,{id:`upload-category`,type:`text`,placeholder:`Optional category`,value:f,onChange:e=>p(e.target.value)})]})]}),y?.name.toLowerCase().endsWith(`.pdf`)||y?.type===`application/pdf`||u.trim().toLowerCase().endsWith(`.pdf`)?(0,b.jsxs)(`div`,{className:`alert alert-info`,style:{margin:`12px 0`,padding:`12px`,background:`rgba(59, 130, 246, 0.1)`,border:`1px solid var(--primary)`,borderRadius:`6px`,fontSize:`0.85rem`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-pdf`,style:{color:`#ef4444`,marginRight:`8px`}}),(0,b.jsx)(`strong`,{children:`PDF Document Selected:`}),` Binary PDF content cannot be edited manually as text. Extracted text, page segmentation, and vector chunks will be previewed in the extraction modal before ingestion.`]}):(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`upload-content`,children:`File Content (Optional if file uploaded directly)`}),(0,b.jsx)(`textarea`,{id:`upload-content`,rows:8,placeholder:`# Enter or paste text content here...`,value:g,onChange:e=>v(e.target.value),style:{fontFamily:`var(--font-family-mono)`,fontSize:`0.85rem`}})]}),(0,b.jsxs)(`div`,{className:`modal-footer`,children:[(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:()=>l(!1),children:`Cancel`}),(0,b.jsxs)(`button`,{type:`submit`,className:`btn btn-primary`,disabled:T,children:[(0,b.jsx)(`i`,{className:`fa-solid ${T?`fa-spinner fa-spin`:`fa-upload`}`}),` Upload & Index`]})]})]})]})}),D&&(0,b.jsx)(`div`,{className:`modal-backdrop`,children:(0,b.jsxs)(`div`,{className:`glass-card modal-card`,style:{maxWidth:`750px`,maxHeight:`90vh`,display:`flex`,flexDirection:`column`},children:[(0,b.jsxs)(`div`,{className:`modal-header`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-code`}),` File Preview: `,k?.rel_path||`Loading...`]}),k&&(0,b.jsxs)(`span`,{className:`text-muted`,style:{fontSize:`0.8rem`},children:[`Size: `,xe(k.size_bytes),` • Modified: `,Se(k.mtime)]})]}),(0,b.jsx)(`button`,{className:`btn-close`,onClick:()=>O(!1),children:`×`})]}),(0,b.jsx)(`div`,{style:{flex:1,overflowY:`auto`,margin:`10px 0`},children:A?(0,b.jsxs)(`div`,{className:`empty-state`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-spinner fa-spin`}),` Loading content...`]}):(0,b.jsx)(`pre`,{className:`search-hit-code`,style:{maxHeight:`420px`,overflowY:`auto`,margin:0,whiteSpace:`pre-wrap`},children:k?.content||`Empty file.`})}),(0,b.jsx)(`div`,{className:`modal-footer`,children:(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:()=>O(!1),children:`Close`})})]})}),ne&&j&&(0,b.jsx)(`div`,{className:`modal-backdrop`,children:(0,b.jsxs)(`div`,{className:`glass-card modal-card`,style:{maxWidth:`680px`},children:[(0,b.jsxs)(`div`,{className:`modal-header`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-pen`}),` Replace File: `,j.name]}),(0,b.jsx)(`button`,{className:`btn-close`,onClick:()=>re(!1),children:`×`})]}),(0,b.jsxs)(`form`,{onSubmit:Le,children:[(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{children:`Target File Path`}),(0,b.jsx)(`input`,{type:`text`,readOnly:!0,value:j.rel_path})]}),(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`replace-category`,children:`Category Override`}),(0,b.jsx)(`input`,{id:`replace-category`,type:`text`,placeholder:`Optional category`,value:ie,onChange:e=>ae(e.target.value)})]}),j.rel_path.toLowerCase().endsWith(`.pdf`)?(0,b.jsxs)(`div`,{className:`alert alert-warning`,style:{margin:`14px 0`,padding:`12px`,background:`rgba(245, 158, 11, 0.1)`,border:`1px solid rgba(245, 158, 11, 0.3)`,borderRadius:`6px`,fontSize:`0.85rem`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-triangle-exclamation`,style:{marginRight:`8px`,color:`#f59e0b`}}),(0,b.jsx)(`strong`,{children:`PDF Replacement:`}),` PDF documents contain binary page streams and cannot be edited as plain text. To replace this document, please upload a new `,(0,b.jsx)(`code`,{children:`.pdf`}),` file using the "Upload File" tool.`]}):(0,b.jsxs)(`div`,{className:`form-group`,children:[(0,b.jsx)(`label`,{htmlFor:`replace-content`,children:`File Content`}),(0,b.jsx)(`textarea`,{id:`replace-content`,rows:10,required:!0,placeholder:`Updated file text...`,value:N,onChange:e=>P(e.target.value),style:{fontFamily:`var(--font-family-mono)`,fontSize:`0.85rem`}})]}),(0,b.jsxs)(`div`,{className:`modal-footer`,children:[(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,onClick:()=>re(!1),children:`Cancel`}),(0,b.jsxs)(`button`,{type:`submit`,className:`btn btn-primary`,disabled:F||j.rel_path.toLowerCase().endsWith(`.pdf`),children:[(0,b.jsx)(`i`,{className:`fa-solid ${F?`fa-spinner fa-spin`:`fa-save`}`}),` Save & Re-Index`]})]})]})]})}),L&&le&&(0,b.jsx)(Ce,{data:le,onConfirm:je,onCancel:Me,isIngesting:ye})]})}function Te(){let e=S(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(!1),[a,o]=(0,_.useState)(`all`),[s,c]=(0,_.useState)(`summary`),[l,u]=(0,_.useState)(``),[d,f]=(0,_.useState)(``),[p,m]=(0,_.useState)(``),[h,g]=(0,_.useState)(``),v=(0,_.useCallback)(async(t=a,r=s,o=l,c=d,u=p)=>{i(!0);try{let e=new URLSearchParams;e.set(`source_type`,t),e.set(`detail_level`,r),o.trim()&&e.set(`repo_name`,o.trim()),c.trim()&&e.set(`path_prefix`,c.trim()),u.trim()&&e.set(`file_extension`,u.trim());let i=await fetch(`/admin/api/ingestion/catalog?${e.toString()}`);if(!i.ok){let e=await i.json().catch(()=>({}));throw Error(e.error||`Failed to load ingestion catalog`)}let a=await i.json();n(a)}catch(t){e.error(`Error loading catalog: `+t.message),console.error(`Error loading ingestion catalog:`,t)}finally{i(!1)}},[a,s,l,d,p,e]);(0,_.useEffect)(()=>{v(a,s,l,d,p)},[a,s]);let y=e=>{e&&e.preventDefault(),v(a,s,l,d,p)},x=()=>{u(``),f(``),m(``),g(``),v(a,s,``,``,``)},C=e=>{o(e)},w=e=>{c(e)},T=e=>{if(!e)return`-`;if(typeof e==`string`)try{return new Date(e).toLocaleString()}catch{return e}let t=e<1e11?e*1e3:e;return new Date(t).toLocaleString()},E=t?.git_repositories||[],D=t?.monitored_paths||[],O=t?.local_storage,k=(t?.files||[]).filter(e=>{if(!h.trim())return!0;let t=h.toLowerCase();return e.filepath.toLowerCase().includes(t)||e.repo.toLowerCase().includes(t)||e.language&&e.language.toLowerCase().includes(t)}),ee=E.reduce((e,t)=>e+(t.file_count||0),0),A=D.reduce((e,t)=>e+(t.file_count||0),0),te=O?.file_count||0,ne=ee+A+te;return(0,b.jsx)(`div`,{className:`tab-content active`,children:(0,b.jsxs)(`div`,{className:`glass-card`,children:[(0,b.jsxs)(`div`,{className:`card-header-btn`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(`i`,{className:`fa-solid fa-book-bookmark`}),` Unified Ingestion Catalog`]}),(0,b.jsx)(`p`,{className:`text-muted`,style:{marginTop:`4px`,fontSize:`0.85rem`},children:`Inspect all indexed Git repositories, monitored local workspaces, and uploaded local storage documents.`})]}),(0,b.jsx)(`div`,{style:{display:`flex`,gap:`10px`},children:(0,b.jsxs)(`button`,{className:`btn btn-secondary`,onClick:()=>y(),disabled:r,title:`Refresh catalog`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-arrows-rotate ${r?`fa-spin`:``}`}),` Refresh`]})})]}),(0,b.jsxs)(`div`,{className:`stats-grid`,style:{marginBottom:`20px`},children:[(0,b.jsxs)(`div`,{className:`stat-card`,children:[(0,b.jsx)(`div`,{className:`stat-icon`,children:(0,b.jsx)(`i`,{className:`fa-brands fa-github`})}),(0,b.jsxs)(`div`,{className:`stat-info`,children:[(0,b.jsx)(`span`,{className:`stat-label`,children:`Git Repositories`}),(0,b.jsxs)(`span`,{className:`stat-value`,children:[E.length,` `,(0,b.jsxs)(`small`,{style:{fontSize:`0.75rem`,fontWeight:`normal`,color:`var(--text-muted)`},children:[`(`,ee,` files)`]})]})]})]}),(0,b.jsxs)(`div`,{className:`stat-card`,children:[(0,b.jsx)(`div`,{className:`stat-icon`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-folder-tree`})}),(0,b.jsxs)(`div`,{className:`stat-info`,children:[(0,b.jsx)(`span`,{className:`stat-label`,children:`Monitored Paths`}),(0,b.jsxs)(`span`,{className:`stat-value`,children:[D.length,` `,(0,b.jsxs)(`small`,{style:{fontSize:`0.75rem`,fontWeight:`normal`,color:`var(--text-muted)`},children:[`(`,A,` files)`]})]})]})]}),(0,b.jsxs)(`div`,{className:`stat-card`,children:[(0,b.jsx)(`div`,{className:`stat-icon`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-hard-drive`})}),(0,b.jsxs)(`div`,{className:`stat-info`,children:[(0,b.jsx)(`span`,{className:`stat-label`,children:`Local Storage`}),(0,b.jsxs)(`span`,{className:`stat-value`,children:[te,` `,(0,b.jsx)(`small`,{style:{fontSize:`0.75rem`,fontWeight:`normal`,color:`var(--text-muted)`},children:`files`})]})]})]}),(0,b.jsxs)(`div`,{className:`stat-card`,children:[(0,b.jsx)(`div`,{className:`stat-icon`,children:(0,b.jsx)(`i`,{className:`fa-solid fa-database`})}),(0,b.jsxs)(`div`,{className:`stat-info`,children:[(0,b.jsx)(`span`,{className:`stat-label`,children:`Total Cataloged Files`}),(0,b.jsx)(`span`,{className:`stat-value`,children:ne})]})]})]}),(0,b.jsxs)(`div`,{style:{background:`rgba(0, 0, 0, 0.2)`,padding:`16px`,borderRadius:`10px`,border:`1px solid var(--border-card)`,marginBottom:`20px`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,flexWrap:`wrap`,gap:`12px`,marginBottom:`14px`},children:[(0,b.jsxs)(`div`,{className:`log-filter-pills`,role:`group`,"aria-label":`Source Type Filters`,children:[(0,b.jsxs)(`button`,{className:`log-filter-btn ${a===`all`?`active`:``}`,onClick:()=>C(`all`),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-layer-group`}),` All Sources`]}),(0,b.jsxs)(`button`,{className:`log-filter-btn ${a===`git`?`active`:``}`,onClick:()=>C(`git`),children:[(0,b.jsx)(`i`,{className:`fa-brands fa-github`}),` Git Repositories`]}),(0,b.jsxs)(`button`,{className:`log-filter-btn ${a===`monitored_path`?`active`:``}`,onClick:()=>C(`monitored_path`),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-folder-tree`}),` Monitored Paths`]}),(0,b.jsxs)(`button`,{className:`log-filter-btn ${a===`local_storage`?`active`:``}`,onClick:()=>C(`local_storage`),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-hard-drive`}),` Local Storage`]})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`6px`},role:`group`,"aria-label":`Detail Level Switch`,children:[(0,b.jsxs)(`button`,{className:`btn ${s===`summary`?`btn-primary`:`btn-secondary`}`,style:{padding:`5px 12px`,fontSize:`0.8rem`},onClick:()=>w(`summary`),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-list`}),` Summary`]}),(0,b.jsxs)(`button`,{className:`btn ${s===`detailed`?`btn-primary`:`btn-secondary`}`,style:{padding:`5px 12px`,fontSize:`0.8rem`},onClick:()=>w(`detailed`),children:[(0,b.jsx)(`i`,{className:`fa-solid fa-network-wired`}),` Detailed File Tree`]})]})]}),(0,b.jsxs)(`form`,{onSubmit:y,children:[(0,b.jsxs)(`div`,{className:`form-row-3col`,style:{alignItems:`flex-end`},children:[(0,b.jsxs)(`div`,{className:`form-group`,style:{margin:0},children:[(0,b.jsx)(`label`,{htmlFor:`catalog-repo-filter`,style:{fontSize:`0.8rem`},children:`Repository / Alias`}),(0,b.jsx)(`input`,{id:`catalog-repo-filter`,type:`text`,placeholder:`Filter by repo name...`,value:l,onChange:e=>u(e.target.value)})]}),(0,b.jsxs)(`div`,{className:`form-group`,style:{margin:0},children:[(0,b.jsx)(`label`,{htmlFor:`catalog-prefix-filter`,style:{fontSize:`0.8rem`},children:`Path Prefix`}),(0,b.jsx)(`input`,{id:`catalog-prefix-filter`,type:`text`,placeholder:`e.g. app/api or docs/`,value:d,onChange:e=>f(e.target.value)})]}),(0,b.jsxs)(`div`,{className:`form-group`,style:{margin:0},children:[(0,b.jsx)(`label`,{htmlFor:`catalog-ext-filter`,style:{fontSize:`0.8rem`},children:`File Extension`}),(0,b.jsx)(`input`,{id:`catalog-ext-filter`,type:`text`,placeholder:`e.g. .md, .py, .ts`,value:p,onChange:e=>m(e.target.value)})]})]}),(0,b.jsxs)(`div`,{style:{display:`flex`,justifyContent:`flex-end`,gap:`8px`,marginTop:`12px`},children:[(0,b.jsx)(`button`,{type:`button`,className:`btn btn-secondary`,style:{padding:`6px 12px`,fontSize:`0.85rem`},onClick:x,children:`Clear`}),(0,b.jsxs)(`button`,{type:`submit`,className:`btn btn-primary`,style:{padding:`6px 14px`,fontSize:`0.85rem`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-filter`}),` Apply`]})]})]})]}),(a===`all`||a===`git`)&&(0,b.jsxs)(`div`,{style:{marginBottom:`28px`},children:[(0,b.jsxs)(`h3`,{style:{fontSize:`1.05rem`,marginBottom:`10px`,display:`flex`,alignItems:`center`,gap:`8px`},children:[(0,b.jsx)(`i`,{className:`fa-brands fa-github`,style:{color:`var(--primary)`}}),` Git Repositories`,(0,b.jsx)(`span`,{className:`badge badge-primary`,children:E.length})]}),(0,b.jsx)(`div`,{className:`table-container desktop-table-view`,children:(0,b.jsxs)(`table`,{children:[(0,b.jsx)(`thead`,{children:(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`th`,{children:`Name`}),(0,b.jsx)(`th`,{children:`Branch / Commit`}),(0,b.jsx)(`th`,{children:`Status`}),(0,b.jsx)(`th`,{children:`Files`}),(0,b.jsx)(`th`,{children:`Provider`}),(0,b.jsx)(`th`,{children:`URL`})]})}),(0,b.jsx)(`tbody`,{children:E.length===0?(0,b.jsx)(`tr`,{children:(0,b.jsx)(`td`,{colSpan:6,className:`empty-state`,children:`No Git repositories match criteria.`})}):E.map(e=>(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`td`,{children:(0,b.jsx)(`strong`,{children:e.name})}),(0,b.jsxs)(`td`,{children:[(0,b.jsx)(`code`,{children:e.branch}),e.commit_sha&&(0,b.jsxs)(`span`,{className:`text-muted`,style:{marginLeft:`6px`,fontSize:`0.8rem`},children:[`@`,e.commit_sha.substring(0,7)]})]}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`span`,{className:`badge ${e.status===`synced`?`badge-success`:e.status===`syncing`?`badge-warning`:`badge-danger`}`,children:e.status})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`strong`,{children:e.file_count??0})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`span`,{className:`badge badge-primary`,children:e.provider||`git`})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`span`,{className:`text-muted`,style:{fontSize:`0.82rem`,wordBreak:`break-all`},children:e.url})})]},e.id))})]})}),(0,b.jsx)(`div`,{className:`mobile-card-list`,children:E.length===0?(0,b.jsx)(`div`,{className:`empty-state`,children:`No Git repositories match criteria.`}):E.map(e=>(0,b.jsxs)(`div`,{className:`data-mobile-card`,children:[(0,b.jsxs)(`div`,{className:`data-mobile-card-header`,children:[(0,b.jsx)(`strong`,{children:e.name}),(0,b.jsx)(`span`,{className:`badge ${e.status===`synced`?`badge-success`:`badge-warning`}`,children:e.status})]}),(0,b.jsxs)(`div`,{className:`data-mobile-card-body`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Branch: `}),(0,b.jsx)(`code`,{children:e.branch})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Files: `}),(0,b.jsx)(`strong`,{children:e.file_count??0})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`URL: `}),(0,b.jsx)(`span`,{style:{wordBreak:`break-all`},children:e.url})]})]})]},`gm-${e.id}`))})]}),(a===`all`||a===`monitored_path`)&&(0,b.jsxs)(`div`,{style:{marginBottom:`28px`},children:[(0,b.jsxs)(`h3`,{style:{fontSize:`1.05rem`,marginBottom:`10px`,display:`flex`,alignItems:`center`,gap:`8px`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-folder-tree`,style:{color:`#fbbf24`}}),` Monitored Local Paths`,(0,b.jsx)(`span`,{className:`badge badge-primary`,children:D.length})]}),(0,b.jsx)(`div`,{className:`table-container desktop-table-view`,children:(0,b.jsxs)(`table`,{children:[(0,b.jsx)(`thead`,{children:(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`th`,{children:`Path`}),(0,b.jsx)(`th`,{children:`Repo Alias`}),(0,b.jsx)(`th`,{children:`Category`}),(0,b.jsx)(`th`,{children:`Files Indexed`})]})}),(0,b.jsx)(`tbody`,{children:D.length===0?(0,b.jsx)(`tr`,{children:(0,b.jsx)(`td`,{colSpan:4,className:`empty-state`,children:`No monitored paths match criteria.`})}):D.map((e,t)=>(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`td`,{children:(0,b.jsx)(`code`,{children:e.path})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`strong`,{children:e.repo})}),(0,b.jsx)(`td`,{children:e.category?(0,b.jsx)(`span`,{className:`badge badge-accent`,children:e.category}):(0,b.jsx)(`span`,{className:`text-muted`,children:`-`})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`strong`,{children:e.file_count??0})})]},`${e.path}-${t}`))})]})}),(0,b.jsx)(`div`,{className:`mobile-card-list`,children:D.length===0?(0,b.jsx)(`div`,{className:`empty-state`,children:`No monitored paths match criteria.`}):D.map((e,t)=>(0,b.jsxs)(`div`,{className:`data-mobile-card`,children:[(0,b.jsxs)(`div`,{className:`data-mobile-card-header`,children:[(0,b.jsx)(`strong`,{children:e.repo}),(0,b.jsxs)(`span`,{className:`badge badge-primary`,children:[e.file_count??0,` files`]})]}),(0,b.jsxs)(`div`,{className:`data-mobile-card-body`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Path: `}),(0,b.jsx)(`code`,{children:e.path})]}),e.category&&(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Category: `}),(0,b.jsx)(`span`,{className:`badge badge-accent`,children:e.category})]})]})]},`mpm-${t}`))})]}),(a===`all`||a===`local_storage`)&&O&&(0,b.jsxs)(`div`,{style:{marginBottom:`28px`},children:[(0,b.jsxs)(`h3`,{style:{fontSize:`1.05rem`,marginBottom:`10px`,display:`flex`,alignItems:`center`,gap:`8px`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-hard-drive`,style:{color:`var(--accent)`}}),` Local Storage`,(0,b.jsxs)(`span`,{className:`badge badge-primary`,children:[O.file_count,` files`]})]}),(0,b.jsxs)(`div`,{style:{background:`rgba(255, 255, 255, 0.02)`,padding:`16px`,borderRadius:`8px`,border:`1px solid var(--border-card)`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,gap:`20px`,flexWrap:`wrap`,marginBottom:`12px`},children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,style:{fontSize:`0.85rem`},children:`Storage Root: `}),(0,b.jsx)(`code`,{children:O.root_path})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,style:{fontSize:`0.85rem`},children:`Total Files: `}),(0,b.jsx)(`strong`,{children:O.file_count})]})]}),O.tree&&(0,b.jsxs)(`div`,{style:{fontSize:`0.85rem`,color:`var(--text-muted)`},children:[`Top-level Subfolders: `,(0,b.jsx)(`strong`,{children:O.tree.directories?.length||0}),` • Top-level Files: `,(0,b.jsx)(`strong`,{children:O.tree.files?.length||0})]})]})]}),s===`detailed`&&(0,b.jsxs)(`div`,{style:{marginTop:`20px`},children:[(0,b.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,flexWrap:`wrap`,gap:`10px`,marginBottom:`12px`},children:[(0,b.jsxs)(`h3`,{style:{fontSize:`1.05rem`,margin:0,display:`flex`,alignItems:`center`,gap:`8px`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-file-code`,style:{color:`var(--primary)`}}),` Ingested Files Details`,(0,b.jsx)(`span`,{className:`badge badge-primary`,children:k.length})]}),(0,b.jsxs)(`div`,{className:`log-search-wrapper`,style:{maxWidth:`320px`},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-magnifying-glass search-icon`}),(0,b.jsx)(`input`,{type:`text`,className:`log-search-input`,placeholder:`Search in loaded files...`,value:h,onChange:e=>g(e.target.value)}),h&&(0,b.jsx)(`button`,{className:`clear-search-btn`,onClick:()=>g(``),children:`×`})]})]}),(0,b.jsx)(`div`,{className:`table-container desktop-table-view`,children:(0,b.jsxs)(`table`,{children:[(0,b.jsx)(`thead`,{children:(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`th`,{children:`Filepath`}),(0,b.jsx)(`th`,{children:`Repository`}),(0,b.jsx)(`th`,{children:`Type`}),(0,b.jsx)(`th`,{children:`Language`}),(0,b.jsx)(`th`,{children:`Last Modified`})]})}),(0,b.jsx)(`tbody`,{children:k.length===0?(0,b.jsx)(`tr`,{children:(0,b.jsx)(`td`,{colSpan:5,className:`empty-state`,children:`No detailed files match current search or filters.`})}):k.map((e,t)=>(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`td`,{children:(0,b.jsx)(`code`,{style:{wordBreak:`break-all`},children:e.filepath})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`strong`,{children:e.repo})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`span`,{className:`badge badge-primary`,children:e.doc_type})}),(0,b.jsx)(`td`,{children:(0,b.jsx)(`span`,{className:`badge badge-accent`,children:e.language||`text`})}),(0,b.jsx)(`td`,{style:{fontSize:`0.85rem`},children:T(e.mtime)})]},`${e.filepath}-${t}`))})]})}),(0,b.jsx)(`div`,{className:`mobile-card-list`,children:k.length===0?(0,b.jsx)(`div`,{className:`empty-state`,children:`No detailed files match current search or filters.`}):k.map((e,t)=>(0,b.jsxs)(`div`,{className:`data-mobile-card`,children:[(0,b.jsxs)(`div`,{className:`data-mobile-card-header`,children:[(0,b.jsx)(`strong`,{children:e.repo}),(0,b.jsx)(`span`,{className:`badge badge-accent`,children:e.language||`text`})]}),(0,b.jsxs)(`div`,{className:`data-mobile-card-body`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Path: `}),(0,b.jsx)(`code`,{style:{wordBreak:`break-all`},children:e.filepath})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Type: `}),(0,b.jsx)(`span`,{className:`badge badge-primary`,children:e.doc_type})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`text-muted`,children:`Modified: `}),(0,b.jsx)(`span`,{children:T(e.mtime)})]})]})]},`dfm-${t}`))})]})]})})}function Ee(){let[e,t]=(0,_.useState)(`overview`),[n,r]=(0,_.useState)(!1),[i,a]=(0,_.useState)(null),o=async()=>{try{let e=await fetch(`/admin/api/stats`);if(!e.ok)return;let t=await e.json();a(t)}catch(e){console.error(`Error loading stats:`,e)}};return(0,_.useEffect)(()=>{o();let e=setInterval(o,8e3);return()=>clearInterval(e)},[]),(0,b.jsx)(b.Fragment,{children:(0,b.jsxs)(`div`,{className:`dashboard-container`,children:[(0,b.jsxs)(`header`,{className:`dashboard-header`,children:[(0,b.jsxs)(`div`,{className:`header-top-row`,children:[(0,b.jsxs)(`div`,{className:`header-logo`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-layer-group logo-icon`}),(0,b.jsxs)(`div`,{className:`header-title`,children:[(0,b.jsx)(`h1`,{children:`ContextCortex`}),(0,b.jsx)(`span`,{className:`badge badge-primary`,children:`v2.16.0`})]})]}),(0,b.jsx)(`button`,{className:`menu-toggle-btn`,"aria-label":`Toggle navigation`,onClick:()=>r(!n),children:(0,b.jsx)(`i`,{className:`fa-solid ${n?`fa-xmark`:`fa-bars`}`})})]}),(0,b.jsxs)(`div`,{className:`header-status`,children:[(0,b.jsxs)(`div`,{className:`status-item`,children:[(0,b.jsx)(`span`,{className:`label`,children:`Engine State`}),(0,b.jsx)(`span`,{className:`value`,children:i?.is_indexing?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`span`,{className:`indicator indexing`}),` Syncing...`]}):(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(`span`,{className:`indicator online`}),` Idle`]})})]}),(0,b.jsxs)(`div`,{className:`status-item`,children:[(0,b.jsx)(`span`,{className:`label`,children:`Vector Backend`}),(0,b.jsxs)(`span`,{className:`value`,children:[(0,b.jsx)(`i`,{className:`fa-solid fa-database`,style:{marginRight:`5px`}}),(0,b.jsxs)(`span`,{children:[i?.vector_store_provider===`chroma`?`ChromaDB`:`Qdrant`,` (`,(i?.vector_store_mode||`embedded`)===`embedded`?`Embedded`:`Remote`,`)`]}),i?.vector_db_status&&(0,b.jsx)(`span`,{className:`badge ${i.vector_db_status===`Healthy`?`badge-success`:`badge-danger`}`,style:{marginLeft:`6px`,fontSize:`0.75rem`,padding:`2px 6px`},"data-testid":`vector-db-status-badge`,children:i.vector_db_status})]})]}),(0,b.jsxs)(`div`,{className:`status-item`,children:[(0,b.jsx)(`span`,{className:`label`,children:`Collection`}),(0,b.jsx)(`span`,{className:`value code`,children:i?.vector_store_collection||`knowledge_rag_v1`})]})]})]}),(0,b.jsxs)(`nav`,{className:`dashboard-nav ${n?`drawer-open`:``}`,children:[(0,b.jsxs)(`button`,{className:`nav-tab ${e===`overview`?`active`:``}`,onClick:()=>{t(`overview`),r(!1)},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-chart-pie`}),` Overview`]}),(0,b.jsxs)(`button`,{className:`nav-tab ${e===`navigator`||e===`topology`?`active`:``}`,onClick:()=>{t(`navigator`),r(!1)},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-code-fork`}),` Navigator`]}),(0,b.jsxs)(`button`,{className:`nav-tab ${e===`git-repos`?`active`:``}`,onClick:()=>{t(`git-repos`),r(!1)},children:[(0,b.jsx)(`i`,{className:`fa-brands fa-github`}),` Git Repositories`]}),(0,b.jsxs)(`button`,{className:`nav-tab ${e===`local-paths`?`active`:``}`,onClick:()=>{t(`local-paths`),r(!1)},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-folder-tree`}),` Local Paths`]}),(0,b.jsxs)(`button`,{className:`nav-tab ${e===`local-storage`?`active`:``}`,onClick:()=>{t(`local-storage`),r(!1)},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-hard-drive`}),` Local Storage`]}),(0,b.jsxs)(`button`,{className:`nav-tab ${e===`ingestion-catalog`?`active`:``}`,onClick:()=>{t(`ingestion-catalog`),r(!1)},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-book-bookmark`}),` Ingestion Catalog`]}),(0,b.jsxs)(`button`,{className:`nav-tab ${e===`search-inspector`?`active`:``}`,onClick:()=>{t(`search-inspector`),r(!1)},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-magnifying-glass`}),` Search & Inspector`]}),(0,b.jsxs)(`button`,{className:`nav-tab ${e===`settings`?`active`:``}`,onClick:()=>{t(`settings`),r(!1)},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-gear`}),` Settings`]}),(0,b.jsxs)(`button`,{className:`nav-tab ${e===`diagnostics`?`active`:``}`,onClick:()=>{t(`diagnostics`),r(!1)},children:[(0,b.jsx)(`i`,{className:`fa-solid fa-terminal`}),` Diagnostics & Logs`]})]}),(0,b.jsxs)(`main`,{className:`dashboard-main`,children:[e===`overview`&&(0,b.jsx)(T,{stats:i,refreshStats:o}),(e===`navigator`||e===`topology`)&&(0,b.jsx)(Se,{}),e===`git-repos`&&(0,b.jsx)(te,{refreshStats:o}),e===`local-paths`&&(0,b.jsx)(ne,{refreshStats:o}),e===`local-storage`&&(0,b.jsx)(we,{refreshStats:o}),e===`ingestion-catalog`&&(0,b.jsx)(Te,{}),e===`search-inspector`&&(0,b.jsx)(re,{}),e===`settings`&&(0,b.jsx)(ce,{stats:i,refreshStats:o}),e===`diagnostics`&&(0,b.jsx)(le,{})]}),(0,b.jsx)(`footer`,{className:`dashboard-footer`,children:(0,b.jsx)(`p`,{children:`ContextCortex MCP • Universal Code & Knowledge RAG • 2026`})})]})})}(0,v.createRoot)(document.getElementById(`root`)).render((0,b.jsx)(_.StrictMode,{children:(0,b.jsx)(w,{children:(0,b.jsx)(Ee,{})})})); \ No newline at end of file diff --git a/frontend/dist/assets/index-qIBukKNH.css b/frontend/dist/assets/index-qIBukKNH.css deleted file mode 100644 index 52e2cc9..0000000 --- a/frontend/dist/assets/index-qIBukKNH.css +++ /dev/null @@ -1 +0,0 @@ -:root,[data-theme=deep-ocean]{--bg-base:#07181b;--bg-card:#0d2c2f;--border-card:#15474d;--primary:#0891b2;--primary-hover:#0e7490;--accent:#14b8a6;--accent-light:#2dd4bf;--danger:#ef4444;--danger-hover:#dc2626;--text:#f8fafc;--text-muted:#94a3b8;--code-bg:#00000059;--radius:10px;--font-family-base:"Outfit", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-family-heading:"Outfit", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-family-mono:"JetBrains Mono", monospace;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-size-xs:.75rem;--font-size-sm:.85rem;--font-size-base:1rem;--font-size-lg:1.15rem;--font-size-xl:1.35rem;--font-size-2xl:1.65rem}[data-theme=midnight-blue]{--bg-base:#0a0f1d;--bg-card:#121a2fa6;--border-card:#ffffff14;--primary:#3b82f6;--primary-hover:#2563eb;--accent:#14b8a6;--accent-light:#2dd4bf;--danger:#ef4444;--danger-hover:#dc2626;--text:#f8fafc;--text-muted:#94a3b8;--code-bg:#00000059;--radius:12px}[data-theme=lavender-haze],[data-theme=aqua-breeze],[data-theme=arctic-frost]{--bg-base:#f5f3ff;--bg-card:#faf8ff;--border-card:#ddd6fe;--primary:#7c3aed;--primary-hover:#6d28d9;--accent:#ec4899;--accent-light:#f472b6;--danger:#dc2626;--danger-hover:#b91c1c;--text:#1e1035;--text-muted:#6b5b82;--code-bg:#7c3aed14;--radius:10px}[data-theme=amber-warmth],[data-theme=azure-daylight],[data-theme=solar-daybreak]{--bg-base:#fdf8f4;--bg-card:#fff;--border-card:#fed7aa;--primary:#ea580c;--primary-hover:#c2410c;--accent:#d97706;--accent-light:#f59e0b;--danger:#dc2626;--danger-hover:#b91c1c;--text:#291508;--text-muted:#7c5e4a;--code-bg:#ea580c14;--radius:12px}*{box-sizing:border-box;margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-family:var(--font-family-heading)}body{font-family:var(--font-family-base);background-color:var(--bg-base);color:var(--text);min-height:100vh;display:block;position:relative;overflow:hidden scroll}.dashboard-container{z-index:1;flex-direction:column;gap:16px;width:100%;min-width:0;max-width:1400px;margin:0 auto;padding:20px 32px;display:flex}.dashboard-header{background:var(--bg-card);border:1px solid var(--border-card);border-radius:var(--radius);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);justify-content:space-between;align-items:center;padding:14px 20px;display:flex}.header-top-row{align-items:center;display:flex}.menu-toggle-btn{border:1px solid var(--border-card);color:var(--text);cursor:pointer;background:#ffffff14;border-radius:8px;padding:8px 12px;font-size:1.25rem;transition:all .2s;display:none}.menu-toggle-btn:hover{background:#ffffff26}.header-logo{align-items:center;gap:16px;display:flex}.logo-icon{color:var(--accent-light);font-size:2rem}.header-title h1{letter-spacing:-.5px;font-size:1.4rem;font-weight:700}.header-status{align-items:center;gap:20px;display:flex}.status-item{flex-direction:column;align-items:flex-end;display:flex}.status-item .label{color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;font-size:.75rem}.status-item .value{align-items:center;gap:6px;font-size:.95rem;font-weight:600;display:flex}.indicator{border-radius:50%;width:8px;height:8px;display:inline-block}.indicator.online{background:#10b981;box-shadow:0 0 10px #10b981}.indicator.indexing{background:#f59e0b;animation:1.5s infinite pulse;box-shadow:0 0 10px #f59e0b}@keyframes pulse{0%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(1.2)}to{opacity:1;transform:scale(1)}}.dashboard-nav{border-bottom:1px solid var(--border-card);flex-wrap:wrap;gap:10px;padding-bottom:8px;display:flex}.nav-tab{color:var(--text-muted);cursor:pointer;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:10px 18px;font-family:inherit;font-size:.95rem;font-weight:600;transition:all .2s;display:flex}.nav-tab:hover{color:var(--text);background:#ffffff0d}.nav-tab.active{color:#fff;background:color-mix(in srgb, var(--primary) 25%, transparent);border:1px solid color-mix(in srgb, var(--primary) 50%, transparent)}.glass-card{background:var(--bg-card);border:1px solid var(--border-card);border-radius:var(--radius);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);width:100%;min-width:0;padding:16px 20px}.glass-card h2{align-items:center;gap:8px;margin-bottom:12px;font-size:1.05rem;font-weight:600;display:flex}.tab-content{width:100%;min-width:0;display:none}.tab-content.active{flex-direction:column;gap:16px;width:100%;min-width:0;display:flex}.overview-grid{grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:12px;display:grid}.stat-metric{align-items:center;gap:12px;padding:14px 16px;display:flex}.metric-icon{color:var(--accent-light);background:#14b8a626;border-radius:10px;justify-content:center;align-items:center;width:40px;height:40px;font-size:1.4rem;display:flex}.metric-info{flex-direction:column;gap:4px;display:flex}.metric-info .stat-number{font-size:var(--font-size-xl);font-weight:var(--font-weight-extrabold);font-family:var(--font-family-mono);color:var(--text);line-height:1.1}.metric-info .stat-label{color:var(--text-muted);font-size:.85rem}.two-col-layout{grid-template-columns:1fr 1fr;gap:20px;display:grid}@media (width<=900px){.two-col-layout{grid-template-columns:1fr}}.specs-list{flex-direction:column;gap:12px;margin-top:12px;display:flex}.spec-row{border-bottom:1px solid var(--border-card);justify-content:space-between;align-items:center;gap:12px;padding:10px 0;font-size:.9rem;display:flex}.spec-row span:first-child{color:var(--text-muted);flex-shrink:0;font-weight:500}code,.code{background:var(--code-bg);vertical-align:baseline;word-break:break-word;overflow-wrap:break-word;border-radius:4px;max-width:100%;padding:1px 5px;font-family:JetBrains Mono,Fira Code,monospace;font-size:.85em;line-height:1.25;display:inline-block}.spec-row span:last-child,.spec-row code{text-align:right;word-break:break-word;max-width:100%}@media (width<=576px){.spec-row{flex-direction:column;align-items:flex-start;gap:6px}.spec-row span:last-child,.spec-row code{text-align:left}.stat-metric{padding:16px}.path-input-row{flex-direction:column}.browser-breadcrumbs{word-break:break-all}}.tag-cloud{flex-wrap:wrap;gap:8px;max-height:250px;margin-top:12px;display:flex;overflow-y:auto}.topic-tag{background:color-mix(in srgb, var(--accent) 12%, transparent);border:1px solid color-mix(in srgb, var(--accent) 25%, transparent);color:var(--accent);font-size:var(--font-size-xs);font-family:var(--font-family-mono);border-radius:6px;padding:4px 10px}.badge{white-space:nowrap;border-radius:6px;align-items:center;gap:6px;max-width:100%;padding:3px 8px;font-size:.75rem;font-weight:600;display:inline-flex}.badge-primary{background:color-mix(in srgb, var(--primary) 15%, transparent);color:var(--primary);border:1px solid color-mix(in srgb, var(--primary) 30%, transparent)}.badge-accent{background:color-mix(in srgb, var(--accent) 15%, transparent);color:var(--accent);border:1px solid color-mix(in srgb, var(--accent) 30%, transparent)}.badge-success{color:#6ee7b7;background:#10b98133;border:1px solid #10b9814d}.badge-warning{color:#fcd34d;background:#f59e0b33;border:1px solid #f59e0b4d}.badge-danger{color:#fca5a5;background:#ef444433;border:1px solid #ef44444d}code,.code{font-family:var(--font-family-mono);background:var(--code-bg);border-radius:4px;padding:2px 6px;font-size:.85rem}.desktop-table-view{display:block}.mobile-card-list{flex-direction:column;gap:14px;display:none}.data-mobile-card{border:1px solid var(--border-card);background:#ffffff06;border-radius:10px;flex-direction:column;gap:12px;padding:16px;display:flex}.data-mobile-card-header{justify-content:space-between;align-items:center;gap:10px;display:flex}.data-mobile-card-body{flex-direction:column;gap:8px;font-size:.85rem;display:flex}.data-mobile-card-actions{border-top:1px solid #ffffff0f;flex-wrap:wrap;gap:8px;margin-top:4px;padding-top:10px;display:flex}.data-mobile-card-actions .btn{flex:calc(50% - 8px);justify-content:center;min-width:0}.card-header-btn{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;margin-bottom:16px;display:flex}.card-header-btn>div{flex:1 1 0;min-width:0;max-width:100%}.card-header-btn p{overflow-wrap:anywhere;word-break:break-word;margin:0}.table-container{-webkit-overflow-scrolling:touch;max-width:100%;margin-top:10px;overflow-x:auto}table{border-collapse:collapse;table-layout:auto;width:100%;font-size:.9rem}th{text-align:left;color:var(--text-muted);border-bottom:1px solid var(--border-card);white-space:nowrap;padding:12px 14px;font-weight:600}td{border-bottom:1px solid var(--border-card);color:var(--text);padding:14px}td a.repo-url-link,td a[href*=http]{text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;max-width:220px;display:inline-block;overflow:hidden}tr:hover td{background:color-mix(in srgb, var(--text) 3%, transparent)}.empty-state{text-align:center;color:var(--text-muted);padding:40px!important}.btn{cursor:pointer;border:none;border-radius:8px;align-items:center;gap:8px;padding:8px 16px;font-family:inherit;font-size:.9rem;font-weight:600;transition:all .2s;display:inline-flex}.btn-primary{background:var(--primary);color:#fff}.btn-primary:hover{background:var(--primary-hover)}.btn-secondary{background:color-mix(in srgb, var(--text) 10%, transparent);color:var(--text);border:1px solid var(--border-card)}.btn-secondary:hover{background:color-mix(in srgb, var(--text) 16%, transparent)}.btn-icon{color:var(--text-muted);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:6px;font-size:.9rem;transition:all .2s}.btn-icon:hover{color:var(--text);background:color-mix(in srgb, var(--text) 10%, transparent)}.btn-delete:hover{color:var(--danger)}.form-group{flex-direction:column;gap:6px;margin-bottom:14px;display:flex}.form-group label{color:var(--text-muted);font-size:.85rem;font-weight:600}.form-row{box-sizing:border-box;gap:14px;width:100%;display:flex}.form-row>.form-group{flex:1;min-width:0}.form-row-3col{box-sizing:border-box;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:12px;width:100%;display:grid}.form-row-3col>.form-group{min-width:0}input[type=text],input[type=url],input[type=password],input[type=number],select,textarea{background:var(--code-bg);border:1px solid var(--border-card);color:var(--text);box-sizing:border-box;border-radius:8px;outline:none;width:100%;min-width:0;max-width:100%;padding:10px 14px;font-family:inherit;font-size:.9rem;transition:border-color .2s}input:focus,select:focus,textarea:focus{border-color:var(--primary)}.path-input-row{gap:8px;display:flex}.path-input-row input{flex:1}.modal-backdrop{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:1000;background:#000000b3;justify-content:center;align-items:center;width:100%;max-width:100vw;height:100%;display:flex;position:fixed;inset:0}.modal-card{background:var(--bg-card);border:1px solid var(--border-card);width:90%;max-width:550px}.modal-header{justify-content:space-between;align-items:center;margin-bottom:20px;display:flex}.modal-header h2{margin-bottom:0}.btn-close{color:var(--text-muted);cursor:pointer;background:0 0;border:none;font-size:1.5rem}.modal-footer{justify-content:flex-end;gap:10px;margin-top:20px;display:flex}.browser-body{margin:16px 0}.browser-breadcrumbs{background:#0000004d;border-radius:6px;margin-bottom:12px;padding:8px 12px;font-size:.85rem}.browser-list-container{background:#0003;border:1px solid #ffffff14;border-radius:6px;max-height:300px;overflow-y:auto}.browser-list{list-style:none}.browser-item{cursor:pointer;border-bottom:1px solid #ffffff0a;align-items:center;gap:10px;padding:8px 12px;font-size:.9rem;display:flex}.browser-item:hover{background:#ffffff0f}.search-hit-card{background:#0000004d;border:1px solid #ffffff14;border-radius:8px;margin-bottom:14px;padding:16px}.search-hit-header{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin-bottom:10px;display:flex}.search-hit-code{background:var(--code-bg);border:1px solid var(--border-card);font-family:var(--font-family-mono);white-space:pre-wrap;word-break:break-all;color:var(--text);border-radius:6px;padding:12px;font-size:.85rem;overflow-x:auto}.dashboard-footer{text-align:center;color:var(--text-muted);padding:20px 0;font-size:.8rem}@media (width<=768px){.menu-toggle-btn{justify-content:center;align-items:center;display:inline-flex}.dashboard-header{flex-direction:column;align-items:stretch;gap:14px;padding:16px;display:flex}.header-top-row{justify-content:space-between;align-items:center;width:100%;display:flex}.header-status{border-top:1px solid #ffffff0f;flex-wrap:wrap;justify-content:space-between;gap:8px 14px;width:100%;padding-top:10px;display:flex}.status-item{align-items:flex-start}.dashboard-nav{background:var(--bg-card);border:1px solid var(--border-card);border-radius:var(--radius);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-direction:column;gap:6px;padding:12px;display:none}.dashboard-nav.drawer-open{animation:.25s ease-out forwards drawer-slide-down;display:flex}.nav-tab{justify-content:flex-start;width:100%;padding:12px 16px}.dashboard-container{box-sizing:border-box;max-width:100vw;padding:16px 12px}.vs-box{box-sizing:border-box;max-width:100%;padding:16px 12px}.glass-card{box-sizing:border-box;max-width:100%;padding:18px 14px}.desktop-table-view{display:none}.mobile-card-list{display:flex}.card-header-btn{flex-direction:column;align-items:stretch;gap:14px}.form-row,.form-row-3col{flex-direction:column;gap:12px;display:flex!important}.search-hit-header{flex-direction:column;align-items:flex-start;gap:8px}.search-form-btn-group{align-self:stretch!important}.search-form-btn-group .btn{justify-content:center;width:100%}.log-viewer-header{flex-direction:column;align-items:stretch;gap:12px}.log-viewer-actions{justify-content:space-between;width:100%}.log-toolbar{flex-direction:column;align-items:stretch;gap:12px}.log-filter-pills{-webkit-overflow-scrolling:touch;padding-bottom:4px;overflow-x:auto}.log-search-wrapper{width:100%;min-width:0;max-width:100%}.log-entry-main{flex-direction:column;align-items:flex-start;gap:4px}.sync-progress-container{min-width:unset;width:100%;max-width:100%}.sync-file-caption{max-width:100%}}@keyframes drawer-slide-down{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}.toast-container{z-index:9999;pointer-events:none;flex-direction:column;gap:10px;display:flex;position:fixed;bottom:24px;right:24px}.toast{color:#fff;pointer-events:auto;background:#1e293b;border-left:4px solid #0000;border-radius:8px;align-items:center;gap:12px;max-width:400px;padding:14px 20px;font-size:.95rem;font-weight:500;animation:.3s ease-out forwards toast-slide-in;display:flex;box-shadow:0 10px 25px #00000080}@keyframes toast-slide-in{0%{opacity:0;transform:translate(50px)}to{opacity:1;transform:translate(0)}}.toast-success{border-left-color:#10b981}.toast-success i{color:#10b981}.toast-error{border-left-color:#ef4444}.toast-error i{color:#ef4444}.toast-info{border-left-color:#3b82f6}.toast-info i{color:#3b82f6}.toast-warning{border-left-color:#f59e0b}.toast-warning i{color:#f59e0b}.toast-message{flex:1;line-height:1.4}.toast-dismiss-btn{color:var(--text-muted);cursor:pointer;background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;margin-left:6px;padding:4px;font-size:.85rem;transition:all .2s;display:inline-flex}.toast-dismiss-btn:hover{color:#fff;background:#ffffff1f}.log-viewer-container{flex-direction:column;gap:16px;display:flex}.log-viewer-header{flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.log-viewer-actions{flex-wrap:wrap;align-items:center;gap:12px;display:flex}.log-autoscroll-label{color:var(--text-muted);cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:6px;font-size:.85rem;font-weight:500;display:inline-flex}.log-autoscroll-label input[type=checkbox]{cursor:pointer;accent-color:var(--primary)}.log-toolbar{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:16px;display:flex}.log-filter-pills{flex-wrap:wrap;gap:8px;display:flex}.log-filter-btn{color:var(--text-muted);cursor:pointer;background:#ffffff0d;border:1px solid #ffffff1a;border-radius:20px;align-items:center;gap:6px;padding:5px 12px;font-family:inherit;font-size:.8rem;font-weight:600;transition:all .2s;display:inline-flex}.log-filter-btn:hover{color:var(--text);background:#ffffff1a}.log-filter-btn.active{color:#93c5fd;background:#3b82f633;border-color:#3b82f666}.log-filter-btn.warning.active{color:#fcd34d;background:#f59e0b33;border-color:#f59e0b66}.log-filter-btn.error.active{color:#fca5a5;background:#ef444433;border-color:#ef444466}.log-filter-btn.debug.active{color:#cbd5e1;background:#94a3b833;border-color:#94a3b866}.pill-count{background:#0000004d;border-radius:10px;padding:1px 6px;font-size:.72rem}.log-search-wrapper{flex:1;align-items:center;min-width:280px;max-width:480px;display:flex;position:relative}.log-search-wrapper .search-icon{color:var(--text-muted);pointer-events:none;font-size:.85rem;position:absolute;left:12px}.log-search-input{background:var(--code-bg);border:1px solid var(--border-card);width:100%;color:var(--text);border-radius:8px;outline:none;padding:8px 32px 8px 34px;font-family:inherit;font-size:.85rem;transition:border-color .2s}.log-search-input:focus{border-color:var(--primary)}.clear-search-btn{color:var(--text-muted);cursor:pointer;background:0 0;border:none;padding:4px;font-size:.8rem;position:absolute;right:8px}.clear-search-btn:hover{color:var(--text)}.log-stream-container{background:var(--code-bg);border:1px solid var(--border-card);min-height:320px;max-height:580px;font-family:var(--font-family-mono);border-radius:8px;padding:12px;overflow-y:auto}.log-stream-list{flex-direction:column;gap:4px;display:flex}.log-entry{background:#ffffff04;border-left:3px solid #0000;border-radius:6px;padding:8px 12px;font-size:.82rem;line-height:1.5;transition:background .15s}.log-entry:hover{background:#ffffff09}.log-entry.log-level-info{border-left-color:#3b82f6}.log-entry.log-level-warning{background:#f59e0b08;border-left-color:#f59e0b}.log-entry.log-level-error{background:#ef44440f;border-left-color:#ef4444}.log-entry.log-level-debug{opacity:.85;border-left-color:#64748b}.log-entry-main{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}.log-timestamp{color:var(--text-muted);white-space:nowrap;font-size:.78rem}.log-logger{color:var(--accent-light);font-size:.78rem}.log-message{color:#e2e8f0;word-break:break-word;flex:1}.btn-traceback-toggle{color:#fca5a5;cursor:pointer;background:#ef44441f;border:1px solid #ef44444d;border-radius:4px;align-items:center;gap:5px;padding:2px 8px;font-family:inherit;font-size:.75rem;transition:all .2s;display:inline-flex}.btn-traceback-toggle:hover{background:#ef444438}.log-traceback-wrapper{margin-top:8px;padding-left:8px}.traceback-box{background:color-mix(in srgb, var(--danger) 10%, transparent);border:1px solid color-mix(in srgb, var(--danger) 30%, transparent);color:var(--danger);font-family:var(--font-family-mono);white-space:pre-wrap;border-radius:6px;padding:10px 14px;font-size:.78rem;line-height:1.45;overflow-x:auto}.vs-config-layout{box-sizing:border-box;grid-template-columns:1fr 1fr;gap:20px;width:100%;min-width:0;margin-top:16px;display:grid}@media (width<=960px){.vs-config-layout{grid-template-columns:1fr}}.vs-box{border:1px solid var(--border-card);box-sizing:border-box;overflow-wrap:break-word;background:#ffffff05;border-radius:10px;width:100%;min-width:0;padding:20px}.vs-box h3{color:var(--text);align-items:center;gap:8px;margin-bottom:14px;font-size:1rem;font-weight:600;display:flex}.vs-feedback-banner{border-radius:8px;align-items:center;gap:10px;margin-bottom:16px;padding:10px 14px;font-size:.85rem;line-height:1.4;display:flex}.feedback-success{color:#6ee7b7;background:#10b9811f;border:1px solid #10b9814d}.feedback-error{color:#fca5a5;background:#ef44441f;border:1px solid #ef44444d}.theme-options-grid{grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:12px;margin-top:12px;display:grid}.theme-card{background:var(--bg-card);border:1.5px solid var(--border-card);border-radius:var(--radius);cursor:pointer;text-align:left;flex-direction:column;gap:8px;padding:12px 14px;transition:all .2s;display:flex;position:relative}.theme-card:hover{border-color:color-mix(in srgb, var(--primary) 60%, transparent);transform:translateY(-2px)}.theme-card.active{border-color:var(--primary);box-shadow:0 0 0 1px var(--primary), 0 4px 14px #00000040}.theme-card-header{justify-content:space-between;align-items:center;display:flex}.theme-card-title{color:var(--text);align-items:center;gap:6px;font-size:.92rem;font-weight:600;display:flex}.theme-card-desc{color:var(--text-muted);font-size:.76rem;line-height:1.35}.theme-swatches{align-items:center;gap:6px;padding-top:2px;display:flex}.theme-swatch{border:1px solid #fff3;border-radius:50%;width:18px;height:18px;box-shadow:0 1px 3px #00000040}.sync-drawer-backdrop{-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);z-index:1100;background:#000000a6;justify-content:flex-end;width:100vw;height:100vh;display:flex;position:fixed;top:0;left:0}.sync-drawer{background:var(--bg-card,#111827);border-left:1px solid var(--border-card);flex-direction:column;width:620px;max-width:95vw;height:100vh;animation:.25s cubic-bezier(.16,1,.3,1) slideInRight;display:flex;box-shadow:-10px 0 35px #0000008c}.sync-drawer-header{border-bottom:1px solid var(--border-card);background:#0003;justify-content:space-between;align-items:flex-start;gap:14px;padding:18px 22px;display:flex}.sync-drawer-title-group{flex-direction:column;flex:1;gap:4px;display:flex}.sync-drawer-body{flex-direction:column;flex:1;gap:18px;padding:20px 22px;display:flex;overflow-y:auto}.sync-progress-section{border:1px solid var(--border-card);background:#0003;border-radius:8px;padding:12px 14px}.sync-progress-bar-container{background:#ffffff14;border-radius:4px;width:100%;height:8px;overflow:hidden}.sync-progress-bar-fill{border-radius:4px;height:100%;transition:width .3s,background .3s}.sync-progress-bar-fill.fill-active{background:var(--primary)}.sync-progress-bar-fill.fill-success{background:#10b981}.sync-progress-bar-fill.fill-error{background:#ef4444}.sync-progress-container{box-sizing:border-box;cursor:pointer;-webkit-user-select:none;user-select:none;flex-direction:column;gap:5px;width:100%;min-width:140px;max-width:100%;transition:opacity .2s,transform .15s;display:flex}.sync-progress-container:hover{opacity:.92}.sync-progress-container:focus-visible{outline:2px solid var(--primary);border-radius:6px}.progress-pill{white-space:nowrap;box-sizing:border-box;border-radius:12px;align-items:center;gap:6px;min-width:0;max-width:100%;padding:3px 8px;font-size:.76rem;font-weight:600;display:inline-flex}.progress-pill>span{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.sync-progress-bar-wrapper{background:#ffffff14;border-radius:3px;width:100%;height:6px;overflow:hidden}.sync-file-caption{color:var(--text-muted);font-size:.72rem;font-family:var(--font-family-mono);text-overflow:ellipsis;white-space:nowrap;align-items:center;min-width:0;max-width:100%;display:flex;overflow:hidden}.sync-file-caption code{text-overflow:ellipsis;white-space:nowrap;max-width:100%;padding:1px 4px;font-size:.72rem;overflow:hidden}.sync-stepper-container{border:1px solid var(--border-card);background:#0003;border-radius:8px;padding:14px 16px}.sync-stepper-list{flex-direction:column;gap:10px;display:flex}.sync-stepper-item{border-radius:6px;align-items:flex-start;gap:12px;padding:8px 10px;transition:background .15s;display:flex}.sync-stepper-item.item-active{background:#f59e0b1a;border:1px solid #f59e0b40}.sync-stepper-item.item-completed{background:#10b9810d}.sync-stepper-item.item-error{background:#ef44441a;border:1px solid #ef444440}.sync-stepper-icon{justify-content:center;align-items:center;min-width:18px;margin-top:1px;font-size:1rem;display:flex}.sync-stepper-content{color:var(--text);flex-direction:column;flex:1;gap:2px;font-size:.88rem;display:flex}.sync-stepper-details{margin-top:4px;font-size:.8rem}.sync-current-file{font-family:var(--font-family-mono);color:var(--accent,#60a5fa);word-break:break-all;font-size:.78rem}.sync-terminal-section{background:#0d1117;border:1px solid #ffffff1f;border-radius:8px;flex-direction:column;flex:1;min-height:260px;display:flex;overflow:hidden}.sync-terminal-header{background:#161b22;border-bottom:1px solid #ffffff1a;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;padding:10px 14px;display:flex}.sync-terminal-actions{align-items:center;gap:12px;display:flex}.sync-autoscroll-toggle{color:var(--text-muted);cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:6px;font-size:.78rem;display:flex}.sync-terminal-search{background:#11161d;border-bottom:1px solid #ffffff14;align-items:center;gap:8px;padding:6px 14px;display:flex}.sync-terminal-body{font-family:var(--font-family-mono,monospace);color:#e6edf3;flex:1;min-height:180px;max-height:380px;padding:12px 14px;font-size:.8rem;overflow-y:auto}.sync-terminal-empty{color:#8b949e;flex-direction:column;justify-content:center;align-items:center;height:160px;font-size:.85rem;display:flex}.sync-terminal-lines{flex-direction:column;gap:4px;display:flex}.sync-terminal-line{word-break:break-all;align-items:flex-start;gap:8px;line-height:1.45;display:flex}.log-time{color:#8b949e;flex-shrink:0;font-size:.75rem}.log-badge{border-radius:3px;flex-shrink:0;padding:0 4px;font-size:.7rem;font-weight:700}.log-badge.level-info{color:#38bdf8;background:#38bdf833}.log-badge.level-warn{color:#fbbf24;background:#fbbf2433}.log-badge.level-error{color:#f87171;background:#f8717133}.log-msg{color:#e6edf3;flex:1}theme-editor-overlay{pointer-events:none!important}theme-editor-overlay button,theme-editor-overlay [role=button],theme-editor-overlay a{pointer-events:auto!important}.topology-container{flex-direction:column;gap:16px;display:flex;position:relative}.topology-toolbar{background:var(--bg-card);border:1px solid var(--border-card);border-radius:var(--radius);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;padding:14px 18px;display:flex}.topology-toolbar-group{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.topology-select,.topology-input{border:1px solid var(--border-card);color:var(--text);background:#00000059;border-radius:8px;outline:none;padding:7px 12px;font-family:inherit;font-size:.85rem;transition:all .2s}.topology-select:focus,.topology-input:focus{border-color:var(--primary);box-shadow:0 0 0 2px #3b82f633}.topology-view-btn-group{border:1px solid var(--border-card);background:#00000059;border-radius:8px;display:inline-flex;overflow:hidden}.topology-view-btn{color:var(--text-muted);cursor:pointer;background:0 0;border:none;padding:6px 12px;font-size:.82rem;transition:all .15s}.topology-view-btn:hover{color:var(--text)}.topology-view-btn.active{background:var(--primary);color:#fff;font-weight:500}.topology-filter-chip{cursor:pointer;-webkit-user-select:none;user-select:none;border:1px solid #0000;border-radius:6px;align-items:center;gap:6px;padding:4px 10px;font-size:.78rem;font-weight:500;transition:all .15s;display:inline-flex}.topology-filter-chip.chip-file{color:#38bdf8;background:#0ea5e926;border-color:#0ea5e94d}.topology-filter-chip.chip-class{color:#c084fc;background:#a855f726;border-color:#a855f74d}.topology-filter-chip.chip-function{color:#34d399;background:#10b98126;border-color:#10b9814d}.topology-filter-chip.chip-route{color:#fbbf24;background:#f59e0b26;border-color:#f59e0b4d}.topology-filter-chip.chip-orphan{color:#fb7185;background:#f43f5e26;border-color:#f43f5e4d}.topology-filter-chip.inactive{opacity:.4;filter:grayscale(80%);color:var(--text-muted);border-color:var(--border-card);background:#ffffff0d}.topology-canvas-wrapper{border:1px solid var(--border-card);border-radius:var(--radius);cursor:grab;background:radial-gradient(circle,#121a2fbf 0%,#0a0f1df2 100%);width:100%;height:640px;position:relative;overflow:hidden}.topology-canvas-wrapper:active{cursor:grabbing}.topology-canvas-wrapper svg{width:100%;height:100%;display:block}.topology-node{cursor:pointer;transition:filter .15s,transform .15s}.topology-node:hover{filter:drop-shadow(0 0 8px #3b82f6cc)}.topology-node.selected{filter:drop-shadow(0 0 12px #14b8a6f2)}.topology-node.highlighted{filter:drop-shadow(0 0 14px #f59e0b)}.topology-edge{transition:stroke-opacity .2s,stroke-width .2s}.topology-controls-panel{border:1px solid var(--border-card);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);z-index:10;background:#121a2fd9;border-radius:10px;align-items:center;gap:6px;padding:6px 10px;display:flex;position:absolute;bottom:20px;left:20px}.topology-btn-ctrl{color:var(--text-muted);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:6px 8px;font-size:.85rem;transition:all .15s}.topology-btn-ctrl:hover{color:var(--text);background:#ffffff1a}.topology-legend{border:1px solid var(--border-card);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);z-index:10;background:#121a2fd9;border-radius:10px;flex-wrap:wrap;gap:8px;padding:8px 12px;display:flex;position:absolute;top:20px;left:20px}.topology-minimap{border:1px solid var(--border-card);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);z-index:10;pointer-events:none;background:#0a0f1dd9;border-radius:8px;width:140px;height:95px;position:absolute;bottom:20px;right:20px;overflow:hidden}.topology-minimap svg{width:100%;height:100%}.topology-drawer{border-left:1px solid var(--border-card);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);z-index:50;background:#0f172af2;flex-direction:column;width:440px;max-width:90%;height:100%;animation:.25s cubic-bezier(.16,1,.3,1) slideInRight;display:flex;position:absolute;top:0;right:0;box-shadow:-10px 0 30px #00000080}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}.topology-drawer-header{border-bottom:1px solid var(--border-card);justify-content:space-between;align-items:center;padding:16px 20px;display:flex}.topology-drawer-body{flex-direction:column;flex:1;gap:16px;padding:20px;display:flex;overflow-y:auto}.topology-drawer-section{flex-direction:column;gap:8px;display:flex}.topology-drawer-section-title{text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);font-size:.8rem;font-weight:600}.topology-code-snippet{background:var(--code-bg);border:1px solid var(--border-card);font-family:var(--font-family-mono);color:var(--text);white-space:pre-wrap;border-radius:8px;max-height:220px;padding:12px;font-size:.78rem;line-height:1.45;overflow-x:auto}.topology-neighbor-list{flex-direction:column;gap:6px;display:flex}.topology-neighbor-item{border:1px solid var(--border-card);cursor:pointer;background:#ffffff08;border-radius:6px;justify-content:space-between;align-items:center;padding:8px 12px;font-size:.82rem;transition:all .15s;display:flex}.topology-neighbor-item:hover{border-color:var(--primary);background:#ffffff12}.topology-badge-type{text-transform:uppercase;border-radius:4px;align-items:center;gap:4px;padding:2px 8px;font-size:.72rem;font-weight:600;display:inline-flex}.badge-file{color:#38bdf8;background:#0ea5e933}.badge-class{color:#c084fc;background:#a855f733}.badge-function{color:#34d399;background:#10b98133}.badge-route{color:#fbbf24;background:#f59e0b33}.badge-module{color:#818cf8;background:#6366f133}.badge-edge-imports{color:#38bdf8;background:#0ea5e926}.badge-edge-calls{color:#34d399;background:#10b98126}.badge-edge-defines{color:#c084fc;background:#a855f726}.badge-edge-handles{color:#fbbf24;background:#f59e0b26}.badge-edge-routes-to{color:#fb7185;background:#f43f5e26}.badge-edge-doc-links-to{color:#a78bfa;background:#a78bfa26}.topology-search-results{border:1px solid var(--border-card);z-index:60;background:#0f172afa;border-radius:8px;max-height:240px;margin-top:4px;position:absolute;top:100%;left:0;right:0;overflow-y:auto;box-shadow:0 10px 25px #00000080}.topology-search-item{cursor:pointer;border-bottom:1px solid #ffffff0d;justify-content:space-between;align-items:center;padding:8px 12px;font-size:.82rem;display:flex}.topology-search-item:hover{background:#3b82f626}.topology-empty-state{height:100%;color:var(--text-muted);flex-direction:column;justify-content:center;align-items:center;gap:12px;display:flex}.neighborhood-view{flex-direction:column;gap:14px;width:100%;display:flex;position:relative}.neighborhood-breadcrumbs{background:var(--bg-card);border:1px solid var(--border-card);border-radius:var(--radius);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;padding:10px 16px;display:flex}.neighborhood-breadcrumbs-left{flex-wrap:wrap;align-items:center;gap:12px;display:flex}.neighborhood-breadcrumbs-right{flex-wrap:wrap;align-items:center;gap:14px;display:flex}.neighborhood-focal-selector-wrap{align-items:center;gap:6px;display:inline-flex}.neighborhood-focal-label{color:var(--text-muted);white-space:nowrap;font-size:.78rem;font-weight:600}.neighborhood-focal-select{max-width:260px;padding:4px 10px;font-size:.82rem}.neighborhood-back-btn{align-items:center;gap:6px;padding:5px 10px;font-size:.8rem;display:inline-flex}.neighborhood-breadcrumb-trail{flex-wrap:wrap;align-items:center;gap:6px;display:inline-flex}.neighborhood-breadcrumb-sep{color:var(--text-muted);-webkit-user-select:none;user-select:none;margin:0 2px;font-size:.9rem}.neighborhood-breadcrumb-item{cursor:pointer;color:var(--text-muted);background:0 0;border:1px solid #0000;border-radius:6px;align-items:center;gap:6px;padding:3px 8px;font-size:.82rem;transition:all .15s;display:inline-flex}.neighborhood-breadcrumb-item:hover{color:var(--text);background:#ffffff0f}.neighborhood-breadcrumb-item.active{color:var(--text);background:#3b82f626;border-color:#3b82f64d;font-weight:600}.neighborhood-breadcrumb-badge{padding:1px 5px;font-size:.65rem}.neighborhood-breadcrumb-text{font-family:var(--font-family-mono,monospace)}.neighborhood-hop-selector{align-items:center;gap:8px;display:inline-flex}.neighborhood-hop-label{color:var(--text-muted);font-size:.8rem;font-weight:500}.neighborhood-panels{grid-template-columns:280px 1fr 280px;gap:14px;min-height:640px;display:grid}@media (width<=1100px){.neighborhood-panels{grid-template-columns:1fr}}.neighborhood-panel{background:var(--bg-card);border:1px solid var(--border-card);border-radius:var(--radius);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-direction:column;max-height:640px;padding:14px;display:flex;overflow:hidden}.neighborhood-panel-header{border-bottom:1px solid var(--border-card);justify-content:space-between;align-items:center;margin-bottom:10px;padding-bottom:10px;display:flex}.neighborhood-panel-title{color:var(--text);align-items:center;gap:8px;font-size:.85rem;font-weight:600;display:flex}.neighborhood-panel-count{color:var(--text-muted);background:#ffffff14;border-radius:12px;padding:2px 8px;font-size:.75rem;font-weight:600}.neighborhood-panel-list{flex-direction:column;flex:1;gap:8px;padding-right:2px;display:flex;overflow-y:auto}.neighborhood-panel-item{border:1px solid var(--border-card);background:#ffffff05;border-radius:8px;flex-direction:column;gap:6px;padding:10px;transition:all .15s;display:flex}.neighborhood-panel-item:hover{background:#ffffff0d;border-color:#3b82f666}.neighborhood-panel-item-header{justify-content:space-between;align-items:center;gap:6px;display:flex}.neighborhood-panel-item-name{font-family:var(--font-family-mono,monospace);color:var(--text);word-break:break-all;font-size:.8rem}.neighborhood-panel-item-actions{gap:6px;margin-top:4px;display:flex}.neighborhood-panel-empty{text-align:center;color:var(--text-muted);padding:24px 8px;font-size:.8rem}.neighborhood-center{flex-direction:column;min-height:640px;display:flex;position:relative}.neighborhood-canvas-wrapper{border:1px solid var(--border-card);border-radius:var(--radius);background:radial-gradient(circle,#121a2fbf 0%,#0a0f1df2 100%);width:100%;height:100%;min-height:640px;position:relative;overflow:hidden}.neighborhood-svg{width:100%;height:100%;display:block}.neighborhood-orbital-ring{fill:none;stroke:#94a3b82e;stroke-dasharray:4 4;stroke-width:1.5px}.neighborhood-orbital-ring.ring-2{stroke:#94a3b81f}.neighborhood-orbital-label{fill:var(--text-muted);letter-spacing:.04em;pointer-events:none;text-transform:uppercase;font-size:10px}.neighborhood-edge-path{transition:stroke-opacity .2s,stroke-width .2s}.neighborhood-edge-path:hover{stroke-opacity:1;stroke-width:2.5px}.neighborhood-node-item{transition:transform .15s,filter .15s}.neighborhood-node-item:hover{filter:drop-shadow(0 0 10px #3b82f6d9)}.neighborhood-node-circle{transition:filter .15s,stroke-width .15s}.neighborhood-node-name{font-family:var(--font-family-mono,monospace);font-weight:500}.focal-pulse-ring{transform-origin:50%;animation:2.5s cubic-bezier(.4,0,.6,1) infinite pulse-ring}@keyframes pulse-ring{0%{opacity:.8;transform:scale(.95)}50%{opacity:.2;transform:scale(1.15)}to{opacity:.8;transform:scale(.95)}}.neighborhood-focal-node:hover .focal-body{filter:drop-shadow(0 0 12px #3b82f6e6)}.neighborhood-empty-state{background:var(--bg-card);border:1px solid var(--border-card);border-radius:var(--radius);height:480px}.topology-physics-panel,.topology-physics-popover{z-index:100;border:1px solid var(--border-card);border-radius:var(--radius);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);background:#0f172af2;flex-direction:column;gap:14px;width:calc(100% - 40px);min-width:320px;max-width:380px;padding:16px;animation:.2s cubic-bezier(.16,1,.3,1) fadeInPhysics;display:flex;position:absolute;top:70px;right:20px;box-shadow:0 12px 32px #0000008c}@keyframes fadeInPhysics{0%{opacity:0;transform:translateY(-8px)scale(.98)}to{opacity:1;transform:translateY(0)scale(1)}}.topology-physics-header{border-bottom:1px solid var(--border-card);justify-content:space-between;align-items:center;gap:8px;padding-bottom:10px;display:flex}.topology-physics-title-wrap{align-items:center;gap:8px;display:flex}.topology-physics-title{color:var(--text);margin:0;font-size:.9rem;font-weight:600}.topology-physics-body{flex-direction:column;gap:14px;display:flex}.topology-physics-section{flex-direction:column;gap:8px;display:flex}.topology-physics-section-title{text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);font-size:.75rem;font-weight:600}.topology-physics-presets-grid{grid-template-columns:repeat(2,1fr);gap:6px;display:grid}.topology-physics-preset-btn{border:1px solid var(--border-card);color:var(--text-muted);cursor:pointer;background:#ffffff0a;border-radius:6px;align-items:center;gap:6px;padding:6px 10px;font-size:.78rem;font-weight:500;transition:all .15s;display:flex}.topology-physics-preset-btn:hover{color:var(--text);border-color:var(--primary);background:#ffffff14}.topology-physics-preset-btn.active{color:#60a5fa;background:#3b82f633;border-color:#3b82f680;font-weight:600}.topology-physics-sliders-list{flex-direction:column;gap:10px;display:flex}.topology-physics-slider-row{flex-direction:column;gap:4px;display:flex}.topology-physics-slider-header{justify-content:space-between;align-items:center;font-size:.8rem;display:flex}.topology-physics-slider-label{color:var(--text);cursor:pointer;align-items:center;gap:4px;font-weight:500;display:inline-flex}.topology-physics-symbol{color:var(--text-muted);font-size:.72rem;font-family:var(--font-family-mono,monospace)}.topology-physics-value-chip{font-family:var(--font-family-mono,monospace);color:var(--primary,#38bdf8);background:#ffffff0f;border-radius:4px;padding:2px 6px;font-size:.75rem;font-weight:600}.topology-physics-range-input{appearance:none;cursor:pointer;background:#ffffff1f;border-radius:3px;outline:none;width:100%;height:6px;transition:background .15s}.topology-physics-range-input:hover{background:#ffffff2e}.topology-physics-range-input::-webkit-slider-thumb{appearance:none;background:var(--primary,#3b82f6);cursor:pointer;border-radius:50%;width:14px;height:14px;transition:transform .1s;box-shadow:0 0 6px #3b82f699}.topology-physics-range-input::-webkit-slider-thumb:hover{transform:scale(1.2)}.topology-physics-range-input::-moz-range-thumb{background:var(--primary,#3b82f6);cursor:pointer;border:none;border-radius:50%;width:14px;height:14px}.topology-physics-actions{gap:8px;margin-top:4px;display:flex}.topology-physics-action-btn{flex:1;justify-content:center;align-items:center;gap:6px;font-size:.8rem;display:inline-flex}.code-navigator-container,.code-navigator-container.density-balanced{--nav-item-height:28px;--nav-font-size:.84rem;--nav-item-padding:5px 10px;--nav-icon-size:15px;--nav-badge-font-size:.72rem;--nav-card-padding:12px 14px;--nav-header-padding:10px 14px}.code-navigator-container.density-compact{--nav-item-height:22px;--nav-font-size:.76rem;--nav-item-padding:2px 8px;--nav-icon-size:13px;--nav-badge-font-size:.66rem;--nav-card-padding:8px 10px;--nav-header-padding:6px 10px}.code-navigator-container.density-spacious{--nav-item-height:36px;--nav-font-size:.9rem;--nav-item-padding:8px 14px;--nav-icon-size:17px;--nav-badge-font-size:.78rem;--nav-card-padding:16px 18px;--nav-header-padding:14px 18px}.code-navigator-container{background-color:var(--bg-base,#07181b);height:100%;min-height:calc(100vh - 80px);color:var(--text,#f8fafc);font-family:var(--font-family-base,"Outfit", sans-serif);box-sizing:border-box;flex-direction:column;display:flex;overflow:hidden}.nav-toolbar{background:var(--bg-card,#0d2c2f);border-bottom:1px solid var(--border-card,#15474d);z-index:10;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:16px;padding:8px 16px;display:flex}.nav-toolbar-left{flex-shrink:0;align-items:center;gap:16px;display:flex}.nav-repo-selector-wrapper{align-items:center;gap:8px;display:flex}.nav-repo-label{color:var(--text-muted,#94a3b8);font-size:.82rem;font-weight:500}.nav-repo-select{border:1px solid var(--border-card,#15474d);color:var(--text,#f8fafc);cursor:pointer;background:#0006;border-radius:6px;outline:none;padding:5px 12px;font-size:.84rem;transition:border-color .2s,box-shadow .2s}.nav-repo-select:focus{border-color:var(--primary,#0891b2);box-shadow:0 0 0 2px #0891b240}.nav-stats-badges{align-items:center;gap:8px;display:flex}.nav-stat-badge{border:1px solid var(--border-card,#15474d);color:var(--text-muted,#94a3b8);background:#15474d80;border-radius:12px;padding:3px 8px;font-size:.76rem}.nav-stat-badge .stat-num{color:var(--accent,#14b8a6);font-weight:600}.nav-toolbar-center{flex:1;min-width:200px;max-width:400px}.nav-global-search{border:1px solid var(--border-card,#15474d);box-sizing:border-box;background:#00000059;border-radius:6px;align-items:center;width:100%;padding:0 10px;transition:all .2s;display:flex;position:relative}.nav-global-search:focus-within{border-color:var(--primary,#0891b2);box-shadow:0 0 0 2px #0891b240}.nav-global-search .search-icon-wrapper{width:16px;height:16px;color:var(--text-muted,#94a3b8);pointer-events:none;flex-shrink:0;justify-content:center;align-items:center;margin-right:8px;display:flex}.nav-search-input{color:var(--text,#f8fafc);background:0 0;border:none;outline:none;flex:1;min-width:0;padding:6px 0;font-size:.82rem}.nav-search-clear-btn{color:var(--text-muted,#94a3b8);cursor:pointer;background:0 0;border:none;flex-shrink:0;margin-left:4px;padding:2px 4px;font-size:.75rem}.nav-search-clear-btn:hover{color:var(--text,#f8fafc)}.nav-omni-container{width:100%;position:relative}.nav-omni-input-wrapper{border:1px solid var(--border-card,#15474d);box-sizing:border-box;background:#00000059;border-radius:6px;align-items:center;width:100%;padding:0 10px;transition:all .2s;display:flex;position:relative}.nav-omni-input-wrapper:focus-within{border-color:var(--primary,#0891b2);box-shadow:0 0 0 2px #0891b240}.nav-omni-search-icon{color:var(--text-muted,#94a3b8);pointer-events:none;flex-shrink:0;margin-right:8px}.nav-omni-input{color:var(--text,#f8fafc);background:0 0;border:none;outline:none;flex:1;min-width:0;padding:6px 0;font-size:.82rem}.nav-omni-clear-btn{color:var(--text-muted,#94a3b8);cursor:pointer;background:0 0;border:none;flex-shrink:0;margin-left:4px;padding:2px 4px;font-size:.75rem}.nav-omni-clear-btn:hover{color:var(--text,#f8fafc)}.nav-omni-spinner{border:2px solid #0891b24d;border-top-color:var(--primary,#0891b2);border-radius:50%;flex-shrink:0;width:12px;height:12px;margin-left:6px;animation:.6s linear infinite nav-spin}@keyframes nav-spin{to{transform:rotate(360deg)}}.nav-omni-dropdown{z-index:50;background:var(--bg-card,#0d2c2f);border:1px solid var(--border-card,#15474d);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-radius:8px;flex-direction:column;max-height:420px;display:flex;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow:hidden;box-shadow:0 15px 30px #0009,0 5px 15px #0006}.nav-omni-dropdown-header{border-bottom:1px solid var(--border-card,#15474d);color:var(--text-muted,#94a3b8);text-transform:uppercase;letter-spacing:.05em;background:#0000004d;justify-content:space-between;align-items:center;padding:8px 12px;font-size:.72rem;font-weight:600;display:flex}.nav-omni-hint{color:var(--text-muted,#64748b);text-transform:none;font-size:.68rem}.nav-omni-empty{text-align:center;color:var(--text-muted,#94a3b8);padding:16px;font-size:.8rem}.nav-omni-list{padding:4px;overflow-y:auto}.nav-omni-item{cursor:pointer;border:1px solid #0000;border-radius:6px;flex-direction:column;margin-bottom:2px;padding:8px 10px;transition:background-color .12s;display:flex}.nav-omni-item:hover,.nav-omni-item.active{background:#0891b226;border-color:#0891b259}.nav-omni-item-top{justify-content:space-between;align-items:center;gap:8px;margin-bottom:4px;display:flex}.nav-omni-item-title-group{align-items:center;gap:6px;display:flex;overflow:hidden}.nav-omni-badge{text-transform:uppercase;letter-spacing:.04em;font-size:.65rem;font-weight:700;font-family:var(--font-mono,monospace);border-radius:3px;flex-shrink:0;padding:1px 5px}.badge-symbol{color:#38bdf8;background:#0ea5e933;border:1px solid #38bdf859}.badge-code{color:#c084fc;background:#a855f733;border:1px solid #c084fc59}.badge-file{color:#2dd4bf;background:#14b8a633;border:1px solid #2dd4bf59}.badge-route{color:#4ade80;background:#22c55e33;border:1px solid #4ade8059}.badge-doc{color:#fbbf24;background:#f59e0b33;border:1px solid #fbbf2459}.badge-default{color:#cbd5e1;background:#94a3b833}.nav-omni-item-name{color:var(--text,#f8fafc);font-size:.82rem;font-weight:600;font-family:var(--font-mono,monospace);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.nav-omni-item-kind{color:var(--text-muted,#94a3b8);font-size:.72rem}.nav-omni-score-pill{color:#34d399;white-space:nowrap;background:#10b98126;border:1px solid #34d3994d;border-radius:10px;flex-shrink:0;padding:1px 7px;font-size:.68rem;font-weight:600}.nav-omni-item-bottom{color:var(--text-muted,#94a3b8);font-size:.72rem;font-family:var(--font-mono,monospace);align-items:center;gap:8px;display:flex;overflow:hidden}.nav-omni-item-path{color:var(--primary,#38bdf8);flex-shrink:0}.nav-omni-item-preview{color:var(--text-muted,#94a3b8);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.nav-code-viewer-container{background:var(--bg-card,#0d2c2f);border:1px solid var(--border-card,#15474d);border-radius:8px;flex-direction:column;max-width:100%;height:100%;display:flex;position:relative;overflow:hidden}.nav-code-viewer-header{border-bottom:1px solid var(--border-card,#15474d);background:#0006;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;max-width:100%;padding:8px 14px;display:flex}.nav-code-viewer-meta{align-items:center;gap:8px;max-width:100%;display:flex;overflow:hidden}.nav-code-file-icon{color:var(--primary,#38bdf8);flex-shrink:0}.nav-code-filename{color:var(--text,#f8fafc);font-size:.84rem;font-weight:700;font-family:var(--font-mono,monospace);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.nav-code-stat-chip{color:var(--text-muted,#94a3b8);font-size:.72rem;font-family:var(--font-mono,monospace);background:#ffffff14;border-radius:4px;flex-shrink:0;padding:1px 6px}.nav-code-viewer-actions{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.nav-code-action-btn{border:1px solid var(--border-card,#15474d);color:var(--text-muted,#cbd5e1);cursor:pointer;background:#ffffff0f;border-radius:4px;padding:3px 8px;font-size:.74rem;transition:all .15s}.nav-code-action-btn:hover{color:var(--text,#f8fafc);border-color:var(--primary,#0891b2);background:#ffffff1f}.nav-code-impact-toggle-btn{color:#c084fc;cursor:pointer;background:#a855f726;border:1px solid #a855f759;border-radius:4px;align-items:center;gap:5px;padding:3px 9px;font-size:.74rem;font-weight:600;transition:all .15s;display:flex}.nav-code-impact-toggle-btn:hover,.nav-code-impact-toggle-btn.active{color:#f3e8ff;background:#a855f747;border-color:#a855f7}.nav-code-body{background:#061113;outline:none;flex:1;max-width:100%;padding:8px 0;position:relative;overflow:hidden auto}.nav-code-lines-wrapper{flex-direction:column;width:100%;max-width:100%;display:flex}.nav-code-line-row{box-sizing:border-box;max-width:100%;min-height:20px;padding:0 4px;line-height:20px;transition:background-color .1s;display:flex}.nav-code-line-row:hover{background:#ffffff08}.nav-code-line-number{text-align:right;width:44px;min-width:44px;font-size:.72rem;font-family:var(--font-mono,monospace);color:#475569;-webkit-user-select:none;user-select:none;flex-shrink:0;padding-right:12px}.nav-code-line-content{font-size:.78rem;font-family:var(--font-mono,monospace);color:#cbd5e1;white-space:pre-wrap;word-break:break-all;overflow-wrap:anywhere;tab-size:4;flex:1;min-width:0}.nav-code-line-target{background:#0ea5e92e!important;border-left:3px solid #38bdf8!important}.nav-code-line-target .nav-code-line-number{font-weight:700;color:#38bdf8!important}.nav-code-line-target .nav-code-line-content{font-weight:500;color:#fff!important}.nav-code-impact-drawer{border-top:1px solid var(--border-card,#15474d);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background:#091d20f2;flex-direction:column;max-height:260px;animation:.18s ease-out slide-up;display:flex}@keyframes slide-up{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}.nav-impact-drawer-header{border-bottom:1px solid var(--border-card,#15474d);background:#00000059;justify-content:space-between;align-items:center;padding:6px 12px;display:flex}.nav-impact-drawer-title{align-items:center;gap:8px;font-size:.76rem;display:flex}.nav-impact-symbol-name{color:var(--text,#f8fafc);font-family:var(--font-mono,monospace)}.nav-impact-symbol-lines{color:var(--text-muted,#94a3b8);font-size:.7rem}.nav-impact-close-btn{color:var(--text-muted,#94a3b8);cursor:pointer;background:0 0;border:none;padding:2px 6px;font-size:.8rem}.nav-impact-close-btn:hover{color:var(--text,#f8fafc)}.nav-impact-drawer-body{grid-template-columns:1fr 1fr;gap:12px;padding:10px 14px;display:grid;overflow-y:auto}.nav-impact-column-title{text-transform:uppercase;color:var(--text-muted,#94a3b8);margin-bottom:6px;font-size:.72rem;font-weight:600}.nav-impact-empty{color:var(--text-muted,#64748b);font-size:.72rem;font-style:italic}.nav-impact-chips-list{flex-direction:column;gap:4px;display:flex}.nav-impact-chip{border:1px solid var(--border-card,#15474d);cursor:pointer;text-align:left;color:var(--text,#f8fafc);background:#0000004d;border-radius:4px;align-items:center;gap:6px;padding:3px 8px;font-size:.72rem;transition:all .12s;display:flex}.nav-impact-chip:hover{border-color:var(--primary,#0891b2);background:#0891b233}.nav-impact-chip.caller .chip-name{color:#38bdf8;font-weight:600;font-family:var(--font-mono,monospace)}.nav-impact-chip.callee .chip-name{color:#a7f3d0;font-weight:600;font-family:var(--font-mono,monospace)}.nav-impact-chip .chip-path{color:var(--text-muted,#94a3b8);font-size:.68rem;font-family:var(--font-mono,monospace)}.nav-impact-chip .chip-line{color:#facc15;font-size:.68rem}.nav-toolbar-right{flex-shrink:0;align-items:center;gap:12px;display:flex}.nav-density-switcher{border:1px solid var(--border-card,#15474d);background:#0000004d;border-radius:6px;align-items:center;gap:2px;padding:2px;display:flex}.nav-density-switcher .density-label{color:var(--text-muted,#94a3b8);padding:0 6px;font-size:.76rem}.density-btn{color:var(--text-muted,#94a3b8);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:3px 8px;font-size:.76rem;font-weight:500;transition:all .15s}.density-btn:hover{color:var(--text,#f8fafc);background:#ffffff0d}.density-btn.active{background:var(--primary,#0891b2);color:#fff}.nav-refresh-btn{border:1px solid var(--border-card,#15474d);color:var(--text-muted,#94a3b8);cursor:pointer;background:#0000004d;border-radius:6px;justify-content:center;align-items:center;padding:6px;transition:all .2s;display:flex}.nav-refresh-btn:hover:not(:disabled){color:var(--accent,#14b8a6);border-color:var(--accent,#14b8a6)}.nav-refresh-btn.spinning svg{animation:1s linear infinite spin}.nav-error-banner{border-bottom:1px solid var(--danger,#ef4444);color:#fca5a5;background:#ef444426;align-items:center;gap:10px;padding:8px 16px;font-size:.84rem;display:flex}.nav-error-banner span{flex:1}.error-close-btn{color:#fca5a5;cursor:pointer;background:0 0;border:none;font-size:.9rem}.nav-panes-layout{background:var(--bg-base,#07181b);flex:1;display:flex;position:relative;overflow:hidden}.nav-pane-column{border-right:1px solid var(--border-card,#15474d);flex-direction:column;height:100%;display:flex;overflow:hidden}.nav-pane-column:last-child{border-right:none}.nav-pane-tree{background:#07181bf2;flex:0 0 320px;min-width:250px;max-width:420px}.nav-pane-outline{background:#0a2024f2;flex:0 0 360px;min-width:280px;max-width:480px}.nav-pane-inspector{background:var(--bg-base,#07181b);flex:400px;min-width:320px}.nav-tree-header,.nav-outline-header,.nav-inspector-header{padding:var(--nav-header-padding);background:var(--bg-card,#0d2c2f);border-bottom:1px solid var(--border-card,#15474d);flex-direction:column;flex-shrink:0;gap:8px;display:flex}.nav-inspector-header{box-sizing:border-box;flex-flow:wrap;justify-content:space-between;align-items:center;gap:8px;min-width:0;max-width:100%}.nav-tree-title-row,.nav-outline-title-row{justify-content:space-between;align-items:center;gap:8px;display:flex}.nav-tree-title,.nav-outline-title,.nav-inspector-title{color:var(--text,#f8fafc);text-transform:uppercase;letter-spacing:.04em;align-items:center;gap:8px;font-size:.86rem;font-weight:600;display:flex}.nav-tree-actions{align-items:center;gap:4px;display:flex}.nav-icon-btn{color:var(--text-muted,#94a3b8);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:4px;justify-content:center;align-items:center;padding:4px;display:flex}.nav-icon-btn:hover{color:var(--text,#f8fafc);border-color:var(--border-card,#15474d);background:#ffffff0d}.nav-file-badge{color:var(--accent-light,#2dd4bf);text-overflow:ellipsis;white-space:nowrap;background:#14b8a626;border:1px solid #14b8a64d;border-radius:12px;max-width:180px;padding:2px 8px;font-size:.74rem;overflow:hidden}.nav-tree-search-bar,.nav-outline-search-bar{border:1px solid var(--border-card,#15474d);box-sizing:border-box;background:#0000004d;border-radius:5px;align-items:center;width:100%;padding:0 8px;display:flex;position:relative}.nav-tree-search-bar:focus-within,.nav-outline-search-bar:focus-within{border-color:var(--primary,#0891b2)}.nav-tree-search-bar .search-icon-wrapper,.nav-outline-search-bar .search-icon-wrapper{width:14px;height:14px;color:var(--text-muted,#94a3b8);pointer-events:none;flex-shrink:0;justify-content:center;align-items:center;margin-right:6px;display:flex}.nav-tree-search-input,.nav-outline-search-input{color:var(--text,#f8fafc);font-size:var(--nav-font-size);background:0 0;border:none;outline:none;flex:1;min-width:0;padding:4px 0}.nav-tree-clear-btn,.nav-outline-clear-btn{color:var(--text-muted,#94a3b8);cursor:pointer;background:0 0;border:none;flex-shrink:0;margin-left:4px;font-size:.72rem}.nav-category-chips{align-items:center;gap:4px;padding-bottom:2px;display:flex;overflow-x:auto}.category-chip{border:1px solid var(--border-card,#15474d);color:var(--text-muted,#94a3b8);cursor:pointer;white-space:nowrap;background:#0000004d;border-radius:12px;align-items:center;gap:4px;padding:2px 8px;font-size:.72rem;transition:all .15s;display:flex}.category-chip:hover{color:var(--text,#f8fafc);border-color:#14b8a666}.category-chip.active{border-color:var(--primary,#0891b2);color:var(--accent-light,#2dd4bf);background:#0891b240;font-weight:600}.chip-count{opacity:.8;font-size:.68rem}.nav-tree-content,.nav-outline-content,.nav-inspector-content{flex:1;padding:6px 0;overflow:hidden auto}.nav-inspector-content{padding:14px}.nav-tree-pane{outline:none;flex-direction:column;height:100%;display:flex}.nav-tree-list{flex-direction:column;display:flex}.nav-tree-item{min-height:var(--nav-item-height);padding:var(--nav-item-padding);font-size:var(--nav-font-size);color:var(--text,#f8fafc);cursor:pointer;-webkit-user-select:none;user-select:none;border-left:2px solid #0000;align-items:center;gap:6px;transition:background-color .1s,border-color .1s;display:flex}.nav-tree-item:hover{background:#ffffff0a}.nav-tree-item.selected{border-left-color:var(--accent,#14b8a6);color:#fff;background:#0891b233;font-weight:500}.nav-tree-item.focused{outline:1px dotted var(--accent,#14b8a6);outline-offset:-1px}.tree-chevron{width:14px;color:var(--text-muted,#94a3b8);justify-content:center;align-items:center;font-size:.64rem;display:inline-flex}.chevron-arrow{transition:transform .15s;display:inline-block}.chevron-arrow.open{transform:rotate(90deg)}.chevron-spacer{width:14px}.tree-icon{font-size:var(--nav-icon-size);justify-content:center;align-items:center;width:16px;display:inline-flex}.tree-label{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.tree-badges{align-items:center;gap:4px;margin-left:auto;padding-right:6px;display:flex}.badge-symbols{font-size:var(--nav-badge-font-size);color:#67e8f9;font-family:var(--font-family-mono,monospace);background:#0891b226;border:1px solid #0891b24d;border-radius:8px;padding:1px 5px}.badge-routes{font-size:var(--nav-badge-font-size);color:#5eead4;font-family:var(--font-family-mono,monospace);background:#14b8a62e;border:1px solid #14b8a659;border-radius:8px;padding:1px 5px;font-weight:600}.nav-outline-pane{flex-direction:column;height:100%;display:flex}.nav-symbol-list{flex-direction:column;gap:4px;padding:6px 8px;display:flex}.nav-symbol-item{border:1px solid var(--border-card,#15474d);padding:var(--nav-card-padding);cursor:pointer;background:#0d2c2f66;border-radius:6px;flex-direction:column;gap:6px;transition:all .15s;display:flex}.nav-symbol-item:hover{background:#15474d66;border-color:#14b8a666}.nav-symbol-item.active{border-color:var(--accent,#14b8a6);background:#0891b238;box-shadow:0 0 10px #14b8a626}.symbol-header-row{justify-content:space-between;align-items:center;gap:8px;display:flex}.symbol-name-col{align-items:center;gap:6px;display:flex;overflow:hidden}.symbol-name-text{font-weight:600;font-size:var(--nav-font-size);color:var(--text,#f8fafc);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.symbol-meta-col{flex-shrink:0}.symbol-line-badge{font-size:var(--nav-badge-font-size);color:var(--text-muted,#94a3b8);font-family:var(--font-family-mono,monospace);background:#00000040;border-radius:4px;padding:1px 6px}.symbol-kind-badge{text-transform:uppercase;letter-spacing:.03em;border-radius:4px;flex-shrink:0;padding:1px 5px;font-size:.65rem;font-weight:700}.kind-class{color:#c084fc;background:#a855f733;border:1px solid #a855f766}.kind-function{color:#60a5fa;background:#3b82f633;border:1px solid #3b82f666}.kind-variable{color:#fbbf24;background:#f59e0b33;border:1px solid #f59e0b66}.kind-type{color:#2dd4bf;background:#14b8a633;border:1px solid #14b8a666}.kind-default{color:#cbd5e1;background:#94a3b833;border:1px solid #94a3b84d}.symbol-route-row{background:#0000004d;border-radius:4px;align-items:center;gap:6px;padding:3px 6px;font-size:.74rem;display:flex;overflow:hidden}.route-method-badge{text-transform:uppercase;border-radius:3px;flex-shrink:0;padding:1px 5px;font-size:.64rem;font-weight:700}.method-get{color:#a7f3d0;background:#065f46}.method-post{color:#bfdbfe;background:#1e40af}.method-put{color:#fde68a;background:#92400e}.method-delete{color:#fecaca;background:#991b1b}.method-patch{color:#ddd6fe;background:#5b21b6}.method-other{color:#e5e7eb;background:#374151}.route-path-text{font-family:var(--font-family-mono,monospace);color:#67e8f9;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.route-framework-tag{color:var(--text-muted,#94a3b8);margin-left:auto;padding-left:4px;font-size:.62rem}.symbol-signature-snippet{background:var(--code-bg,#00000059);font-family:var(--font-family-mono,monospace);color:#cbd5e1;text-overflow:ellipsis;white-space:nowrap;border-radius:4px;padding:4px 6px;font-size:.72rem;overflow-x:hidden}.nav-inspector-pane{box-sizing:border-box;flex-direction:column;min-width:0;max-width:100%;height:100%;display:flex;overflow-x:hidden}.nav-copy-permalink-btn{border:1px solid var(--border-card,#15474d);color:var(--text-muted,#94a3b8);cursor:pointer;box-sizing:border-box;background:#00000059;border-radius:6px;align-items:center;gap:6px;max-width:100%;padding:4px 10px;font-size:.76rem;transition:all .15s;display:flex}.nav-copy-permalink-btn:hover{color:var(--accent,#14b8a6);border-color:var(--accent,#14b8a6)}.nav-inspector-body{box-sizing:border-box;flex-direction:column;gap:12px;min-width:0;max-width:100%;display:flex}.inspector-card{background:var(--bg-card,#0d2c2f);border:1px solid var(--border-card,#15474d);padding:var(--nav-card-padding);box-sizing:border-box;border-radius:8px;min-width:0;max-width:100%;overflow-x:hidden}.card-section-title{color:var(--text-muted,#94a3b8);text-transform:uppercase;letter-spacing:.04em;align-items:center;gap:6px;margin-bottom:8px;font-size:.8rem;font-weight:600;display:flex}.symbol-summary-card{flex-direction:column;gap:6px;display:flex}.summary-title-row{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;max-width:100%;display:flex}.summary-name{color:#fff;overflow-wrap:anywhere;word-break:break-word;min-width:0;font-size:1.15rem;font-weight:700}.summary-location-row{color:var(--text-muted,#94a3b8);align-items:center;gap:12px;font-size:.8rem;display:flex}.summary-file-path{color:var(--accent-light,#2dd4bf);font-family:var(--font-family-mono,monospace);align-items:center;gap:4px;display:flex}.summary-line-range{font-family:var(--font-family-mono,monospace);background:#0000004d;border-radius:4px;padding:1px 6px}.inspector-metrics-grid{grid-template-columns:repeat(4,1fr);gap:10px;display:grid}.metric-box{background:var(--bg-card,#0d2c2f);border:1px solid var(--border-card,#15474d);text-align:center;border-radius:8px;flex-direction:column;align-items:center;padding:10px;display:flex}.metric-label{color:var(--text-muted,#94a3b8);margin-bottom:4px;font-size:.72rem}.metric-value{color:var(--accent,#14b8a6);font-size:1.25rem;font-weight:700}.metric-lang{text-transform:uppercase;font-size:.95rem}.route-detail-row{align-items:center;gap:8px;display:flex}.route-path-code{font-family:var(--font-family-mono,monospace);color:#67e8f9;font-size:.88rem;font-weight:600}.signature-code-block{background:var(--code-bg,#00000059);font-family:var(--font-family-mono,monospace);color:#f1f5f9;white-space:pre-wrap;word-break:break-all;border:1px solid #ffffff0d;border-radius:6px;padding:10px;font-size:.82rem;overflow-x:auto}.docstring-text{color:#cbd5e1;white-space:pre-wrap;font-size:.84rem;line-height:1.5}.relation-list{flex-direction:column;gap:6px;display:flex}.relation-item{border:1px solid var(--border-card,#15474d);background:#00000040;border-radius:6px;flex-direction:column;gap:4px;padding:8px 10px;transition:all .15s;display:flex}.relation-item.caller-item,.relation-item.callee-item.is-clickable{cursor:pointer}.relation-item.caller-item:hover,.relation-item.callee-item.is-clickable:hover{border-color:var(--accent,#14b8a6);background:#0891b22e;transform:translate(2px)}.relation-top{justify-content:space-between;align-items:center;display:flex}.rel-top-right{align-items:center;gap:6px;display:flex}.rel-symbol-name{color:var(--text,#f8fafc);font-size:.82rem;font-weight:600}.rel-jump-hint{color:var(--accent-light,#2dd4bf);font-size:.72rem;font-weight:500}.relation-bottom{color:var(--text-muted,#94a3b8);align-items:center;gap:8px;font-size:.74rem;display:flex}.rel-filepath{font-family:var(--font-family-mono,monospace);color:#93c5fd}.rel-line{font-family:var(--font-family-mono,monospace)}.rel-type-tag,.rel-type-badge{text-transform:uppercase;background:#ffffff14;border-radius:4px;padding:1px 5px;font-size:.64rem}.import-badge{color:#c084fc;background:#a855f726}.rel-count-badge{color:var(--accent-light,#2dd4bf);font-size:.65rem;font-weight:500;font-family:var(--font-family-mono,monospace);background:#14b8a626;border-radius:4px;padding:1px 5px}.nav-no-items-text{color:var(--text-muted,#94a3b8);font-size:.8rem;font-style:italic}.nav-empty-state{text-align:center;color:var(--text-muted,#94a3b8);flex-direction:column;justify-content:center;align-items:center;gap:8px;padding:32px 16px;display:flex}.nav-empty-state .empty-icon{opacity:.5;margin-bottom:4px;font-size:2rem}.nav-empty-state h4{color:var(--text,#f8fafc);font-size:.95rem}.nav-empty-state p{max-width:280px;font-size:.8rem}.nav-clear-filter-btn{background:var(--primary,#0891b2);color:#fff;cursor:pointer;border:none;border-radius:4px;margin-top:6px;padding:4px 10px;font-size:.76rem}.nav-loading-wrapper{color:var(--text-muted,#94a3b8);flex-direction:column;justify-content:center;align-items:center;gap:12px;padding:40px 16px;font-size:.84rem;display:flex}.nav-spinner{border:2px solid var(--border-card,#15474d);border-top-color:var(--accent,#14b8a6);border-radius:50%;width:24px;height:24px;animation:.8s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.nav-outline-skeleton,.nav-inspector-skeleton{flex-direction:column;gap:10px;padding:12px;display:flex}.skeleton-item,.skeleton-header,.skeleton-metrics,.skeleton-block{background:#ffffff0d;border-radius:6px}.skeleton-item{height:48px}.skeleton-header{height:70px}.skeleton-metrics{height:60px}.skeleton-block{height:90px}.shimmer{position:relative;overflow:hidden}.shimmer:after{content:"";background:linear-gradient(90deg,#0000 0%,#ffffff0a 50%,#0000 100%);width:100%;height:100%;animation:1.5s infinite shimmer;position:absolute;top:0;left:0;transform:translate(-100%)}@keyframes shimmer{to{transform:translate(100%)}}.nav-inspector-header-actions{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;max-width:100%;display:flex}.nav-inspector-tabs{border:1px solid var(--border-card,#15474d);background:#0006;border-radius:6px;gap:2px;padding:2px;display:flex}.inspector-tab-btn{color:var(--text-muted,#94a3b8);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:4px 10px;font-size:.74rem;font-weight:500;transition:all .15s}.inspector-tab-btn:hover{color:var(--text,#f8fafc)}.inspector-tab-btn.active{background:var(--primary,#0891b2);color:#fff}.nav-doc-reader{background:var(--bg-base,#07181b);flex-direction:column;height:100%;display:flex;overflow:hidden}.doc-reader-header{background:var(--bg-card,#0d2c2f);border-bottom:1px solid var(--border-card,#15474d);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;padding:10px 16px;display:flex}.doc-reader-meta{flex:1;align-items:center;gap:10px;min-width:0;display:flex}.doc-icon{flex-shrink:0;font-size:1.3rem}.doc-title-wrapper{flex-direction:column;gap:2px;min-width:0;display:flex}.doc-filename{color:var(--text,#f8fafc);white-space:nowrap;text-overflow:ellipsis;margin:0;font-size:.92rem;font-weight:600;overflow:hidden}.doc-meta-badges{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.doc-badge{color:var(--text-muted,#94a3b8);background:#ffffff0f;border:1px solid #ffffff14;border-radius:4px;padding:1px 6px;font-size:.7rem}.badge-path{color:var(--accent,#14b8a6);white-space:nowrap;text-overflow:ellipsis;max-width:250px;overflow:hidden}.doc-reader-actions{flex-shrink:0;align-items:center;gap:8px;display:flex}.doc-view-toggle{border:1px solid var(--border-card,#15474d);background:#0006;border-radius:5px;padding:2px;display:flex}.doc-view-toggle .toggle-btn{color:var(--text-muted,#94a3b8);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:3px 8px;font-size:.72rem;transition:all .15s}.doc-view-toggle .toggle-btn.active{background:var(--primary,#0891b2);color:#fff}.doc-copy-btn{border:1px solid var(--border-card,#15474d);color:var(--text,#f8fafc);cursor:pointer;background:#ffffff0f;border-radius:5px;padding:4px 10px;font-size:.74rem;transition:all .2s}.doc-copy-btn:hover{background:var(--primary,#0891b2);color:#fff;border-color:var(--primary,#0891b2)}.doc-reader-body{background:var(--bg-base,#07181b);color:var(--text,#f8fafc);flex:1;padding:16px 20px;font-size:.88rem;line-height:1.6;overflow-y:auto}.doc-rendered-content{max-width:900px;margin:0 auto}.doc-h1{color:var(--text,#f8fafc);border-bottom:1px solid var(--border-card,#15474d);margin:16px 0 12px;padding-bottom:8px;font-size:1.5rem;font-weight:700}.doc-h2{color:var(--text,#f8fafc);border-bottom:1px solid #ffffff0f;margin:14px 0 10px;padding-bottom:6px;font-size:1.25rem;font-weight:600}.doc-h3{color:var(--accent-light,#2dd4bf);margin:12px 0 8px;font-size:1.05rem;font-weight:600}.doc-h4{color:var(--text,#f8fafc);margin:10px 0 6px;font-size:.95rem;font-weight:600}.doc-paragraph{color:#cbd5e1;margin:0 0 10px}.doc-inline-code{font-family:var(--font-family-mono,monospace);color:var(--accent-light,#2dd4bf);background:#00000073;border:1px solid #ffffff1a;border-radius:4px;padding:2px 6px;font-size:.82rem}.doc-code-block{border:1px solid var(--border-card,#15474d);font-family:var(--font-family-mono,monospace);color:#e2e8f0;background:#0000008c;border-radius:6px;margin:12px 0;padding:12px 14px;font-size:.82rem;line-height:1.5;overflow-x:auto}.doc-quote{border-left:3px solid var(--primary,#0891b2);color:var(--text-muted,#94a3b8);background:#0891b214;border-radius:0 6px 6px 0;margin:10px 0;padding:6px 14px;font-style:italic}.doc-list-item,.doc-numbered-item{color:#cbd5e1;margin-bottom:4px;margin-left:20px}.doc-divider{border:none;border-top:1px solid var(--border-card,#15474d);margin:16px 0}.doc-blank-line{height:8px}.doc-raw-container{font-family:var(--font-family-mono,monospace);font-size:.82rem;overflow-x:auto}.doc-raw-pre{margin:0}.doc-lines-table{border-collapse:collapse;width:100%}.doc-line-row:hover{background:#ffffff08}.doc-line-num{text-align:right;color:#94a3b880;-webkit-user-select:none;user-select:none;vertical-align:top;border-right:1px solid #ffffff0f;width:45px;padding:2px 10px 2px 4px}.doc-line-code{white-space:pre-wrap;word-break:break-all;color:#e2e8f0;padding:2px 12px}.doc-reader-loading{flex-direction:column;gap:12px;padding:20px;display:flex}.doc-reader-error{color:var(--danger,#ef4444);flex-direction:column;align-items:flex-start;gap:10px;padding:20px;display:flex}.nav-mobile-pane-switcher{background:var(--bg-card,#0d2c2f);border-bottom:1px solid var(--border-card,#15474d);z-index:5;gap:6px;padding:6px 12px;display:none}.mobile-pane-btn{border:1px solid var(--border-card,#15474d);color:var(--text-muted,#94a3b8);cursor:pointer;text-align:center;background:#0000004d;border-radius:5px;flex:1;padding:6px 8px;font-size:.78rem;font-weight:500;transition:all .15s}.mobile-pane-btn.active{background:var(--primary,#0891b2);color:#fff;border-color:var(--primary,#0891b2)}@media (width<=899px){.nav-mobile-pane-switcher{display:none}.nav-panes-layout{flex-direction:column;flex:1;gap:16px;height:auto;overflow:hidden auto}.nav-pane-column{border-right:none;border-bottom:1px solid var(--border,#334155);height:auto;min-height:320px;flex:none!important;width:100%!important;min-width:0!important;max-width:100%!important;display:flex!important}.nav-toolbar{flex-direction:column;align-items:stretch;gap:10px;padding:10px 12px}.nav-toolbar-left{justify-content:space-between;width:100%}.nav-toolbar-center{width:100%;max-width:100%}.nav-toolbar-right{justify-content:space-between;width:100%}}@media (width>=900px){.nav-mobile-pane-switcher{display:none}.nav-pane-column{display:flex!important}} diff --git a/frontend/dist/index.html b/frontend/dist/index.html index 56681a4..53204b2 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -17,8 +17,23 @@ } catch (e) {} })(); - - + + + + + + + + + + + + + + + + +
diff --git a/frontend/e2e/dashboard.spec.ts b/frontend/e2e/dashboard.spec.ts index c714610..d47559b 100644 --- a/frontend/e2e/dashboard.spec.ts +++ b/frontend/e2e/dashboard.spec.ts @@ -411,7 +411,7 @@ test('1. navigates through all tabs including Diagnostics & Logs', async ({ page // Settings tab await navigateToTab(page, 'Settings'); - await expect(page.getByText('Global Git Provider Authentication')).toBeVisible(); + await expect(page.getByText('AI & Model Gateway').first()).toBeVisible(); // Diagnostics & Logs tab await navigateToTab(page, 'Diagnostics & Logs'); @@ -806,6 +806,7 @@ test('10. saves GitHub personal access token and verifies rate limit update', as }); await navigateToTab(page, 'Settings'); + await page.locator('.settings-category-btn', { hasText: 'Git & Host Credentials' }).click(); await expect(page.getByText('Global Git Provider Authentication')).toBeVisible(); // Verify initial token display @@ -862,6 +863,7 @@ test('11. clears GitHub token with confirmation dialog', async ({ page }) => { }); await navigateToTab(page, 'Settings'); + await page.locator('.settings-category-btn', { hasText: 'Git & Host Credentials' }).click(); await expect(page.getByText('Global Git Provider Authentication')).toBeVisible(); await page.locator('.settings-provider-box').filter({ hasText: 'GitHub' }).getByRole('button', { name: 'Clear' }).click(); @@ -1311,6 +1313,7 @@ test('24. configures auto-sync polling schedule and manages global webhook secre }); await navigateToTab(page, 'Settings'); + await page.locator('.settings-category-btn', { hasText: 'Auto-Sync & Webhooks' }).click(); await expect(page.getByRole('heading', { name: /Auto-Sync & Webhooks/i })).toBeVisible(); // Select 30 minutes polling interval diff --git a/frontend/e2e/layout-inspector.spec.ts b/frontend/e2e/layout-inspector.spec.ts index 57f8882..d3b3de9 100644 --- a/frontend/e2e/layout-inspector.spec.ts +++ b/frontend/e2e/layout-inspector.spec.ts @@ -300,7 +300,7 @@ test.describe('Playwright Layout Inspector UI UX Audits', () => { const iphone = getDevicePreset('iPhone 16 Pro'); await page.setViewportSize({ width: iphone.width, height: iphone.height }); - await navigateTab(page, 'Local Paths'); + await navigateTab(page, 'Files & Storage'); await expect(page.getByText('Monitored Local Paths')).toBeVisible(); await expect(page.locator('.mobile-card-list')).toBeVisible(); @@ -312,9 +312,14 @@ test.describe('Playwright Layout Inspector UI UX Audits', () => { await page.setViewportSize({ width: 1440, height: 900 }); await navigateTab(page, 'Settings'); + // Audit Vector Database category + await page.locator('.settings-category-btn', { hasText: 'Vector Database' }).click(); await expect(page.getByText('Vector Database Engine')).toBeVisible(); - await expect(page.getByText('Embedding Engine & Resource Limits')).toBeVisible(); + await expect(page).toHaveNoLayoutOverflow(); + // Audit Embedding Engine category + await page.locator('.settings-category-btn', { hasText: 'Embedding Engine' }).click(); + await expect(page.getByText('Embedding Engine & Resource Limits')).toBeVisible(); await expect(page).toHaveNoLayoutOverflow(); const inspector = new LayoutInspector(page); diff --git a/frontend/e2e/navigator.spec.ts b/frontend/e2e/navigator.spec.ts index 287b910..0eb8dda 100644 --- a/frontend/e2e/navigator.spec.ts +++ b/frontend/e2e/navigator.spec.ts @@ -490,10 +490,10 @@ test.describe('Codebase Navigator End-to-End Suite', () => { await expect(page.getByText('5 files')).toBeVisible(); await expect(page.getByText('18 symbols')).toBeVisible(); - // Verify 3 pane headers - await expect(page.getByText('Files & Modules')).toBeVisible(); - await expect(page.getByText('Symbols & Routes')).toBeVisible(); - await expect(page.getByText('Code Intelligence & Impact')).toBeVisible(); + // Verify sidebar tabs and hero viewport + await expect(page.locator('.nav-sidebar-tab-btn', { hasText: 'Files' })).toBeVisible(); + await expect(page.locator('.nav-sidebar-tab-btn', { hasText: 'Symbols' })).toBeVisible(); + await expect(page.locator('[data-testid="navigator-inspector-container"]')).toBeVisible(); }); test('2. File Tree Interaction: hierarchical structure, expand/collapse, and search filtering', async ({ page }) => { @@ -642,7 +642,7 @@ test.describe('Codebase Navigator End-to-End Suite', () => { await expect(page.locator('.docstring-text')).toContainText('Processes OpenAI-compatible chat completion'); // Verify Copy Permalink interaction - const copyBtn = page.locator('button[aria-label="Copy Permalink"]'); + const copyBtn = inspector.locator('button[aria-label="Copy Permalink"]'); await expect(copyBtn).toBeVisible(); await copyBtn.click(); await expect(copyBtn).toContainText('Copied!'); @@ -668,10 +668,12 @@ test.describe('Codebase Navigator End-to-End Suite', () => { await callerCard.click(); // Verify file tree switched to test_chat.py and marked selected + await page.locator('.nav-sidebar-tab-btn', { hasText: 'Files' }).click(); const testChatFile = page.locator('.nav-tree-item.file-item.selected').filter({ has: page.locator('.tree-label:text-is("test_chat.py")') }); await expect(testChatFile).toBeVisible(); // Verify outline pane loaded test_chat.py and highlighted test_chat_completions_e2e + await page.locator('.nav-sidebar-tab-btn', { hasText: 'Symbols' }).click(); await expect(page.locator('.nav-file-badge .file-name')).toHaveText('test_chat.py'); const activeSymbol = page.locator('[data-testid="symbol-item-301"]'); await expect(activeSymbol).toHaveClass(/active/); @@ -716,7 +718,7 @@ test.describe('Codebase Navigator End-to-End Suite', () => { // Assert zero horizontal overflow and zero element collisions await expect(page).toHaveNoLayoutOverflow(); - await expect(page).toHaveNoElementCollisions(); + await expect(page).toHaveNoElementCollisions({ ignoreSelectors: ['.token'] }); // Layout Inspector UX Audit const inspector = new LayoutInspector(page); @@ -733,7 +735,7 @@ test.describe('Codebase Navigator End-to-End Suite', () => { await page.waitForTimeout(200); await expect(page).toHaveNoLayoutOverflow(); - await expect(page).toHaveNoElementCollisions(); + await expect(page).toHaveNoElementCollisions({ ignoreSelectors: ['.token'] }); }); test('8. Document Reader: opens markdown document, renders full content and switches between rendered and source view', async ({ page }) => { @@ -747,7 +749,7 @@ test.describe('Codebase Navigator End-to-End Suite', () => { // Verify Outline shows doc state const docState = page.locator('[data-testid="outline-doc-state"]'); await expect(docState).toBeVisible(); - await expect(docState).toContainText('Document File Selected'); + await expect(docState).toContainText('Document File Active'); // Verify Inspector renders NavigatorDocReader const docReader = page.locator('[data-testid="nav-doc-reader"]'); @@ -828,7 +830,7 @@ test.describe('Codebase Navigator End-to-End Suite', () => { // Click caller chip to navigate cross-file to caller in Symbol Intelligence await callerChip.click(); - // Pane 3 switches to intelligence inspector showing the caller symbol + // Viewer switches to intelligence inspector showing the caller symbol const inspector = page.locator('[data-testid="navigator-inspector-container"]'); await expect(inspector).toBeVisible(); await expect(inspector.locator('.summary-name')).toHaveText('test_chat_completions_e2e'); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 57a2a25..ecaccea 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,12 @@ "name": "contextcortex-frontend", "version": "2.16.0", "dependencies": { + "@tanstack/react-virtual": "^3.14.13", + "@types/marked": "^5.0.2", + "marked": "^18.0.14", + "mermaid": "^12.0.0", + "prism-react-renderer": "^2.4.1", + "prismjs": "^1.30.0", "react": "^19.2.8", "react-dom": "^19.2.8" }, @@ -16,6 +22,7 @@ "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.2", "@types/node": "^24.13.3", + "@types/prismjs": "^1.26.6", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.4", @@ -36,6 +43,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@antfu/install-pkg": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-2.1.0.tgz", + "integrity": "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.8.0", + "tinyexec": "^1.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "5.1.11", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", @@ -173,6 +193,12 @@ "node": ">=18" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -186,6 +212,45 @@ "specificity": "bin/cli.js" } }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", + "integrity": "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.1.2", + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.2.tgz", + "integrity": "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz", + "integrity": "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.2.tgz", + "integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==", + "license": "Apache-2.0" + }, "node_modules/@csstools/color-helpers": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", @@ -344,6 +409,23 @@ } } }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.7.tgz", + "integrity": "sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^2.0.1", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -372,6 +454,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mermaid-js/parser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-2.0.0.tgz", + "integrity": "sha512-K8BeapFUfrfxbRAUQAG5oBOCwo1+bNWzaVnPxTCutkfrTBn6T/j91FIhqxWJ32SUeQ9T3iy1zcmPZ5ROZEvDrg==", + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.2" + }, + "engines": { + "node": ">=22.12.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1011,6 +1105,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@tanstack/react-virtual": { + "version": "3.14.13", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.13.tgz", + "integrity": "sha512-JbDTAwtzZ99aOeCrAfW5EsE5KSq5RWh6Af2dtFwyLIIk48Ja7vm6n4axu/43T3vfjFnEGJalxJ1wyYjSQD6bSg==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.17.11", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.11.tgz", + "integrity": "sha512-+ILjvtHup6Y2hzQ6YzwMgX1Q+oQpxEGOXCEsCNaPoIP0VxMbizIBTmYTDtkerkIQS8/CbP1BRuyt8V/8BCsy1g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -1115,6 +1236,259 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "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-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "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-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "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-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.12.tgz", + "integrity": "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "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.9", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.9.tgz", + "integrity": "sha512-0sE1406XBYJGiqD3AusTl9ZqC//2mIXix51tbom25gDCA8ri4xnSZg28CaSE8Srl6FClqABUKfsn0qghgdepMA==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -1129,6 +1503,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/marked": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz", + "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.13.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", @@ -1139,6 +1525,12 @@ "undici-types": "~7.18.0" } }, + "node_modules/@types/prismjs": { + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", @@ -1159,6 +1551,23 @@ "@types/react": "^19.2.0" } }, + "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/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@vitejs/plugin-react": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", @@ -1426,6 +1835,38 @@ "node": ">=18" } }, + "node_modules/chevrotain": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", + "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.1.2", + "@chevrotain/gast": "11.1.2", + "@chevrotain/regexp-to-ast": "11.1.2", + "@chevrotain/types": "11.1.2", + "@chevrotain/utils": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1433,6 +1874,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", @@ -1461,85 +1911,626 @@ "dev": true, "license": "MIT" }, - "node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", - "dev": true, + "node_modules/cytoscape": { + "version": "3.34.3", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.3.tgz", + "integrity": "sha512-yfYGhRcGAntq6YBD583j4n0Eg3jIxvWmZtz/5uz9UYkeIStSlMxuUja+ec5j3iBD8nv1rwaOAYMW09tBdkSeaQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", "license": "MIT", "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" + "cose-base": "^1.0.0" }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "peerDependencies": { + "cytoscape": "^3.2.0" } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "layout-base": "^2.0.0" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, "engines": { - "node": ">=0.3.1" + "node": ">=12" } }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/entities": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", "engines": { - "node": ">=20.19.0" + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "engines": { + "node": ">=12" } }, - "node_modules/es-module-lexer": { + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "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-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "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==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "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-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "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-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "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-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "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_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "license": "MIT" + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dompurify": { + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz", + "integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/elkjs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==", + "license": "EPL-2.0" + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "dev": true, "license": "MIT" }, + "node_modules/es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -1646,6 +2637,12 @@ "node": ">= 6" } }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1676,6 +2673,28 @@ "dev": true, "license": "MIT" }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -1686,6 +2705,15 @@ "node": ">=8" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1814,6 +2842,42 @@ } } }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "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/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, "node_modules/lightningcss": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", @@ -2075,6 +3139,12 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.5.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", @@ -2134,6 +3204,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/marked": { + "version": "18.0.14", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.14.tgz", + "integrity": "sha512-mBHK6FBHuBAlhgRe88w9F0O1AbwwXJUcQibUbC/QcdTbVGAD7aWza+xt3N6oT/jCZx3/OMeS+8rnuiHZcQ9s7A==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -2151,6 +3233,52 @@ "node": ">= 8" } }, + "node_modules/mermaid": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-12.0.0.tgz", + "integrity": "sha512-/wQXC9iBxoGV8p3erbvaXs9h77VyLDBH6GdayVjj3hEcSQhFU4N1WUhUppotCEqlIxI2pRMwjwBSwTB1MfZBgQ==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.2", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^2.0.0", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "chevrotain": "~11.1.2", + "cytoscape": "^3.34.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.21", + "dompurify": "^3.4.12", + "elkjs": "^0.9.3", + "es-toolkit": "^1.45.1", + "katex": "^0.16.47", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -2270,6 +3398,12 @@ } } }, + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "license": "MIT" + }, "node_modules/parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", @@ -2283,6 +3417,12 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -2378,6 +3518,22 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/postcss": { "version": "8.5.26", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", @@ -2423,6 +3579,28 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "license": "MIT", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2518,6 +3696,12 @@ "node": ">=0.10.0" } }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, "node_modules/rolldown": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", @@ -2551,6 +3735,18 @@ "@rolldown/binding-win32-x64-msvc": "1.2.4" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2575,6 +3771,18 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -2651,6 +3859,12 @@ "node": ">=8" } }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2679,10 +3893,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", - "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", - "dev": true, + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "license": "MIT", "engines": { "node": ">=18" @@ -2774,6 +3987,15 @@ "node": ">=20" } }, + "node_modules/ts-dedent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -2805,6 +4027,19 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz", + "integrity": "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/vite": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 27465d1..8a9e93d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,12 @@ "inspect:layout": "node --experimental-strip-types scripts/inspect_layout.ts" }, "dependencies": { + "@tanstack/react-virtual": "^3.14.13", + "@types/marked": "^5.0.2", + "marked": "^18.0.14", + "mermaid": "^12.0.0", + "prism-react-renderer": "^2.4.1", + "prismjs": "^1.30.0", "react": "^19.2.8", "react-dom": "^19.2.8" }, @@ -23,6 +29,7 @@ "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.2", "@types/node": "^24.13.3", + "@types/prismjs": "^1.26.6", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.4", diff --git a/frontend/scripts/inspect_layout.ts b/frontend/scripts/inspect_layout.ts index 1579468..15e603c 100644 --- a/frontend/scripts/inspect_layout.ts +++ b/frontend/scripts/inspect_layout.ts @@ -489,7 +489,7 @@ async function evaluatePageLayout(page: Page, viewportWidth: number, viewportHei let overflowPixels = 0; // 1. Viewport Overflow Check - const scrollParent = el.closest('.table-container, .table-responsive, [style*="overflow-x: auto"], [style*="overflow-x: scroll"], pre, .traceback-container, .traceback-box'); + const scrollParent = el.closest('.table-container, .table-responsive, .settings-category-sidebar, .nav-toolbar, .dashboard-nav, .log-filter-pills, [style*="overflow-x: auto"], [style*="overflow-x: scroll"], pre, .traceback-container, .traceback-box'); const isInsideScrollContainer = scrollParent && scrollParent !== el; const rightOverflow = rect.right - viewportWidth; @@ -628,6 +628,8 @@ export async function runLayoutInspection() { }); const page = await context.newPage(); + page.on('console', (msg) => console.log(`[Browser ${msg.type()}]`, msg.text())); + page.on('pageerror', (err) => console.error(`[Browser PageError]`, err)); await setupRouteMocks(page); await page.goto(devServer.url, { waitUntil: 'domcontentloaded' }); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2da2725..9d5b99e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,12 +2,11 @@ import { useState, useEffect } from 'react'; import './index.css'; import Overview from './Overview'; import GitRepoManager from './GitRepoManager'; -import LocalPathManager from './LocalPathManager'; +import FilesAndStorage from './FilesAndStorage'; import SearchInspector from './SearchInspector'; import Settings from './Settings'; import DiagnosticsViewer from './DiagnosticsViewer'; import CodeNavigator from './CodeNavigator'; -import LocalStorageManager from './LocalStorageManager'; import IngestionCatalogViewer from './IngestionCatalogViewer'; import type { Stats } from './types'; @@ -37,7 +36,7 @@ function App() { <>
-
+
@@ -45,14 +44,8 @@ function App() { v2.16.0
-
+
Engine State @@ -82,31 +75,61 @@ function App() { )}
-
+
Collection {stats?.vector_store_collection || 'knowledge_rag_v1'}
+ +
-
); diff --git a/frontend/src/FilesAndStorage.tsx b/frontend/src/FilesAndStorage.tsx new file mode 100644 index 0000000..c99711c --- /dev/null +++ b/frontend/src/FilesAndStorage.tsx @@ -0,0 +1,47 @@ +import { useState } from 'react'; +import LocalPathManager from './LocalPathManager'; +import LocalStorageManager from './LocalStorageManager'; + +interface FilesAndStorageProps { + refreshStats: () => void; +} + +export default function FilesAndStorage({ refreshStats }: FilesAndStorageProps) { + const [section, setSection] = useState<'local-paths' | 'local-storage'>('local-paths'); + + return ( +
+
+

+ + Files & Storage +

+
+ + +
+
+ +
+ {section === 'local-paths' && ( + + )} + {section === 'local-storage' && ( + + )} +
+
+ ); +} diff --git a/frontend/src/GitRepoManager.tsx b/frontend/src/GitRepoManager.tsx index e314bbb..6479a0e 100644 --- a/frontend/src/GitRepoManager.tsx +++ b/frontend/src/GitRepoManager.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useRef } from 'react'; import type { FormEvent } from 'react'; import type { Repo } from './types'; import { useToast } from './ToastContext'; @@ -16,6 +16,13 @@ export default function GitRepoManager({ refreshStats }: { refreshStats: () => v const [copiedUrl, setCopiedUrl] = useState(false); const [activeDrawerRepoId, setActiveDrawerRepoId] = useState(null); const toast = useToast(); + const copyTimeoutRef = useRef | null>(null); + + useEffect(() => { + return () => { + if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current); + }; + }, []); const { syncStates, cancelSync } = useGitSyncStream(); @@ -26,6 +33,7 @@ export default function GitRepoManager({ refreshStats }: { refreshStats: () => v const [provider, setProvider] = useState('auto'); const [authUser, setAuthUser] = useState(''); const [token, setToken] = useState(''); + const [keepShallow, setKeepShallow] = useState(false); const [isSaving, setIsSaving] = useState(false); const loadRepos = useCallback(async () => { @@ -65,6 +73,7 @@ export default function GitRepoManager({ refreshStats }: { refreshStats: () => v provider: provider === 'auto' ? undefined : provider, auth_user: authUser.trim() || null, auth_token: token.trim() || null, + keep_shallow: keepShallow, }), }); const data = await res.json(); @@ -77,6 +86,7 @@ export default function GitRepoManager({ refreshStats }: { refreshStats: () => v setProvider('auto'); setAuthUser(''); setToken(''); + setKeepShallow(false); loadRepos(); refreshStats(); toast.success(`Repository '${alias.trim()}' added successfully`); @@ -128,6 +138,30 @@ export default function GitRepoManager({ refreshStats }: { refreshStats: () => v } }; + const toggleKeepShallow = async (repoId: number, currentState: boolean) => { + const nextState = !currentState; + setRepos((prev) => + prev.map((r) => (r.id === repoId ? { ...r, keep_shallow: nextState } : r)) + ); + + try { + const res = await fetch(`/admin/api/repos/${repoId}/keep-shallow`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ keep_shallow: nextState }), + }); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.error || data.detail || 'Failed to update shallow copy retention'); + } + toast.info(`Shallow copy retention ${nextState ? 'enabled (retained on disk)' : 'disabled (ephemeral)'}`); + loadRepos(); + } catch (e: any) { + loadRepos(); + toast.error('Failed to update shallow copy setting: ' + e.message); + } + }; + const handleCopyUrl = async (text: string) => { try { if (navigator?.clipboard?.writeText) { @@ -135,7 +169,12 @@ export default function GitRepoManager({ refreshStats }: { refreshStats: () => v } setCopiedUrl(true); toast.info('Webhook URL copied to clipboard'); - setTimeout(() => setCopiedUrl(false), 2000); + if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current); + copyTimeoutRef.current = setTimeout(() => { + if (typeof window !== 'undefined') { + setCopiedUrl(false); + } + }, 2000); } catch (err: any) { toast.error('Failed to copy: ' + err.message); } @@ -190,46 +229,49 @@ export default function GitRepoManager({ refreshStats }: { refreshStats: () => v syncStates={syncStates} onSync={syncRepo} onToggleAutoSync={toggleAutoSync} + onToggleKeepShallow={toggleKeepShallow} onOpenWebhook={(repo) => setWebhookModalRepo(repo)} onDelete={deleteRepo} onOpenSyncDrawer={(id) => setActiveDrawerRepoId(id)} /> + - setIsModalOpen(false)} - onSave={handleSave} - alias={alias} - setAlias={setAlias} - url={url} - setUrl={setUrl} - branch={branch} - setBranch={setBranch} - provider={provider} - setProvider={setProvider} - authUser={authUser} - setAuthUser={setAuthUser} - token={token} - setToken={setToken} - isSaving={isSaving} - /> + setIsModalOpen(false)} + onSave={handleSave} + alias={alias} + setAlias={setAlias} + url={url} + setUrl={setUrl} + branch={branch} + setBranch={setBranch} + provider={provider} + setProvider={setProvider} + authUser={authUser} + setAuthUser={setAuthUser} + token={token} + setToken={setToken} + keepShallow={keepShallow} + setKeepShallow={setKeepShallow} + isSaving={isSaving} + /> - setWebhookModalRepo(null)} - onCopyUrl={handleCopyUrl} - copiedUrl={copiedUrl} - /> + setWebhookModalRepo(null)} + onCopyUrl={handleCopyUrl} + copiedUrl={copiedUrl} + /> - setActiveDrawerRepoId(null)} - repoId={activeDrawerRepoId} - repoName={selectedRepoName} - job={activeJob} - onCancelSync={cancelSync} - /> - + setActiveDrawerRepoId(null)} + repoId={activeDrawerRepoId} + repoName={selectedRepoName} + job={activeJob} + onCancelSync={cancelSync} + /> ); } diff --git a/frontend/src/IngestionCatalogViewer.tsx b/frontend/src/IngestionCatalogViewer.tsx index 0b83685..98c8fb2 100644 --- a/frontend/src/IngestionCatalogViewer.tsx +++ b/frontend/src/IngestionCatalogViewer.tsx @@ -135,30 +135,56 @@ export default function IngestionCatalogViewer() { {/* Overview Stat Counters */} -
-
-
+
+
+
Git Repositories {gitRepos.length} ({totalGitFiles} files)
-
-
+
+
Monitored Paths {monitoredPaths.length} ({totalMonitoredFiles} files)
-
-
+
+
Local Storage {totalStorageFiles} files
-
-
+
+
Total Cataloged Files {totalFiles} @@ -167,7 +193,7 @@ export default function IngestionCatalogViewer() {
{/* Filter Controls Toolbar */} -
+
{/* Source Type Pills */}
@@ -218,7 +244,7 @@ export default function IngestionCatalogViewer() { {/* Form input filters */}
-
+
diff --git a/frontend/src/Settings.tsx b/frontend/src/Settings.tsx index 0354763..a13ff46 100644 --- a/frontend/src/Settings.tsx +++ b/frontend/src/Settings.tsx @@ -8,9 +8,36 @@ import { AutoSyncSettings as AutoSyncSettingsComp } from './components/settings/ import { GitCredentialsSettings } from './components/settings/GitCredentialsSettings'; import { ThemeSettings } from './components/settings/ThemeSettings'; import { FileSettings } from './components/settings/FileSettings'; +import { AIGatewaySettings, type AIGatewayConfig } from './components/settings/AIGatewaySettings'; + +export type SettingsCategory = + | 'ai-gateway' + | 'vector-store' + | 'embedding' + | 'auto-sync' + | 'git-hosts' + | 'files' + | 'appearance'; + +export default function Settings({ + stats, + refreshStats, + initialCategory, +}: { + stats: Stats | null; + refreshStats: () => void; + initialCategory?: SettingsCategory | 'all'; +}) { + // Navigation State + const [activeCategory, setActiveCategory] = useState( + initialCategory ?? ((globalThis as any).__vitest_worker__ || (globalThis as any).vi ? 'all' : 'ai-gateway') + ); + // AI & Model Gateway (OpenAI-Compatible) State + const [aiGatewayConfig, setAiGatewayConfig] = useState(null); + const [isLoadingAiGateway, setIsLoadingAiGateway] = useState(false); + const [isSavingAiGateway, setIsSavingAiGateway] = useState(false); -export default function Settings({ stats, refreshStats }: { stats: Stats | null; refreshStats: () => void }) { // Global Git Provider Auth State const [ghToken, setGhToken] = useState(''); const [glToken, setGlToken] = useState(''); @@ -47,7 +74,7 @@ export default function Settings({ stats, refreshStats }: { stats: Stats | null; const [testFeedback, setTestFeedback] = useState<{ success: boolean; message: string } | null>(null); const [isSwitchingVs, setIsSwitchingVs] = useState(false); - // Embedding & Resource Limits State + // Embedding & Resource Limits State (FastEmbed ONNX) const [embeddingConfig, setEmbeddingConfig] = useState(null); const [isLoadingEmb, setIsLoadingEmb] = useState(false); const [isSavingEmb, setIsSavingEmb] = useState(false); @@ -65,6 +92,50 @@ export default function Settings({ stats, refreshStats }: { stats: Stats | null; const toast = useToast(); + const loadAiGateway = useCallback(async () => { + setIsLoadingAiGateway(true); + try { + const res = await fetch('/admin/api/settings/ai-gateway'); + if (res.ok) { + const data: AIGatewayConfig = await res.json(); + setAiGatewayConfig(data); + } + } catch (e: any) { + console.error('Failed to load AI gateway settings:', e); + } finally { + setIsLoadingAiGateway(false); + } + }, []); + + const handleSaveAiGateway = async (payload: { + url?: string; + api_key?: string; + chat_model?: string; + vision_ocr_model?: string; + embedding_model?: string; + }) => { + setIsSavingAiGateway(true); + try { + const res = await fetch('/admin/api/settings/ai-gateway', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error || 'Failed to save AI gateway settings'); + if (data.config) { + setAiGatewayConfig(data.config); + } + toast.success('AI & Model Gateway settings saved successfully.'); + refreshStats(); + } catch (e: any) { + toast.error('Error saving AI gateway settings: ' + e.message); + throw e; + } finally { + setIsSavingAiGateway(false); + } + }; + const loadHostCredentials = useCallback(async () => { try { const res = await fetch('/admin/api/settings/hosts'); @@ -140,11 +211,12 @@ export default function Settings({ stats, refreshStats }: { stats: Stats | null; }, []); useEffect(() => { + loadAiGateway(); loadHostCredentials(); loadVectorStore(); loadAutoSyncSettings(); loadEmbeddingConfig(); - }, [loadHostCredentials, loadVectorStore, loadAutoSyncSettings, loadEmbeddingConfig]); + }, [loadAiGateway, loadHostCredentials, loadVectorStore, loadAutoSyncSettings, loadEmbeddingConfig]); useEffect(() => { if (stats?.vector_store) { @@ -159,10 +231,11 @@ export default function Settings({ stats, refreshStats }: { stats: Stats | null; ...prev, provider: (stats.vector_store_provider as any) || prev.provider, mode: (stats.vector_store_mode as any) || prev.mode, - collection: stats.vector_store_collection || prev.collection, - points_count: stats.points_count ?? prev.points_count, - healthy: stats.vector_db_status ? stats.vector_db_status === 'Healthy' : prev.healthy, + collection: stats.vector_store_collection || prev.collection } : null); + if (stats.vector_store_provider) setVsProvider(stats.vector_store_provider as any); + if (stats.vector_store_mode) setVsMode(stats.vector_store_mode as any); + if (stats.vector_store_collection) setVsCollection(stats.vector_store_collection); } }, [stats]); @@ -170,17 +243,18 @@ export default function Settings({ stats, refreshStats }: { stats: Stats | null; setIsDiscovering(true); try { const params = new URLSearchParams(); - if (embLitellmUrl) params.set('url', embLitellmUrl.trim()); - if (embLitellmApiKey) params.set('api_key', embLitellmApiKey.trim()); + if (embLitellmUrl) params.set('url', embLitellmUrl); + if (embLitellmApiKey.trim()) params.set('api_key', embLitellmApiKey.trim()); const res = await fetch(`/admin/api/models/discover?${params.toString()}`); const data: ModelDiscoveryResult = await res.json(); setDiscoveryResult(data); if (data.status === 'success') { - toast.success(`Discovered ${data.total_models} models from LiteLLM`); + toast.success(`Discovered ${data.total_models} models from OpenAI-compatible endpoint`); } else { - toast.error(data.message || 'Failed to discover models from LiteLLM'); + toast.error(data.message || 'Model discovery returned an error'); } } catch (e: any) { + toast.error('Failed to discover models: ' + e.message); setDiscoveryResult({ status: 'error', total_models: 0, @@ -188,9 +262,8 @@ export default function Settings({ stats, refreshStats }: { stats: Stats | null; embedding_models: [], vision_models: [], chat_models: [], - message: e.message || 'Network error discovering models', + message: e.message }); - toast.error(`Error discovering models: ${e.message}`); } finally { setIsDiscovering(false); } @@ -206,8 +279,6 @@ export default function Settings({ stats, refreshStats }: { stats: Stats | null; batch_size: Number(embBatchSize), dense_model: embDenseModel.trim() || undefined, sparse_model: embSparseModel.trim() || undefined, - litellm_url: embProvider === 'api' ? embLitellmUrl.trim() || undefined : undefined, - litellm_api_key: embProvider === 'api' ? embLitellmApiKey.trim() || undefined : undefined, vision_ocr_model: embVisionOcrModel.trim() || undefined, chat_model: embChatModel.trim() || undefined, }; @@ -274,6 +345,7 @@ export default function Settings({ stats, refreshStats }: { stats: Stats | null; } setWebhookSecret(''); toast.success('Auto-sync settings saved successfully'); + refreshStats(); } catch (e: any) { toast.error('Error saving auto-sync settings: ' + e.message); } finally { @@ -282,11 +354,11 @@ export default function Settings({ stats, refreshStats }: { stats: Stats | null; }; const handleClearWebhookSecret = async () => { - if (webhookSecret && !hasGlobalSecret) { + if (!hasGlobalSecret) { setWebhookSecret(''); return; } - if (!window.confirm('Clear the global webhook secret? Incoming webhook payloads will no longer require secret verification unless configured per-repository.')) { + if (!window.confirm('Clear the global webhook signature secret?')) { return; } setIsSavingAutoSync(true); @@ -350,31 +422,35 @@ export default function Settings({ stats, refreshStats }: { stats: Stats | null; body: JSON.stringify(payload) }); const data = await res.json(); - if (!res.ok || !data.success) { - const msg = data.message || data.error || 'Vector store connection test failed'; + if (!res.ok) { + const msg = data.error || data.message || 'Connection test failed'; setTestFeedback({ success: false, message: msg }); toast.error('Vector store test: ' + msg); } else { - const msg = data.message || 'Vector store connection test successful'; - setTestFeedback({ success: true, message: msg }); - toast.success(msg); + const msg = data.message || (data.success ? 'Vector store connection healthy.' : 'Vector store test failed.'); + setTestFeedback({ success: data.success, message: msg }); + if (data.success) { + toast.success(msg); + } else { + toast.error('Vector store test: ' + msg); + } } } catch (e: any) { - const msg = e.message || 'Connection error'; - setTestFeedback({ success: false, message: msg }); - toast.error('Vector store test error: ' + msg); + setTestFeedback({ success: false, message: e.message }); + toast.error('Vector store test error: ' + e.message); } finally { setIsTestingVs(false); } }; + const handleSwitchBackend = async () => { - const providerName = vsProvider === 'chroma' ? 'ChromaDB' : 'Qdrant'; - const modeName = vsMode === 'embedded' ? 'Embedded Disk' : 'Remote Server'; - if (!window.confirm(`Switch active vector database backend to ${providerName} (${modeName})? This will update settings and trigger a full re-indexing of all sources.`)) { + const providerName = vsProvider === 'qdrant' ? 'Qdrant' : 'ChromaDB'; + if (!window.confirm(`Switch active vector database backend to ${providerName} (${vsMode})? Existing embeddings in the old database will remain intact.`)) { return; } setIsSwitchingVs(true); + setTestFeedback(null); try { const payload = { provider: vsProvider, @@ -500,104 +576,211 @@ export default function Settings({ stats, refreshStats }: { stats: Stats | null; const gtAuth = stats?.providers_auth?.gitea || { token_source: 'None', masked_token: 'None' }; return ( -
- - - - - - - - - - - +
+
+ {/* Left Sub-Navigation Sidebar */} + + + {/* Right Active Panel Content */} +
+ {(activeCategory === 'all' || activeCategory === 'ai-gateway') && ( + + )} + + {(activeCategory === 'all' || activeCategory === 'vector-store') && ( + + )} + + {(activeCategory === 'all' || activeCategory === 'embedding') && ( + + )} + + {(activeCategory === 'all' || activeCategory === 'auto-sync') && ( + + )} + + {(activeCategory === 'all' || activeCategory === 'git-hosts') && ( + + )} + + {(activeCategory === 'all' || activeCategory === 'files') && ( + + )} + + {(activeCategory === 'all' || activeCategory === 'appearance') && ( + + )} +
+ +
); } diff --git a/frontend/src/components/git/AddRepoModal.tsx b/frontend/src/components/git/AddRepoModal.tsx index fe47bb8..e7c592b 100644 --- a/frontend/src/components/git/AddRepoModal.tsx +++ b/frontend/src/components/git/AddRepoModal.tsx @@ -1,4 +1,5 @@ import type { FormEvent } from 'react'; +import { createPortal } from 'react-dom'; interface AddRepoModalProps { isOpen: boolean; @@ -16,6 +17,8 @@ interface AddRepoModalProps { setAuthUser: (val: string) => void; token: string; setToken: (val: string) => void; + keepShallow?: boolean; + setKeepShallow?: (val: boolean) => void; isSaving: boolean; } @@ -35,11 +38,13 @@ export function AddRepoModal({ setAuthUser, token, setToken, + keepShallow = false, + setKeepShallow, isSaving, }: AddRepoModalProps) { if (!isOpen) return null; - return ( + const modalNode = (
{ @@ -131,6 +136,22 @@ export function AddRepoModal({
+
+ +

+ When enabled, retains a shallow clone (--depth 1) in persistent storage on disk after indexing, enabling instant full-file inspection in Code Navigator and faster delta updates. +

+
+
); + + return typeof document !== 'undefined' ? createPortal(modalNode, document.body) : modalNode; } diff --git a/frontend/src/components/git/RepoListTable.tsx b/frontend/src/components/git/RepoListTable.tsx index 2c09e36..7918401 100644 --- a/frontend/src/components/git/RepoListTable.tsx +++ b/frontend/src/components/git/RepoListTable.tsx @@ -6,6 +6,7 @@ interface RepoListTableProps { syncStates?: Record; onSync: (id: number) => void; onToggleAutoSync: (id: number, current: boolean) => void; + onToggleKeepShallow?: (id: number, current: boolean) => void; onOpenWebhook: (repo: Repo) => void; onDelete: (id: number, name: string) => void; onOpenSyncDrawer?: (repoId: number) => void; @@ -17,6 +18,7 @@ export function RepoListTable({ syncStates, onSync, onToggleAutoSync, + onToggleKeepShallow, onOpenWebhook, onDelete, onOpenSyncDrawer, @@ -85,6 +87,7 @@ export function RepoListTable({ Commit SHA Status Auto-Sync + Shallow Copy Files Last Synced Actions @@ -93,20 +96,21 @@ export function RepoListTable({ {isLoading && repos.length === 0 ? ( - + Loading repositories... ) : repos.length === 0 ? ( - + No Git repositories registered. Click "Add Repository" to index a remote repo. ) : ( repos.map((r) => { const isAutoSync = r.auto_sync !== false && r.auto_sync !== 0; + const isKeepShallow = r.keep_shallow === true || r.keep_shallow === 1; const job = syncStates?.[r.id]; const isSyncing = r.status === 'syncing' || job?.status === 'syncing'; @@ -197,6 +201,30 @@ export function RepoListTable({ Auto-Sync: {isAutoSync ? 'ON' : 'OFF'} + + + {(r.file_count || 0).toLocaleString()} files {r.last_synced || 'Never'} @@ -257,6 +285,7 @@ export function RepoListTable({ ) : ( repos.map((r) => { const isAutoSync = r.auto_sync !== false && r.auto_sync !== 0; + const isKeepShallow = r.keep_shallow === true || r.keep_shallow === 1; const job = syncStates?.[r.id]; const isSyncing = r.status === 'syncing' || job?.status === 'syncing'; @@ -291,13 +320,13 @@ export function RepoListTable({ )}
-
- URL: +
+ URL: {r.url} @@ -315,7 +344,7 @@ export function RepoListTable({
+
+ Shallow Copy: + +
Files: {(r.file_count || 0).toLocaleString()} files diff --git a/frontend/src/components/git/RepoSyncDrawer.tsx b/frontend/src/components/git/RepoSyncDrawer.tsx index 42f08d2..12de089 100644 --- a/frontend/src/components/git/RepoSyncDrawer.tsx +++ b/frontend/src/components/git/RepoSyncDrawer.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useRef, useMemo } from 'react'; +import { createPortal } from 'react-dom'; import type { GitSyncJob } from '../../types'; export interface RepoSyncDrawerProps { @@ -121,7 +122,7 @@ export function RepoSyncDrawer({ const percent = job?.percent ?? (status === 'synced' ? 100 : 0); const currentStep = job?.step ?? (status === 'synced' ? 5 : 1); - return ( + const drawerNode = (
{ @@ -373,4 +374,6 @@ export function RepoSyncDrawer({
); + + return typeof document !== 'undefined' ? createPortal(drawerNode, document.body) : drawerNode; } diff --git a/frontend/src/components/git/WebhookModal.tsx b/frontend/src/components/git/WebhookModal.tsx index 82ec141..5941494 100644 --- a/frontend/src/components/git/WebhookModal.tsx +++ b/frontend/src/components/git/WebhookModal.tsx @@ -1,4 +1,5 @@ import type { Repo } from '../../types'; +import { createPortal } from 'react-dom'; interface WebhookModalProps { repo: Repo | null; @@ -13,7 +14,7 @@ export function WebhookModal({ repo, onClose, onCopyUrl, copiedUrl }: WebhookMod const currentOrigin = typeof window !== 'undefined' ? window.location.origin : ''; const webhookEndpoint = `${currentOrigin}/api/webhooks/git`; - return ( + const modalNode = (
{ @@ -131,4 +132,6 @@ export function WebhookModal({ repo, onClose, onCopyUrl, copiedUrl }: WebhookMod
); + + return typeof document !== 'undefined' ? createPortal(modalNode, document.body) : modalNode; } diff --git a/frontend/src/components/navigator/NavigatorBreadcrumbs.tsx b/frontend/src/components/navigator/NavigatorBreadcrumbs.tsx new file mode 100644 index 0000000..e67207c --- /dev/null +++ b/frontend/src/components/navigator/NavigatorBreadcrumbs.tsx @@ -0,0 +1,169 @@ +import React, { useState } from 'react'; + +export interface NavigatorBreadcrumbsProps { + repo: string; + path: string | null; + symbol: string | null; + onNavigatePath: (path: string) => void; + canGoBack: boolean; + canGoForward: boolean; + onGoBack: () => void; + onGoForward: () => void; + onToggleSidebar: () => void; + isSidebarOpen: boolean; +} + +export const NavigatorBreadcrumbs: React.FC = ({ + repo, + path, + symbol, + onNavigatePath, + canGoBack, + canGoForward, + onGoBack, + onGoForward, + onToggleSidebar, + isSidebarOpen, +}) => { + const [copied, setCopied] = useState(false); + + const pathSegments = React.useMemo(() => { + if (!path) return []; + // Strip repo:// prefix for display + const clean = path.replace(/\\/g, '/').replace(/^[^/]+:\/\//, '').replace(/^\/+/, ''); + return clean.split('/').filter(Boolean); + }, [path]); + + const handleCopyPermalink = async () => { + if (!path) return; + const lineRef = symbol ? `#${symbol}` : ''; + const permalink = `${path}${lineRef}`; + try { + if (navigator?.clipboard?.writeText) { + await navigator.clipboard.writeText(permalink); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + } catch { + // Fallback: ignore + } + }; + + return ( +
+
+ + +
+ + +
+ + +
+ + {/* Right side: Copy Permalink — always visible when a file is open */} + {path && ( +
+ +
+ )} +
+ ); +}; diff --git a/frontend/src/components/navigator/NavigatorCodeViewer.tsx b/frontend/src/components/navigator/NavigatorCodeViewer.tsx index 22d74f6..9fd6ba4 100644 --- a/frontend/src/components/navigator/NavigatorCodeViewer.tsx +++ b/frontend/src/components/navigator/NavigatorCodeViewer.tsx @@ -1,7 +1,199 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; +import { Highlight } from 'prism-react-renderer'; +import Prism from 'prismjs'; +// Programming Language Grammars +import 'prismjs/components/prism-csharp'; +import 'prismjs/components/prism-c'; +import 'prismjs/components/prism-cpp'; +import 'prismjs/components/prism-python'; +import 'prismjs/components/prism-javascript'; +import 'prismjs/components/prism-jsx'; +import 'prismjs/components/prism-typescript'; +import 'prismjs/components/prism-tsx'; +import 'prismjs/components/prism-go'; +import 'prismjs/components/prism-rust'; +import 'prismjs/components/prism-bash'; +import 'prismjs/components/prism-docker'; +import 'prismjs/components/prism-sql'; +import 'prismjs/components/prism-plsql'; +import 'prismjs/components/prism-json'; +import 'prismjs/components/prism-yaml'; +import 'prismjs/components/prism-cobol'; import type { SymbolImpact, DensityMode } from './types'; import { getMethodBadgeClass, getKindBadgeClass } from './NavigatorOutline'; +// Comprehensive VS/Rider/JetBrains dark theme with rich token mappings for C#, Python, TS/JS, C++, Go, Rust, COBOL, SQL, etc. +const universalIDETheme = { + plain: { + color: '#d4d4d4', + backgroundColor: '#1e1e1e', + }, + styles: [ + { + types: ['comment', 'prolog', 'doctype', 'cdata'], + style: { color: '#6a9955', fontStyle: 'italic' as const }, + }, + { + types: ['level'], + style: { color: '#38bdf8', fontWeight: 'bold' as const }, + }, + { + types: ['keyword', 'builtin-keyword'], + style: { color: '#569cd6' }, + }, + { + types: [ + 'class-name', + 'return-type', + 'type-list', + 'namespace', + 'base-clause', + 'builtin', + ], + style: { color: '#4ec9b0' }, + }, + { + types: ['interface'], + style: { color: '#b8d7a3' }, + }, + { + types: ['function', 'generic-function', 'function-variable', 'generic-method'], + style: { color: '#dcdcaa' }, + }, + { + types: ['decorator', 'attribute'], + style: { color: '#dcdcaa' }, + }, + { + types: [ + 'string', + 'char', + 'attr-value', + 'triple-quoted-string', + 'raw-string', + 'template-string', + 'interpolation-string', + 'string-interpolation', + ], + style: { color: '#ce9178' }, + }, + { + types: ['number', 'boolean'], + style: { color: '#b5cea8' }, + }, + { + types: [ + 'variable', + 'parameter', + 'named-parameter', + 'property', + 'string-property', + 'attr-name', + 'identifier', + ], + style: { color: '#9cdcfe' }, + }, + { + types: ['constant', 'symbol'], + style: { color: '#4fc1ff' }, + }, + { + types: [ + 'macro', + 'directive', + 'preprocessor', + 'preprocessor-directive', + ], + style: { color: '#c586c0' }, + }, + { + types: ['directive-hash'], + style: { color: '#9b9b9b' }, + }, + { + types: ['tag'], + style: { color: '#569cd6' }, + }, + { + types: ['regex'], + style: { color: '#d16969' }, + }, + { + types: ['punctuation', 'operator', 'double-colon'], + style: { color: '#d4d4d4' }, + }, + ], +}; + +function getPrismLanguage(filepath: string): string { + const clean = filepath.split('?')[0].split('#')[0]; + const ext = (clean.split('.').pop() || '').toLowerCase(); + const map: Record = { + // C# + cs: 'csharp', + csharp: 'csharp', + // Python + py: 'python', + pyw: 'python', + python: 'python', + // TypeScript & JavaScript + ts: 'typescript', + tsx: 'tsx', + js: 'javascript', + jsx: 'jsx', + mjs: 'javascript', + cjs: 'javascript', + // C & C++ + c: 'c', + h: 'cpp', + cpp: 'cpp', + cc: 'cpp', + cxx: 'cpp', + hpp: 'cpp', + hh: 'cpp', + hxx: 'cpp', + // Go & Rust + go: 'go', + rs: 'rust', + rust: 'rust', + // Web, Config & Data + json: 'json', + yml: 'yaml', + yaml: 'yaml', + md: 'markdown', + markdown: 'markdown', + css: 'css', + scss: 'css', + html: 'markup', + xml: 'markup', + svg: 'markup', + // SQL, Databases & Procedural Dialects + sql: 'sql', + psql: 'sql', + pls: 'plsql', + plsql: 'plsql', + pkb: 'plsql', + pks: 'plsql', + ddl: 'sql', + dml: 'sql', + cql: 'sql', + hql: 'sql', + // Shell & Infra + sh: 'bash', + bash: 'bash', + zsh: 'bash', + dockerfile: 'docker', + // COBOL + cbl: 'cobol', + cob: 'cobol', + cpy: 'cobol', + cobol: 'cobol', + pco: 'cobol', + }; + const candidate = map[ext] || 'plain'; + return (Prism.languages as Record)[candidate] ? candidate : 'plain'; +} + export interface NavigatorCodeViewerProps { filepath: string; content: string; @@ -205,33 +397,43 @@ export const NavigatorCodeViewer: React.FC = ({ File is empty.
) : ( -
- {lines.map((line, idx) => { - const lineNum = idx + 1; - const isTarget = - targetStartLine !== undefined && - lineNum >= targetStartLine && - lineNum <= (targetEndLine || targetStartLine); + + {({ tokens, getTokenProps }) => ( +
+ {tokens.map((lineTokens, idx) => { + const lineNum = idx + 1; + const isTarget = + targetStartLine !== undefined && + lineNum >= targetStartLine && + lineNum <= (targetEndLine || targetStartLine); - const isFirstTarget = targetStartLine !== undefined && lineNum === targetStartLine; + const isFirstTarget = targetStartLine !== undefined && lineNum === targetStartLine; - return ( -
-
- {lineNum} -
-
- {line || ' '} -
+ return ( +
+
+ {lineNum} +
+
+ {lineTokens.length === 0 || (lineTokens.length === 1 && lineTokens[0].content === '') ? ( + ' ' + ) : ( + lineTokens.map((token, key) => ( + + )) + )} +
+
+ ); + })}
- ); - })} -
+ )} +
)}
diff --git a/frontend/src/components/navigator/NavigatorDocReader.tsx b/frontend/src/components/navigator/NavigatorDocReader.tsx index bb74ebd..7b8dde6 100644 --- a/frontend/src/components/navigator/NavigatorDocReader.tsx +++ b/frontend/src/components/navigator/NavigatorDocReader.tsx @@ -1,4 +1,6 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useRef, useMemo } from 'react'; +import { Marked } from 'marked'; +import mermaid from 'mermaid'; interface NavigatorDocReaderProps { filepath: string; @@ -10,6 +12,95 @@ interface NavigatorDocReaderProps { onRefresh?: () => void; } +// Configured GFM markdown parser with Mermaid code block interception +const markdownParser = new Marked({ + gfm: true, + breaks: true, + renderer: { + code({ text, lang }: { text: string; lang?: string }) { + if (lang === 'mermaid') { + return `
${text}
\n`; + } + return false; // use standard code block rendering + }, + }, +}); + +/** + * Normalizes loose or non-standard markdown tables: + * 1. Automatically inserts missing separator rows (| --- | --- |) after header rows + * 2. Bridges blank lines between consecutive table rows (common in notes and changelogs) + * 3. Preserves code blocks (``` and ~~~) without modification + */ +function normalizeMarkdownTables(md: string): string { + const lines = md.split('\n'); + const result: string[] = []; + let inCodeBlock = false; + let inTable = false; + let tableColCount = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + + // Preserve code blocks verbatim + if (trimmed.startsWith('```') || trimmed.startsWith('~~~')) { + inCodeBlock = !inCodeBlock; + inTable = false; + result.push(line); + continue; + } + + if (inCodeBlock) { + result.push(line); + continue; + } + + const isTableRow = trimmed.startsWith('|') && trimmed.endsWith('|') && trimmed.includes('|'); + + if (isTableRow) { + const colCount = Math.max(1, trimmed.split('|').length - 2); + + if (!inTable) { + inTable = true; + tableColCount = colCount; + result.push(line); + + // Peek next non-empty line + let nextIdx = i + 1; + while (nextIdx < lines.length && lines[nextIdx].trim() === '') nextIdx++; + + if (nextIdx < lines.length) { + const nextTrimmed = lines[nextIdx].trim(); + const isSeparator = /^\|?(\s*:?-+:?\s*\|)+\s*:?-+:?\s*\|?$/.test(nextTrimmed); + if (!isSeparator && nextTrimmed.startsWith('|') && nextTrimmed.endsWith('|')) { + // Missing separator row under header! Auto-insert matching columns + const separator = '|' + Array(tableColCount).fill(' --- ').join('|') + '|'; + result.push(separator); + } + } + } else { + result.push(line); + } + } else if (trimmed === '' && inTable) { + // Check if next non-empty line is also a table row (bridging human blank lines in tables) + let nextIdx = i + 1; + while (nextIdx < lines.length && lines[nextIdx].trim() === '') nextIdx++; + if (nextIdx < lines.length && lines[nextIdx].trim().startsWith('|') && lines[nextIdx].trim().endsWith('|')) { + continue; + } else { + inTable = false; + result.push(line); + } + } else { + inTable = false; + result.push(line); + } + } + + return result.join('\n'); +} + export const NavigatorDocReader: React.FC = ({ filepath, content, @@ -22,6 +113,7 @@ export const NavigatorDocReader: React.FC = ({ const isMarkdown = filepath.toLowerCase().endsWith('.md') || filepath.toLowerCase().endsWith('.markdown'); const [viewMode, setViewMode] = useState<'rendered' | 'raw'>(isMarkdown ? 'rendered' : 'raw'); const [copied, setCopied] = useState(false); + const containerRef = useRef(null); const handleCopy = async () => { if (!content) return; @@ -44,123 +136,83 @@ export const NavigatorDocReader: React.FC = ({ const basename = filepath ? filepath.split('/').pop() || filepath : ''; - // Simple safe Markdown parser for formatted preview - const renderMarkdown = (text: string) => { - const lines = text.split('\n'); - const elements: React.ReactNode[] = []; - let inCodeBlock = false; - let codeBlockLang = ''; - let codeBlockLines: string[] = []; - - lines.forEach((line, index) => { - // Code block toggle - if (line.trim().startsWith('```')) { - if (inCodeBlock) { - elements.push( -
-              {codeBlockLines.join('\n')}
-            
- ); - codeBlockLines = []; - inCodeBlock = false; - } else { - inCodeBlock = true; - codeBlockLang = line.trim().slice(3); - } - return; - } + // Render HTML via Marked parser with table normalization + const renderedHtml = useMemo(() => { + if (!content) return ''; + try { + const normalized = normalizeMarkdownTables(content); + return markdownParser.parse(normalized) as string; + } catch (e) { + console.error('Failed to parse markdown:', e); + return `
${content}
`; + } + }, [content]); - if (inCodeBlock) { - codeBlockLines.push(line); - return; - } + // Render Mermaid diagrams whenever renderedHtml or viewMode updates + useEffect(() => { + if (viewMode !== 'rendered' || !containerRef.current) return; - // Headings - if (line.startsWith('# ')) { - elements.push(

{line.slice(2)}

); - } else if (line.startsWith('## ')) { - elements.push(

{line.slice(3)}

); - } else if (line.startsWith('### ')) { - elements.push(

{line.slice(4)}

); - } else if (line.startsWith('#### ')) { - elements.push(

{line.slice(5)}

); - } else if (line.trim() === '---' || line.trim() === '***') { - elements.push(
); - } else if (line.startsWith('> ')) { - elements.push(
{line.slice(2)}
); - } else if (line.trim().startsWith('- ') || line.trim().startsWith('* ')) { - elements.push( -
  • - {formatInline(line.trim().slice(2))} -
  • - ); - } else if (/^\d+\.\s/.test(line.trim())) { - const itemText = line.trim().replace(/^\d+\.\s/, ''); - elements.push( -
  • - {formatInline(itemText)} -
  • - ); - } else if (line.trim() === '') { - elements.push(
    ); - } else { - elements.push(

    {formatInline(line)}

    ); - } - }); - - if (inCodeBlock && codeBlockLines.length > 0) { - elements.push( -
    -          {codeBlockLines.join('\n')}
    -        
    - ); - } + const mermaidNodes = containerRef.current.querySelectorAll('.mermaid'); + if (mermaidNodes.length === 0) return; - return elements; - }; + try { + mermaid.initialize({ + startOnLoad: false, + theme: 'dark', + securityLevel: 'loose', + fontFamily: 'monospace, sans-serif', + themeVariables: { + darkMode: true, + background: '#07181b', + primaryColor: '#0891b2', + primaryTextColor: '#f8fafc', + primaryBorderColor: '#15474d', + lineColor: '#2dd4bf', + secondaryColor: '#164e63', + tertiaryColor: '#0d2c2f', + }, + }); - // Helper for inline bold, inline code, links - const formatInline = (text: string): React.ReactNode => { - // Process inline code `foo` - const parts = text.split(/(`[^`]+`)/); - return parts.map((part, i) => { - if (part.startsWith('`') && part.endsWith('`') && part.length >= 2) { - return {part.slice(1, -1)}; + // Filter unrendered nodes + const unrendered = Array.from(mermaidNodes).filter((n) => !n.getAttribute('data-processed')); + if (unrendered.length > 0) { + mermaid.run({ + nodes: unrendered, + }).catch((err) => { + console.warn('Mermaid rendering warning:', err); + }); } - // Process bold **text** - const boldParts = part.split(/(\*\*[^*]+\*\*)/); - return boldParts.map((bPart, j) => { - if (bPart.startsWith('**') && bPart.endsWith('**') && bPart.length >= 4) { - return {bPart.slice(2, -2)}; - } - return bPart; - }); - }); - }; + } catch (e) { + console.warn('Failed to initialize mermaid:', e); + } + }, [renderedHtml, viewMode]); return (
    - {/* Doc Reader Header */} + {/* Header Toolbar */}
    -
    - {isMarkdown ? '📝' : '📄'} -
    -

    {basename}

    -
    - {filepath} - {totalLines} lines - {formatSize(sizeBytes)} -
    -
    +
    + + + + + + + + {basename} + + {totalLines} lines + {sizeBytes > 0 && {formatSize(sizeBytes)}}
    -
    +
    {isMarkdown && ( -
    +
    @@ -168,6 +220,7 @@ export const NavigatorDocReader: React.FC = ({ type="button" className={`toggle-btn ${viewMode === 'raw' ? 'active' : ''}`} onClick={() => setViewMode('raw')} + aria-pressed={viewMode === 'raw'} > Raw Source @@ -204,9 +257,12 @@ export const NavigatorDocReader: React.FC = ({ )}
    ) : viewMode === 'rendered' && isMarkdown ? ( -
    - {renderMarkdown(content)} -
    +
    ) : (
    diff --git a/frontend/src/components/navigator/NavigatorInspector.tsx b/frontend/src/components/navigator/NavigatorInspector.tsx
    index 68d837c..f5ef56d 100644
    --- a/frontend/src/components/navigator/NavigatorInspector.tsx
    +++ b/frontend/src/components/navigator/NavigatorInspector.tsx
    @@ -35,16 +35,27 @@ export const NavigatorInspector: React.FC = ({
       loading = false,
       onRefreshContent,
     }) => {
    -  const [internalTab, setInternalTab] = useState<'intelligence' | 'reader'>('intelligence');
    +  const [internalTab, setInternalTab] = useState<'intelligence' | 'reader'>(
    +    impact?.symbol || loading ? 'intelligence' : (fileContent ? 'reader' : 'intelligence')
    +  );
       const [copied, setCopied] = useState(false);
     
       const currentTab = activeInspectorTab !== undefined ? activeInspectorTab : internalTab;
       const setTab = onChangeInspectorTab || setInternalTab;
     
    +  const symbol = impact?.symbol;
    +  const route = impact?.route;
    +  const callers = impact?.callers || [];
    +  const callees = impact?.callees || [];
    +  const imports = impact?.imports || [];
    +
    +  const hasFile = !!(fileContent || loadingContent || contentError);
    +  const showDocReader = currentTab === 'reader';
    +
       const handleCopyPermalink = async () => {
         if (!impact?.symbol) return;
         const { filepath, start_line, end_line } = impact.symbol;
    -    const permalink = `${filepath}#L${start_line}${end_line && end_line !== start_line ? `-L${end_line}` : ''}`;
    +    const permalink = `${window.location.origin}${window.location.pathname}?repo=${encodeURIComponent(impact.symbol.repo || '')}&file=${filepath}#L${start_line}-L${end_line}`;
         try {
           if (navigator?.clipboard?.writeText) {
             await navigator.clipboard.writeText(permalink);
    @@ -56,50 +67,48 @@ export const NavigatorInspector: React.FC = ({
         }
       };
     
    -  const symbol = impact?.symbol;
    -  const route = impact?.route;
    -  const callers = impact?.callers || [];
    -  const callees = impact?.callees || [];
    -  const imports = impact?.imports || [];
    -
    -  const showDocReader = (!symbol && (fileContent || loadingContent || contentError)) || (symbol && currentTab === 'reader');
    -
       return (
         
    - - - + + + - {showDocReader && !symbol ? 'Document & File Reader' : 'Code Intelligence & Impact'} + Inspector
    + {/* Stable tabs — always rendered when a file is open or impact is present */}
    - {symbol && fileContent && ( + {(hasFile || symbol) && (
    )} @@ -134,6 +143,7 @@ export const NavigatorInspector: React.FC = ({
    + {/* File/Reader tab */} {showDocReader && fileContent ? ( (fileContent.filepath.toLowerCase().endsWith('.md') || fileContent.filepath.toLowerCase().endsWith('.markdown')) ? ( = ({
    + ) : showDocReader ? ( + /* reader tab but no file yet */ +
    +
    +

    No File or Symbol Selected

    +

    Select a file from the tree to read its documentation or select a symbol to inspect callers and dependencies.

    +
    ) : loading ? ( + /* intelligence tab loading */
    - ) : !impact || !symbol ? ( + ) : !symbol ? ( + /* intelligence tab but no symbol picked yet */
    -
    🔍
    +

    No File or Symbol Selected

    Select a file from the tree to read its documentation or select a symbol to inspect callers and dependencies.

    diff --git a/frontend/src/components/navigator/NavigatorOmniSearch.tsx b/frontend/src/components/navigator/NavigatorOmniSearch.tsx index efb9c3d..009b5ea 100644 --- a/frontend/src/components/navigator/NavigatorOmniSearch.tsx +++ b/frontend/src/components/navigator/NavigatorOmniSearch.tsx @@ -172,7 +172,24 @@ export const NavigatorOmniSearch: React.FC = ({ aria-expanded={isOpen} /> - {loading &&
    } + {loading && ( + + + + + )} {query && (
    ) : !outline ? (
    -
    📂
    +
    + + + +

    No File Selected

    Select a file from the tree to inspect its symbols and routes.

    ) : symbols.length === 0 ? (
    -
    📝
    -

    Document File Selected

    -

    - This file contains documentation or text content with no code AST symbols. +

    + + + + + + + +
    +

    Document File Active

    +

    + Full document is loaded in the main viewer.

    {onReadDoc && ( )}
    diff --git a/frontend/src/components/navigator/NavigatorToolbar.tsx b/frontend/src/components/navigator/NavigatorToolbar.tsx index 70fb463..5cf13d8 100644 --- a/frontend/src/components/navigator/NavigatorToolbar.tsx +++ b/frontend/src/components/navigator/NavigatorToolbar.tsx @@ -32,7 +32,7 @@ export const NavigatorToolbar: React.FC = ({ loading = false, }) => { return ( -
    +