From 9ed3031a42935b8e4171b9e878ef99f65dc8dc53 Mon Sep 17 00:00:00 2001 From: Xubin Ren Date: Sat, 18 Apr 2026 18:51:53 +0000 Subject: [PATCH] feat(webui): add initial webui with websocket chat flow --- .gitignore | 4 + nanobot/channels/manager.py | 35 +- nanobot/channels/websocket.py | 339 ++++++- nanobot/cli/commands.py | 120 ++- nanobot/session/manager.py | 66 +- nanobot/web/__init__.py | 6 + pyproject.toml | 1 + tests/agent/test_session_delete.py | 65 ++ tests/channels/test_websocket_http_routes.py | 381 +++++++ tests/channels/test_websocket_integration.py | 39 + webui/.gitignore | 8 + webui/README.md | 45 + webui/bun.lock | 951 ++++++++++++++++++ webui/components.json | 20 + webui/index.html | 100 ++ webui/package.json | 55 + webui/postcss.config.js | 6 + webui/public/brand/nanobot_apple_touch.png | Bin 0 -> 30083 bytes webui/public/brand/nanobot_favicon_32.png | Bin 0 -> 2209 bytes webui/public/brand/nanobot_icon.png | Bin 0 -> 7365 bytes webui/public/brand/nanobot_logo.png | Bin 0 -> 28453 bytes webui/public/brand/nanobot_logo.webp | Bin 0 -> 11938 bytes webui/src/App.tsx | 311 ++++++ webui/src/components/ChatList.tsx | 109 ++ webui/src/components/ChatPane.tsx | 126 +++ webui/src/components/CodeBlock.tsx | 68 ++ webui/src/components/Composer.tsx | 124 +++ webui/src/components/ConnectionBadge.tsx | 59 ++ webui/src/components/DeleteConfirm.tsx | 46 + webui/src/components/EmptyState.tsx | 26 + webui/src/components/MarkdownText.tsx | 39 + webui/src/components/MarkdownTextRenderer.tsx | 86 ++ webui/src/components/MessageBubble.tsx | 159 +++ webui/src/components/MessageList.tsx | 109 ++ webui/src/components/Sidebar.tsx | 87 ++ .../src/components/thread/ThreadComposer.tsx | 144 +++ webui/src/components/thread/ThreadHeader.tsx | 52 + .../src/components/thread/ThreadMessages.tsx | 16 + webui/src/components/thread/ThreadShell.tsx | 162 +++ .../src/components/thread/ThreadViewport.tsx | 114 +++ webui/src/components/ui/alert-dialog.tsx | 133 +++ webui/src/components/ui/avatar.tsx | 48 + webui/src/components/ui/button.tsx | 56 ++ webui/src/components/ui/dialog.tsx | 116 +++ webui/src/components/ui/dropdown-menu.tsx | 177 ++++ webui/src/components/ui/input.tsx | 24 + webui/src/components/ui/scroll-area.tsx | 46 + webui/src/components/ui/separator.tsx | 29 + webui/src/components/ui/sheet.tsx | 109 ++ webui/src/components/ui/textarea.tsx | 23 + webui/src/components/ui/tooltip.tsx | 26 + webui/src/globals.css | 113 +++ webui/src/hooks/useNanobotStream.ts | 165 +++ webui/src/hooks/useSessions.ts | 187 ++++ webui/src/hooks/useTheme.ts | 48 + webui/src/lib/api.ts | 93 ++ webui/src/lib/bootstrap.ts | 40 + webui/src/lib/format.ts | 44 + webui/src/lib/nanobot-client.ts | 264 +++++ webui/src/lib/types.ts | 74 ++ webui/src/lib/utils.ts | 6 + webui/src/main.tsx | 14 + webui/src/providers/ClientProvider.tsx | 37 + webui/src/tests/api.test.ts | 37 + webui/src/tests/app-layout.test.tsx | 148 +++ webui/src/tests/message-bubble.test.tsx | 43 + webui/src/tests/nanobot-client.test.ts | 192 ++++ webui/src/tests/setup.ts | 14 + webui/src/tests/thread-composer.test.tsx | 23 + webui/src/tests/thread-shell.test.tsx | 324 ++++++ webui/src/tests/useNanobotStream.test.tsx | 94 ++ webui/src/tests/useSessions.test.tsx | 116 +++ webui/tailwind.config.js | 81 ++ webui/tsconfig.build.json | 7 + webui/tsconfig.json | 32 + webui/vite.config.ts | 65 ++ 76 files changed, 7088 insertions(+), 38 deletions(-) create mode 100644 nanobot/web/__init__.py create mode 100644 tests/agent/test_session_delete.py create mode 100644 tests/channels/test_websocket_http_routes.py create mode 100644 webui/.gitignore create mode 100644 webui/README.md create mode 100644 webui/bun.lock create mode 100644 webui/components.json create mode 100644 webui/index.html create mode 100644 webui/package.json create mode 100644 webui/postcss.config.js create mode 100644 webui/public/brand/nanobot_apple_touch.png create mode 100644 webui/public/brand/nanobot_favicon_32.png create mode 100644 webui/public/brand/nanobot_icon.png create mode 100644 webui/public/brand/nanobot_logo.png create mode 100644 webui/public/brand/nanobot_logo.webp create mode 100644 webui/src/App.tsx create mode 100644 webui/src/components/ChatList.tsx create mode 100644 webui/src/components/ChatPane.tsx create mode 100644 webui/src/components/CodeBlock.tsx create mode 100644 webui/src/components/Composer.tsx create mode 100644 webui/src/components/ConnectionBadge.tsx create mode 100644 webui/src/components/DeleteConfirm.tsx create mode 100644 webui/src/components/EmptyState.tsx create mode 100644 webui/src/components/MarkdownText.tsx create mode 100644 webui/src/components/MarkdownTextRenderer.tsx create mode 100644 webui/src/components/MessageBubble.tsx create mode 100644 webui/src/components/MessageList.tsx create mode 100644 webui/src/components/Sidebar.tsx create mode 100644 webui/src/components/thread/ThreadComposer.tsx create mode 100644 webui/src/components/thread/ThreadHeader.tsx create mode 100644 webui/src/components/thread/ThreadMessages.tsx create mode 100644 webui/src/components/thread/ThreadShell.tsx create mode 100644 webui/src/components/thread/ThreadViewport.tsx create mode 100644 webui/src/components/ui/alert-dialog.tsx create mode 100644 webui/src/components/ui/avatar.tsx create mode 100644 webui/src/components/ui/button.tsx create mode 100644 webui/src/components/ui/dialog.tsx create mode 100644 webui/src/components/ui/dropdown-menu.tsx create mode 100644 webui/src/components/ui/input.tsx create mode 100644 webui/src/components/ui/scroll-area.tsx create mode 100644 webui/src/components/ui/separator.tsx create mode 100644 webui/src/components/ui/sheet.tsx create mode 100644 webui/src/components/ui/textarea.tsx create mode 100644 webui/src/components/ui/tooltip.tsx create mode 100644 webui/src/globals.css create mode 100644 webui/src/hooks/useNanobotStream.ts create mode 100644 webui/src/hooks/useSessions.ts create mode 100644 webui/src/hooks/useTheme.ts create mode 100644 webui/src/lib/api.ts create mode 100644 webui/src/lib/bootstrap.ts create mode 100644 webui/src/lib/format.ts create mode 100644 webui/src/lib/nanobot-client.ts create mode 100644 webui/src/lib/types.ts create mode 100644 webui/src/lib/utils.ts create mode 100644 webui/src/main.tsx create mode 100644 webui/src/providers/ClientProvider.tsx create mode 100644 webui/src/tests/api.test.ts create mode 100644 webui/src/tests/app-layout.test.tsx create mode 100644 webui/src/tests/message-bubble.test.tsx create mode 100644 webui/src/tests/nanobot-client.test.ts create mode 100644 webui/src/tests/setup.ts create mode 100644 webui/src/tests/thread-composer.test.tsx create mode 100644 webui/src/tests/thread-shell.test.tsx create mode 100644 webui/src/tests/useNanobotStream.test.tsx create mode 100644 webui/src/tests/useSessions.test.tsx create mode 100644 webui/tailwind.config.js create mode 100644 webui/tsconfig.build.json create mode 100644 webui/tsconfig.json create mode 100644 webui/vite.config.ts diff --git a/.gitignore b/.gitignore index 151e6947..5806617e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ webui/node_modules/ webui/dist/ webui/coverage/ webui/.vite/ +webui/*.tsbuildinfo + +# Built webui assets shipped from `webui/` into the Python package +nanobot/web/dist/ # Python bytecode & caches *.pyc diff --git a/nanobot/channels/manager.py b/nanobot/channels/manager.py index c0622a27..f8f2c1d8 100644 --- a/nanobot/channels/manager.py +++ b/nanobot/channels/manager.py @@ -3,7 +3,8 @@ from __future__ import annotations import asyncio -from typing import Any +from pathlib import Path +from typing import TYPE_CHECKING, Any from loguru import logger @@ -13,6 +14,19 @@ from nanobot.channels.base import BaseChannel from nanobot.config.schema import Config from nanobot.utils.restart import consume_restart_notice_from_env, format_restart_completed_message +if TYPE_CHECKING: + from nanobot.session.manager import SessionManager + + +def _default_webui_dist() -> Path | None: + """Return the absolute path to the bundled webui dist directory if it exists.""" + try: + import nanobot.web as web_pkg # type: ignore[import-not-found] + except ImportError: + return None + candidate = Path(web_pkg.__file__).resolve().parent / "dist" + return candidate if candidate.is_dir() else None + # Retry delays for message sending (exponential backoff: 1s, 2s, 4s) _SEND_RETRY_DELAYS = (1, 2, 4) @@ -27,9 +41,16 @@ class ChannelManager: - Route outbound messages """ - def __init__(self, config: Config, bus: MessageBus): + def __init__( + self, + config: Config, + bus: MessageBus, + *, + session_manager: "SessionManager | None" = None, + ): self.config = config self.bus = bus + self._session_manager = session_manager self.channels: dict[str, BaseChannel] = {} self._dispatch_task: asyncio.Task | None = None @@ -55,7 +76,15 @@ class ChannelManager: if not enabled: continue try: - channel = cls(section, self.bus) + kwargs: dict[str, Any] = {} + # Only the WebSocket channel currently hosts the embedded webui + # surface; other channels stay oblivious to these knobs. + if cls.name == "websocket" and self._session_manager is not None: + kwargs["session_manager"] = self._session_manager + static_path = _default_webui_dist() + if static_path is not None: + kwargs["static_dist_path"] = static_path + channel = cls(section, self.bus, **kwargs) channel.transcription_provider = transcription_provider channel.transcription_api_key = transcription_key channel.transcription_api_base = transcription_base diff --git a/nanobot/channels/websocket.py b/nanobot/channels/websocket.py index 882793ae..639519a4 100644 --- a/nanobot/channels/websocket.py +++ b/nanobot/channels/websocket.py @@ -7,13 +7,15 @@ import email.utils import hmac import http import json +import mimetypes import re import secrets import ssl import time import uuid -from typing import Any, Self -from urllib.parse import parse_qs, urlparse +from pathlib import Path +from typing import TYPE_CHECKING, Any, Self +from urllib.parse import parse_qs, unquote, urlparse from loguru import logger from pydantic import Field, field_validator, model_validator @@ -28,6 +30,9 @@ from nanobot.bus.queue import MessageBus from nanobot.channels.base import BaseChannel from nanobot.config.schema import Base +if TYPE_CHECKING: + from nanobot.session.manager import SessionManager + def _strip_trailing_slash(path: str) -> str: if len(path) > 1 and path.endswith("/"): @@ -116,6 +121,18 @@ def _http_json_response(data: dict[str, Any], *, status: int = 200) -> Response: return Response(status, reason, headers, body) +def _read_webui_model_name() -> str | None: + """Return the configured default model for readonly webui display.""" + try: + from nanobot.config.loader import load_config + + model = load_config().agents.defaults.model.strip() + return model or None + except Exception as e: + logger.debug("webui bootstrap could not load model name: {}", e) + return None + + def _parse_request_path(path_with_query: str) -> tuple[str, dict[str, list[str]]]: """Parse normalized path and query parameters in one pass.""" parsed = urlparse("ws://x" + path_with_query) @@ -189,6 +206,78 @@ def _parse_envelope(raw: str) -> dict[str, Any] | None: return data +_LOCALHOSTS = frozenset({"127.0.0.1", "::1", "localhost"}) + +# Matches the legacy chat-id pattern but allows file-system-safe stems too, +# so the API can address sessions whose keys came from non-WebSocket channels. +_API_KEY_RE = re.compile(r"^[A-Za-z0-9_:.-]{1,128}$") + + +def _decode_api_key(raw_key: str) -> str | None: + """Decode a percent-encoded API path segment, then validate the result.""" + key = unquote(raw_key) + if _API_KEY_RE.match(key) is None: + return None + return key + + +def _is_localhost(connection: Any) -> bool: + """Return True if *connection* originated from the loopback interface.""" + addr = getattr(connection, "remote_address", None) + if not addr: + return False + host = addr[0] if isinstance(addr, tuple) else addr + if not isinstance(host, str): + return False + # ``::ffff:127.0.0.1`` is loopback in IPv6-mapped form. + if host.startswith("::ffff:"): + host = host[7:] + return host in _LOCALHOSTS + + +def _http_response( + body: bytes, + *, + status: int = 200, + content_type: str = "text/plain; charset=utf-8", + extra_headers: list[tuple[str, str]] | None = None, +) -> Response: + headers = [ + ("Date", email.utils.formatdate(usegmt=True)), + ("Connection", "close"), + ("Content-Length", str(len(body))), + ("Content-Type", content_type), + ] + if extra_headers: + headers.extend(extra_headers) + reason = http.HTTPStatus(status).phrase + return Response(status, reason, Headers(headers), body) + + +def _http_error(status: int, message: str | None = None) -> Response: + body = (message or http.HTTPStatus(status).phrase).encode("utf-8") + return _http_response(body, status=status) + + +def _bearer_token(headers: Any) -> str | None: + """Pull a Bearer token out of standard or query-style headers.""" + auth = headers.get("Authorization") or headers.get("authorization") + if auth and auth.lower().startswith("bearer "): + return auth[7:].strip() or None + return None + + +def _is_websocket_upgrade(request: WsRequest) -> bool: + """Detect an actual WS upgrade; plain HTTP GETs to the same path should fall through.""" + upgrade = request.headers.get("Upgrade") or request.headers.get("upgrade") + connection = request.headers.get("Connection") or request.headers.get("connection") + if not upgrade or "websocket" not in upgrade.lower(): + return False + if not connection or "upgrade" not in connection.lower(): + return False + return True + + def _issue_route_secret_matches(headers: Any, configured_secret: str) -> bool: """Return True if the token-issue HTTP request carries credentials matching ``token_issue_secret``.""" if not configured_secret: @@ -209,7 +298,14 @@ class WebSocketChannel(BaseChannel): name = "websocket" display_name = "WebSocket" - def __init__(self, config: Any, bus: MessageBus): + def __init__( + self, + config: Any, + bus: MessageBus, + *, + session_manager: "SessionManager | None" = None, + static_dist_path: Path | None = None, + ): if isinstance(config, dict): config = WebSocketConfig.model_validate(config) super().__init__(config, bus) @@ -220,9 +316,16 @@ class WebSocketChannel(BaseChannel): self._conn_chats: dict[Any, set[str]] = {} # connection -> default chat_id for legacy frames that omit routing. self._conn_default: dict[Any, str] = {} + # Single-use tokens consumed at WebSocket handshake. self._issued_tokens: dict[str, float] = {} + # Multi-use tokens for the embedded webui's REST surface; checked but not consumed. + self._api_tokens: dict[str, float] = {} self._stop_event: asyncio.Event | None = None self._server_task: asyncio.Task[None] | None = None + self._session_manager = session_manager + self._static_dist_path: Path | None = ( + static_dist_path.resolve() if static_dist_path is not None else None + ) # -- Subscription bookkeeping ------------------------------------------- @@ -324,6 +427,209 @@ class WebSocketChannel(BaseChannel): {"token": token_value, "expires_in": self.config.token_ttl_s} ) + # -- HTTP dispatch ------------------------------------------------------ + + async def _dispatch_http(self, connection: Any, request: WsRequest) -> Any: + """Route an inbound HTTP request to a handler or to the WS upgrade path.""" + got, query = _parse_request_path(request.path) + + # 1. Token issue endpoint (legacy, optional, gated by configured secret). + if self.config.token_issue_path: + issue_expected = _normalize_config_path(self.config.token_issue_path) + if got == issue_expected: + return self._handle_token_issue_http(connection, request) + + # 2. WebUI bootstrap: localhost-only, mints tokens for the embedded UI. + if got == "/webui/bootstrap": + return self._handle_webui_bootstrap(connection) + + # 3. REST surface for the embedded UI. + if got == "/api/sessions": + return self._handle_sessions_list(request) + + m = re.match(r"^/api/sessions/([^/]+)/messages$", got) + if m: + return self._handle_session_messages(request, m.group(1)) + + # NOTE: websockets' HTTP parser only accepts GET, so we cannot expose a + # true ``DELETE`` verb. The action is folded into the path instead. + m = re.match(r"^/api/sessions/([^/]+)/delete$", got) + if m: + return self._handle_session_delete(request, m.group(1)) + + # 4. WebSocket upgrade (the channel's primary purpose). Only run the + # handshake gate on requests that actually ask to upgrade; otherwise + # a bare ``GET /`` from the browser would be rejected as an + # unauthorized WS handshake instead of serving the SPA's index.html. + expected_ws = self._expected_path() + if got == expected_ws and _is_websocket_upgrade(request): + client_id = _query_first(query, "client_id") or "" + if len(client_id) > 128: + client_id = client_id[:128] + if not self.is_allowed(client_id): + return connection.respond(403, "Forbidden") + return self._authorize_websocket_handshake(connection, query) + + # 5. Static SPA serving (only if a build directory was wired in). + if self._static_dist_path is not None: + response = self._serve_static(got) + if response is not None: + return response + + return connection.respond(404, "Not Found") + + # -- HTTP route handlers ------------------------------------------------ + + def _check_api_token(self, request: WsRequest) -> bool: + """Validate a request against the API token pool (multi-use, TTL-bound).""" + self._purge_expired_api_tokens() + token = _bearer_token(request.headers) or _query_first( + _parse_query(request.path), "token" + ) + if not token: + return False + expiry = self._api_tokens.get(token) + if expiry is None or time.monotonic() > expiry: + self._api_tokens.pop(token, None) + return False + return True + + def _purge_expired_api_tokens(self) -> None: + now = time.monotonic() + for token_key, expiry in list(self._api_tokens.items()): + if now > expiry: + self._api_tokens.pop(token_key, None) + + def _handle_webui_bootstrap(self, connection: Any) -> Response: + if not _is_localhost(connection): + return _http_error(403, "webui bootstrap is localhost-only") + # Cap outstanding tokens to avoid runaway growth from a misbehaving client. + self._purge_expired_issued_tokens() + self._purge_expired_api_tokens() + if ( + len(self._issued_tokens) >= self._MAX_ISSUED_TOKENS + or len(self._api_tokens) >= self._MAX_ISSUED_TOKENS + ): + return _http_response( + json.dumps({"error": "too many outstanding tokens"}).encode("utf-8"), + status=429, + content_type="application/json; charset=utf-8", + ) + token = f"nbwt_{secrets.token_urlsafe(32)}" + expiry = time.monotonic() + float(self.config.token_ttl_s) + # Same string registered in both pools: the WS handshake consumes one copy + # while the REST surface keeps validating the other until TTL expiry. + self._issued_tokens[token] = expiry + self._api_tokens[token] = expiry + return _http_json_response( + { + "token": token, + "ws_path": self._expected_path(), + "expires_in": self.config.token_ttl_s, + "model_name": _read_webui_model_name(), + } + ) + + def _handle_sessions_list(self, request: WsRequest) -> Response: + if not self._check_api_token(request): + return _http_error(401, "Unauthorized") + if self._session_manager is None: + return _http_error(503, "session manager unavailable") + sessions = self._session_manager.list_sessions() + # The webui is only meaningful for websocket-channel chats — CLI / + # Slack / Lark / Discord sessions can't be resumed from the browser, + # so leaking them into the sidebar is just noise. Filter to the + # ``websocket:`` prefix and strip absolute paths on the way out. + cleaned = [ + {k: v for k, v in s.items() if k != "path"} + for s in sessions + if isinstance(s.get("key"), str) and s["key"].startswith("websocket:") + ] + return _http_json_response({"sessions": cleaned}) + + @staticmethod + def _is_webui_session_key(key: str) -> bool: + """Return True when *key* belongs to the webui's websocket-only surface.""" + return key.startswith("websocket:") + + def _handle_session_messages(self, request: WsRequest, key: str) -> Response: + if not self._check_api_token(request): + return _http_error(401, "Unauthorized") + if self._session_manager is None: + return _http_error(503, "session manager unavailable") + decoded_key = _decode_api_key(key) + if decoded_key is None: + return _http_error(400, "invalid session key") + # The embedded webui only understands websocket-channel sessions. Keep + # its read surface aligned with ``/api/sessions`` instead of letting a + # caller probe arbitrary CLI / Slack / Lark history by handcrafted URL. + if not self._is_webui_session_key(decoded_key): + return _http_error(404, "session not found") + data = self._session_manager.read_session_file(decoded_key) + if data is None: + return _http_error(404, "session not found") + return _http_json_response(data) + + def _handle_session_delete(self, request: WsRequest, key: str) -> Response: + if not self._check_api_token(request): + return _http_error(401, "Unauthorized") + if self._session_manager is None: + return _http_error(503, "session manager unavailable") + decoded_key = _decode_api_key(key) + if decoded_key is None: + return _http_error(400, "invalid session key") + # Same boundary as ``_handle_session_messages``: the webui may only + # mutate websocket sessions, and deletion really does unlink the local + # JSONL, so keep the blast radius narrow and explicit. + if not self._is_webui_session_key(decoded_key): + return _http_error(404, "session not found") + deleted = self._session_manager.delete_session(decoded_key) + return _http_json_response({"deleted": bool(deleted)}) + + def _serve_static(self, request_path: str) -> Response | None: + """Resolve *request_path* against the built SPA directory; SPA fallback to index.html.""" + assert self._static_dist_path is not None + rel = request_path.lstrip("/") + if not rel: + rel = "index.html" + # Reject path-traversal attempts and absolute targets. + if ".." in rel.split("/") or rel.startswith("/"): + return _http_error(403, "Forbidden") + candidate = (self._static_dist_path / rel).resolve() + try: + candidate.relative_to(self._static_dist_path) + except ValueError: + return _http_error(403, "Forbidden") + if not candidate.is_file(): + # SPA history-mode fallback: unknown routes serve index.html so the + # client-side router can render them. + index = self._static_dist_path / "index.html" + if index.is_file(): + candidate = index + else: + return None + try: + body = candidate.read_bytes() + except OSError as e: + logger.warning("websocket static: failed to read {}: {}", candidate, e) + return _http_error(500, "Internal Server Error") + ctype, _ = mimetypes.guess_type(candidate.name) + if ctype is None: + ctype = "application/octet-stream" + if ctype.startswith("text/") or ctype in {"application/javascript", "application/json"}: + ctype = f"{ctype}; charset=utf-8" + # Hash-named build assets are cache-friendly; index.html must stay fresh. + if candidate.name == "index.html": + cache = "no-cache" + else: + cache = "public, max-age=31536000, immutable" + return _http_response( + body, + status=200, + content_type=ctype, + extra_headers=[("Cache-Control", cache)], + ) + def _authorize_websocket_handshake(self, connection: Any, query: dict[str, list[str]]) -> Any: supplied = _query_first(query, "token") static_token = self.config.token.strip() @@ -355,24 +661,7 @@ class WebSocketChannel(BaseChannel): connection: ServerConnection, request: WsRequest, ) -> Any: - got, _ = _parse_request_path(request.path) - if self.config.token_issue_path: - issue_expected = _normalize_config_path(self.config.token_issue_path) - if got == issue_expected: - return self._handle_token_issue_http(connection, request) - - expected_ws = self._expected_path() - if got != expected_ws: - return connection.respond(404, "Not Found") - # Early reject before WebSocket upgrade to avoid unnecessary overhead; - # _handle_message() performs a second check as defense-in-depth. - query = _parse_query(request.path) - client_id = _query_first(query, "client_id") or "" - if len(client_id) > 128: - client_id = client_id[:128] - if not self.is_allowed(client_id): - return connection.respond(403, "Forbidden") - return self._authorize_websocket_handshake(connection, query) + return await self._dispatch_http(connection, request) async def handler(connection: ServerConnection) -> None: await self._connection_loop(connection) @@ -523,6 +812,7 @@ class WebSocketChannel(BaseChannel): self._conn_chats.clear() self._conn_default.clear() self._issued_tokens.clear() + self._api_tokens.clear() async def _safe_send_to(self, connection: Any, raw: str, *, label: str = "") -> None: """Send a raw frame to one connection, cleaning up on ConnectionClosed.""" @@ -550,6 +840,13 @@ class WebSocketChannel(BaseChannel): payload["media"] = msg.media if msg.reply_to: payload["reply_to"] = msg.reply_to + # Mark intermediate agent breadcrumbs (tool-call hints, generic + # progress strings) so WS clients can render them as subordinate + # trace rows rather than conversational replies. + if msg.metadata.get("_tool_hint"): + payload["kind"] = "tool_hint" + elif msg.metadata.get("_progress"): + payload["kind"] = "progress" raw = json.dumps(payload, ensure_ascii=False) for connection in conns: await self._safe_send_to(connection, raw, label=" ") diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index 5f043050..f3b72a72 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -636,6 +636,21 @@ def gateway( config: str | None = typer.Option(None, "--config", "-c", help="Path to config file"), ): """Start the nanobot gateway.""" + if verbose: + import logging + + logging.basicConfig(level=logging.DEBUG) + cfg = _load_runtime_config(config, workspace) + _run_gateway(cfg, port=port) + + +def _run_gateway( + config: Config, + *, + port: int | None = None, + open_browser_url: str | None = None, +) -> None: + """Shared gateway runtime; ``open_browser_url`` opens a tab once channels are up.""" from nanobot.agent.loop import AgentLoop from nanobot.bus.queue import MessageBus from nanobot.channels.manager import ChannelManager @@ -644,12 +659,6 @@ def gateway( from nanobot.heartbeat.service import HeartbeatService from nanobot.session.manager import SessionManager - if verbose: - import logging - - logging.basicConfig(level=logging.DEBUG) - - config = _load_runtime_config(config, workspace) port = port if port is not None else config.gateway.port console.print(f"{__logo__} Starting nanobot gateway version {__version__} on port {port}...") @@ -749,8 +758,9 @@ def gateway( cron.on_job = on_cron_job - # Create channel manager - channels = ChannelManager(config, bus) + # Create channel manager (forwards SessionManager so the WebSocket channel + # can serve the embedded webui's REST surface). + channels = ChannelManager(config, bus, session_manager=session_manager) def _pick_heartbeat_target() -> tuple[str, str]: """Pick a routable channel/chat target for heartbeat-triggered messages.""" @@ -881,15 +891,43 @@ def gateway( )) console.print(f"[green]✓[/green] Dream: {dream_cfg.describe_schedule()}") + async def _open_browser_when_ready() -> None: + """Wait for the gateway to bind, then point the user's browser at the webui.""" + if not open_browser_url: + return + import webbrowser + # Channels start asynchronously; a short poll lets us avoid racing the bind. + for _ in range(40): # ~4s max + try: + reader, writer = await asyncio.open_connection( + config.gateway.host or "127.0.0.1", port + ) + writer.close() + try: + await writer.wait_closed() + except Exception: + pass + break + except OSError: + await asyncio.sleep(0.1) + try: + webbrowser.open(open_browser_url) + console.print(f"[green]✓[/green] Opened browser at {open_browser_url}") + except Exception as e: + console.print(f"[yellow]Could not open browser ({e}); visit {open_browser_url}[/yellow]") + async def run(): try: await cron.start() await heartbeat.start() - await asyncio.gather( + tasks = [ agent.run(), channels.start_all(), _health_server(config.gateway.host, port), - ) + ] + if open_browser_url: + tasks.append(_open_browser_when_ready()) + await asyncio.gather(*tasks) except KeyboardInterrupt: console.print("\nShutting down...") except Exception: @@ -907,6 +945,68 @@ def gateway( asyncio.run(run()) +@app.command() +def web( + port: int | None = typer.Option(None, "--port", "-p", help="WebSocket port for the webui"), + workspace: str | None = typer.Option(None, "--workspace", "-w", help="Workspace directory"), + verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"), + config: str | None = typer.Option(None, "--config", "-c", help="Path to config file"), + open_browser: bool = typer.Option(True, "--open/--no-open", help="Open the browser when ready"), +): + """Start the gateway with the embedded webui and (by default) open a browser.""" + if verbose: + import logging + + logging.basicConfig(level=logging.DEBUG) + + cfg = _load_runtime_config(config, workspace) + + # Force the websocket channel on with token-gated auth so the webui is functional. + # ``--port`` applies to the webui's websocket/HTTP port, not the gateway's + # management port, since that's the only surface users visit. + ws_section = cfg.channels.websocket + if isinstance(ws_section, dict): + ws_section.setdefault("host", "127.0.0.1") + ws_section["enabled"] = True + ws_section["websocketRequiresToken"] = True + if port is not None: + ws_section["port"] = port + ws_host = ws_section.get("host", "127.0.0.1") + ws_port = ws_section.get("port", 8765) + ws_path = ws_section.get("path", "/") + else: + ws_section.enabled = True + if hasattr(ws_section, "websocket_requires_token"): + ws_section.websocket_requires_token = True + if port is not None: + ws_section.port = port + ws_host = getattr(ws_section, "host", "127.0.0.1") or "127.0.0.1" + ws_port = getattr(ws_section, "port", 8765) + ws_path = getattr(ws_section, "path", "/") or "/" + + # Confirm the bundled SPA exists before promising the user a browser launch. + from nanobot.channels.manager import _default_webui_dist + + dist = _default_webui_dist() + if dist is None: + console.print( + "[yellow]Warning: webui assets not found at nanobot/web/dist/. " + "Run `cd webui && bun install && bun run build` from a source checkout.[/yellow]" + ) + + scheme = "http" + # Browsers refuse cookies/JS on 0.0.0.0 — collapse to loopback for the visit URL. + visit_host = "127.0.0.1" if ws_host in {"0.0.0.0", "::"} else ws_host + open_url = f"{scheme}://{visit_host}:{ws_port}{ws_path if ws_path != '/' else ''}/" + + # The gateway's management port is separate from the webui port; leave it + # on its configured default so --port only moves the visible surface. + _run_gateway( + cfg, + open_browser_url=open_url if open_browser else None, + ) + + # ============================================================================ # Agent Commands # ============================================================================ diff --git a/nanobot/session/manager.py b/nanobot/session/manager.py index 2ed0624a..c91eabcb 100644 --- a/nanobot/session/manager.py +++ b/nanobot/session/manager.py @@ -106,15 +106,18 @@ class SessionManager: self.legacy_sessions_dir = get_legacy_sessions_dir() self._cache: dict[str, Session] = {} + @staticmethod + def safe_key(key: str) -> str: + """Public helper used by HTTP handlers to map an arbitrary key to a stable filename stem.""" + return safe_filename(key.replace(":", "_")) + def _get_session_path(self, key: str) -> Path: """Get the file path for a session.""" - safe_key = safe_filename(key.replace(":", "_")) - return self.sessions_dir / f"{safe_key}.jsonl" + return self.sessions_dir / f"{self.safe_key(key)}.jsonl" def _get_legacy_session_path(self, key: str) -> Path: """Legacy global session path (~/.nanobot/sessions/).""" - safe_key = safe_filename(key.replace(":", "_")) - return self.legacy_sessions_dir / f"{safe_key}.jsonl" + return self.legacy_sessions_dir / f"{self.safe_key(key)}.jsonl" def get_or_create(self, key: str) -> Session: """ @@ -209,6 +212,61 @@ class SessionManager: """Remove a session from the in-memory cache.""" self._cache.pop(key, None) + def delete_session(self, key: str) -> bool: + """Remove a session from disk and the in-memory cache. + + Returns True if a JSONL file was found and unlinked. + """ + path = self._get_session_path(key) + self.invalidate(key) + if not path.exists(): + return False + try: + path.unlink() + return True + except OSError as e: + logger.warning("Failed to delete session file {}: {}", path, e) + return False + + def read_session_file(self, key: str) -> dict[str, Any] | None: + """Load a session from disk without caching; intended for read-only HTTP endpoints. + + Returns ``{"key", "created_at", "updated_at", "metadata", "messages"}`` or + ``None`` when the session file does not exist or fails to parse. + """ + path = self._get_session_path(key) + if not path.exists(): + return None + try: + messages: list[dict[str, Any]] = [] + metadata: dict[str, Any] = {} + created_at: str | None = None + updated_at: str | None = None + stored_key: str | None = None + with open(path, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line: + continue + data = json.loads(line) + if data.get("_type") == "metadata": + metadata = data.get("metadata", {}) + created_at = data.get("created_at") + updated_at = data.get("updated_at") + stored_key = data.get("key") + else: + messages.append(data) + return { + "key": stored_key or key, + "created_at": created_at, + "updated_at": updated_at, + "metadata": metadata, + "messages": messages, + } + except Exception as e: + logger.warning("Failed to read session {}: {}", key, e) + return None + def list_sessions(self) -> list[dict[str, Any]]: """ List all sessions. diff --git a/nanobot/web/__init__.py b/nanobot/web/__init__.py new file mode 100644 index 00000000..7a08932f --- /dev/null +++ b/nanobot/web/__init__.py @@ -0,0 +1,6 @@ +"""Embedded web UI assets. + +The ``dist/`` subdirectory is populated by ``cd webui && bun run build`` and +is shipped in the wheel; it stays empty in source checkouts until that command +has been run. +""" diff --git a/pyproject.toml b/pyproject.toml index 1e55fed4..5878570a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,6 +113,7 @@ include = [ "nanobot/templates/**/*.md", "nanobot/skills/**/*.md", "nanobot/skills/**/*.sh", + "nanobot/web/dist/**/*", ] [tool.hatch.build.targets.wheel] diff --git a/tests/agent/test_session_delete.py b/tests/agent/test_session_delete.py new file mode 100644 index 00000000..aa3296d9 --- /dev/null +++ b/tests/agent/test_session_delete.py @@ -0,0 +1,65 @@ +"""Tests for SessionManager.delete_session and read_session_file.""" + +from pathlib import Path + +from nanobot.session.manager import Session, SessionManager + + +def _seed(workspace: Path, key: str = "telegram:abc") -> SessionManager: + sm = SessionManager(workspace) + session = Session(key=key) + session.add_message("user", "hello") + session.add_message("assistant", "hi back") + sm.save(session) + return sm + + +def test_delete_session_removes_file_and_invalidates_cache(tmp_path: Path) -> None: + sm = _seed(tmp_path, "telegram:abc") + file_path = sm._get_session_path("telegram:abc") + assert file_path.exists() + # Populate cache as a real consumer would. + cached = sm.get_or_create("telegram:abc") + assert cached.messages + + assert sm.delete_session("telegram:abc") is True + assert not file_path.exists() + # Subsequent get_or_create returns a fresh, empty Session (no stale cache). + fresh = sm.get_or_create("telegram:abc") + assert fresh.messages == [] + + +def test_delete_session_returns_false_when_missing(tmp_path: Path) -> None: + sm = SessionManager(tmp_path) + assert sm.delete_session("nope:none") is False + + +def test_read_session_file_returns_metadata_and_messages(tmp_path: Path) -> None: + sm = _seed(tmp_path, "telegram:abc") + data = sm.read_session_file("telegram:abc") + assert data is not None + assert data["key"] == "telegram:abc" + assert isinstance(data["messages"], list) + assert [m["role"] for m in data["messages"]] == ["user", "assistant"] + assert data["created_at"] + assert data["updated_at"] + + +def test_read_session_file_does_not_populate_cache(tmp_path: Path) -> None: + sm = _seed(tmp_path, "telegram:abc") + sm.invalidate("telegram:abc") + assert "telegram:abc" not in sm._cache + sm.read_session_file("telegram:abc") + assert "telegram:abc" not in sm._cache + + +def test_read_session_file_missing(tmp_path: Path) -> None: + sm = SessionManager(tmp_path) + assert sm.read_session_file("nope:none") is None + + +def test_safe_key_matches_internal_path(tmp_path: Path) -> None: + sm = SessionManager(tmp_path) + key = "telegram:abc/def" + expected = sm._get_session_path(key).name + assert SessionManager.safe_key(key) + ".jsonl" == expected diff --git a/tests/channels/test_websocket_http_routes.py b/tests/channels/test_websocket_http_routes.py new file mode 100644 index 00000000..51fd50f4 --- /dev/null +++ b/tests/channels/test_websocket_http_routes.py @@ -0,0 +1,381 @@ +"""End-to-end tests for the embedded webui's HTTP routes on the WebSocket channel.""" + +import asyncio +import functools +import json +from pathlib import Path +from typing import Any +from unittest.mock import AsyncMock, MagicMock + +import httpx +import pytest + +from nanobot.channels.websocket import WebSocketChannel +from nanobot.session.manager import Session, SessionManager + +_PORT = 29900 + + +def _ch( + bus: Any, + *, + session_manager: SessionManager | None = None, + static_dist_path: Path | None = None, + port: int = _PORT, + **extra: Any, +) -> WebSocketChannel: + cfg: dict[str, Any] = { + "enabled": True, + "allowFrom": ["*"], + "host": "127.0.0.1", + "port": port, + "path": "/", + "websocketRequiresToken": False, + } + cfg.update(extra) + return WebSocketChannel( + cfg, + bus, + session_manager=session_manager, + static_dist_path=static_dist_path, + ) + + +@pytest.fixture() +def bus() -> MagicMock: + b = MagicMock() + b.publish_inbound = AsyncMock() + return b + + +async def _http_get( + url: str, headers: dict[str, str] | None = None +) -> httpx.Response: + return await asyncio.to_thread( + functools.partial(httpx.get, url, headers=headers or {}, timeout=5.0) + ) + + +def _seed_session(workspace: Path, key: str = "websocket:test") -> SessionManager: + sm = SessionManager(workspace) + s = Session(key=key) + s.add_message("user", "hi") + s.add_message("assistant", "hello back") + sm.save(s) + return sm + + +def _seed_many(workspace: Path, keys: list[str]) -> SessionManager: + sm = SessionManager(workspace) + for k in keys: + s = Session(key=k) + s.add_message("user", f"hi from {k}") + sm.save(s) + return sm + + +@pytest.mark.asyncio +async def test_bootstrap_returns_token_for_localhost( + bus: MagicMock, tmp_path: Path +) -> None: + sm = _seed_session(tmp_path) + channel = _ch(bus, session_manager=sm, port=29901) + server_task = asyncio.create_task(channel.start()) + await asyncio.sleep(0.3) + try: + resp = await _http_get("http://127.0.0.1:29901/webui/bootstrap") + assert resp.status_code == 200 + body = resp.json() + assert body["token"].startswith("nbwt_") + assert body["ws_path"] == "/" + assert body["expires_in"] > 0 + assert isinstance(body.get("model_name"), str) + finally: + await channel.stop() + await server_task + + +@pytest.mark.asyncio +async def test_sessions_routes_require_bearer_token( + bus: MagicMock, tmp_path: Path +) -> None: + sm = _seed_session(tmp_path, key="websocket:abc") + channel = _ch(bus, session_manager=sm, port=29902) + server_task = asyncio.create_task(channel.start()) + await asyncio.sleep(0.3) + try: + # Unauthenticated → 401. + deny = await _http_get("http://127.0.0.1:29902/api/sessions") + assert deny.status_code == 401 + + # Mint a token via bootstrap, then call the API with it. + boot = await _http_get("http://127.0.0.1:29902/webui/bootstrap") + token = boot.json()["token"] + auth = {"Authorization": f"Bearer {token}"} + + listing = await _http_get("http://127.0.0.1:29902/api/sessions", headers=auth) + assert listing.status_code == 200 + keys = [s["key"] for s in listing.json()["sessions"]] + assert "websocket:abc" in keys + # Server stays an opaque source: filesystem paths must not leak to the wire. + assert all("path" not in s for s in listing.json()["sessions"]) + + msgs = await _http_get( + "http://127.0.0.1:29902/api/sessions/websocket:abc/messages", + headers=auth, + ) + assert msgs.status_code == 200 + body = msgs.json() + assert body["key"] == "websocket:abc" + assert [m["role"] for m in body["messages"]] == ["user", "assistant"] + finally: + await channel.stop() + await server_task + + +@pytest.mark.asyncio +async def test_sessions_list_only_returns_websocket_sessions_by_default( + bus: MagicMock, tmp_path: Path +) -> None: + # Seed a realistic multi-channel disk state: CLI, Slack, Lark and + # websocket sessions all live in the same ``sessions/`` directory. + sm = _seed_many( + tmp_path, + [ + "cli:direct", + "slack:C123", + "lark:oc_abc", + "websocket:alpha", + "websocket:beta", + ], + ) + channel = _ch(bus, session_manager=sm, port=29906) + server_task = asyncio.create_task(channel.start()) + await asyncio.sleep(0.3) + try: + boot = await _http_get("http://127.0.0.1:29906/webui/bootstrap") + token = boot.json()["token"] + auth = {"Authorization": f"Bearer {token}"} + + listing = await _http_get( + "http://127.0.0.1:29906/api/sessions", headers=auth + ) + assert listing.status_code == 200 + keys = {s["key"] for s in listing.json()["sessions"]} + # Only websocket-channel sessions are part of the webui surface; CLI / + # Slack / Lark rows would be non-resumable from the browser. + assert keys == {"websocket:alpha", "websocket:beta"} + finally: + await channel.stop() + await server_task + + +@pytest.mark.asyncio +async def test_session_delete_removes_file(bus: MagicMock, tmp_path: Path) -> None: + sm = _seed_session(tmp_path, key="websocket:doomed") + channel = _ch(bus, session_manager=sm, port=29903) + server_task = asyncio.create_task(channel.start()) + await asyncio.sleep(0.3) + try: + boot = await _http_get("http://127.0.0.1:29903/webui/bootstrap") + token = boot.json()["token"] + auth = {"Authorization": f"Bearer {token}"} + + path = sm._get_session_path("websocket:doomed") + assert path.exists() + resp = await _http_get( + "http://127.0.0.1:29903/api/sessions/websocket:doomed/delete", + headers=auth, + ) + assert resp.status_code == 200 + assert resp.json()["deleted"] is True + assert not path.exists() + finally: + await channel.stop() + await server_task + + +@pytest.mark.asyncio +async def test_session_routes_accept_percent_encoded_websocket_keys( + bus: MagicMock, tmp_path: Path +) -> None: + sm = _seed_session(tmp_path, key="websocket:encoded-key") + channel = _ch(bus, session_manager=sm, port=29910) + server_task = asyncio.create_task(channel.start()) + await asyncio.sleep(0.3) + try: + boot = await _http_get("http://127.0.0.1:29910/webui/bootstrap") + token = boot.json()["token"] + auth = {"Authorization": f"Bearer {token}"} + + msgs = await _http_get( + "http://127.0.0.1:29910/api/sessions/websocket%3Aencoded-key/messages", + headers=auth, + ) + assert msgs.status_code == 200 + assert msgs.json()["key"] == "websocket:encoded-key" + + path = sm._get_session_path("websocket:encoded-key") + assert path.exists() + deleted = await _http_get( + "http://127.0.0.1:29910/api/sessions/websocket%3Aencoded-key/delete", + headers=auth, + ) + assert deleted.status_code == 200 + assert deleted.json()["deleted"] is True + assert not path.exists() + finally: + await channel.stop() + await server_task + + +@pytest.mark.asyncio +async def test_session_routes_reject_non_websocket_keys( + bus: MagicMock, tmp_path: Path +) -> None: + sm = _seed_many( + tmp_path, + [ + "websocket:kept", + "cli:direct", + "slack:C123", + ], + ) + channel = _ch(bus, session_manager=sm, port=29909) + server_task = asyncio.create_task(channel.start()) + await asyncio.sleep(0.3) + try: + boot = await _http_get("http://127.0.0.1:29909/webui/bootstrap") + token = boot.json()["token"] + auth = {"Authorization": f"Bearer {token}"} + + # The webui list already hides non-websocket sessions; handcrafted URLs + # should hit the same boundary rather than exposing or deleting them. + msgs = await _http_get( + "http://127.0.0.1:29909/api/sessions/cli:direct/messages", + headers=auth, + ) + assert msgs.status_code == 404 + + doomed = sm._get_session_path("slack:C123") + assert doomed.exists() + deny_delete = await _http_get( + "http://127.0.0.1:29909/api/sessions/slack:C123/delete", + headers=auth, + ) + assert deny_delete.status_code == 404 + assert doomed.exists() + finally: + await channel.stop() + await server_task + + +@pytest.mark.asyncio +async def test_session_routes_reject_invalid_key( + bus: MagicMock, tmp_path: Path +) -> None: + sm = _seed_session(tmp_path) + channel = _ch(bus, session_manager=sm, port=29904) + server_task = asyncio.create_task(channel.start()) + await asyncio.sleep(0.3) + try: + boot = await _http_get("http://127.0.0.1:29904/webui/bootstrap") + token = boot.json()["token"] + auth = {"Authorization": f"Bearer {token}"} + + # Invalid characters in the key -> regex match fails -> 404 + # (route doesn't match, falls through to channel 404). + resp = await _http_get( + "http://127.0.0.1:29904/api/sessions/bad%20key/messages", + headers=auth, + ) + assert resp.status_code in {400, 404} + finally: + await channel.stop() + await server_task + + +@pytest.mark.asyncio +async def test_static_serves_index_when_dist_present( + bus: MagicMock, tmp_path: Path +) -> None: + dist = tmp_path / "dist" + dist.mkdir() + (dist / "index.html").write_text("nbweb") + (dist / "favicon.svg").write_text("") + sm = _seed_session(tmp_path / "ws_state") + channel = _ch(bus, session_manager=sm, static_dist_path=dist, port=29905) + server_task = asyncio.create_task(channel.start()) + await asyncio.sleep(0.3) + try: + # Bare ``GET /`` is a browser opening the app: it must return the SPA + # index.html, not the WS-upgrade handler's 401/426. + root = await _http_get("http://127.0.0.1:29905/") + assert root.status_code == 200 + assert "nbweb" in root.text + asset = await _http_get("http://127.0.0.1:29905/favicon.svg") + assert asset.status_code == 200 + assert " None: + dist = tmp_path / "dist" + dist.mkdir() + (dist / "index.html").write_text("ok") + secret = tmp_path / "secret.txt" + secret.write_text("classified") + channel = _ch(bus, static_dist_path=dist, port=29906) + server_task = asyncio.create_task(channel.start()) + await asyncio.sleep(0.3) + try: + resp = await _http_get("http://127.0.0.1:29906/../secret.txt") + # Normalized by httpx into /secret.txt → falls back to index.html, not 'classified'. + assert "classified" not in resp.text + finally: + await channel.stop() + await server_task + + +@pytest.mark.asyncio +async def test_unknown_route_returns_404(bus: MagicMock) -> None: + channel = _ch(bus, port=29907) + server_task = asyncio.create_task(channel.start()) + await asyncio.sleep(0.3) + try: + resp = await _http_get("http://127.0.0.1:29907/api/unknown") + assert resp.status_code == 404 + finally: + await channel.stop() + await server_task + + +@pytest.mark.asyncio +async def test_api_token_pool_purges_expired(bus: MagicMock, tmp_path: Path) -> None: + sm = _seed_session(tmp_path) + channel = _ch(bus, session_manager=sm, port=29908) + # Don't start a server — directly inject and validate. + import time as _time + channel._api_tokens["expired"] = _time.monotonic() - 1 + channel._api_tokens["live"] = _time.monotonic() + 60 + + class _FakeReq: + path = "/api/sessions" + headers = {"Authorization": "Bearer expired"} + + assert channel._check_api_token(_FakeReq()) is False + + class _LiveReq: + path = "/api/sessions" + headers = {"Authorization": "Bearer live"} + + assert channel._check_api_token(_LiveReq()) is True diff --git a/tests/channels/test_websocket_integration.py b/tests/channels/test_websocket_integration.py index ff0d5085..8e98aa48 100644 --- a/tests/channels/test_websocket_integration.py +++ b/tests/channels/test_websocket_integration.py @@ -189,6 +189,45 @@ async def test_server_send_message(bus: MagicMock) -> None: await ch.stop(); await t +@pytest.mark.asyncio +async def test_server_send_tags_tool_hint_with_kind(bus: MagicMock) -> None: + """``_tool_hint`` metadata must surface as ``kind: "tool_hint"`` so WS + clients render breadcrumbs separately from conversational replies.""" + ch = _ch(bus, 29919) + t = asyncio.create_task(ch.start()) + await asyncio.sleep(0.3) + try: + async with WsTestClient("ws://127.0.0.1:29919/", client_id="h") as c: + ready = await c.recv_ready() + # Plain reply: no "kind" field. + await ch.send(OutboundMessage( + channel="websocket", chat_id=ready.chat_id, content="hi", + )) + plain = await c.recv_message() + assert plain.raw.get("kind") is None + + # Tool-hint breadcrumb: kind == "tool_hint". + await ch.send(OutboundMessage( + channel="websocket", chat_id=ready.chat_id, + content='weather("get")', + metadata={"_progress": True, "_tool_hint": True}, + )) + hint = await c.recv_message() + assert hint.raw.get("kind") == "tool_hint" + assert hint.text == 'weather("get")' + + # Generic progress (non-tool-hint) gets the softer "progress" label. + await ch.send(OutboundMessage( + channel="websocket", chat_id=ready.chat_id, + content="thinking…", + metadata={"_progress": True}, + )) + prog = await c.recv_message() + assert prog.raw.get("kind") == "progress" + finally: + await ch.stop(); await t + + @pytest.mark.asyncio async def test_server_send_with_media_and_reply(bus: MagicMock) -> None: ch = _ch(bus, 29910) diff --git a/webui/.gitignore b/webui/.gitignore new file mode 100644 index 00000000..cd8c35d5 --- /dev/null +++ b/webui/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +.vite/ +coverage/ +*.tsbuildinfo +.env +.env.* +!.env.example diff --git a/webui/README.md b/webui/README.md new file mode 100644 index 00000000..6d3e554c --- /dev/null +++ b/webui/README.md @@ -0,0 +1,45 @@ +# nanobot webui + +The browser front-end for `nanobot web`. Built with Vite + React 18 + +TypeScript + Tailwind 3 + shadcn/ui. Talks to the gateway over the WebSocket +multiplex protocol; session metadata comes from the embedded REST surface on +the same port. + +## Layout + +``` +webui/ source tree (this directory) +nanobot/web/dist/ build output, shipped in the Python wheel +``` + +## Develop + +```bash +cd webui +bun install # npm install also works +bun run dev # http://127.0.0.1:5173 (proxies /api /webui /auth -> 8765) +``` + +In a separate shell, start the gateway with the WebSocket channel: + +```bash +uv run nanobot gateway # or `nanobot web` once you've built once +``` + +If the gateway listens on a non-default port, point the dev server at it: + +```bash +NANOBOT_API_URL=http://127.0.0.1:9000 bun run dev +``` + +## Build + +```bash +bun run build # writes ../nanobot/web/dist (consumed by `nanobot web`) +``` + +## Test + +```bash +bun run test # vitest, jsdom-style happy-dom environment +``` diff --git a/webui/bun.lock b/webui/bun.lock new file mode 100644 index 00000000..e71f2dc5 --- /dev/null +++ b/webui/bun.lock @@ -0,0 +1,951 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "nanobot-webui", + "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.4", + "@radix-ui/react-avatar": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-scroll-area": "^1.2.2", + "@radix-ui/react-separator": "^1.1.1", + "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-tooltip": "^1.1.6", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.469.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-markdown": "^9.0.1", + "react-syntax-highlighter": "^15.6.1", + "rehype-katex": "^7.0.1", + "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", + "tailwind-merge": "^2.6.0", + }, + "devDependencies": { + "@tailwindcss/typography": "^0.5.19", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.1.0", + "@testing-library/user-event": "^14.5.2", + "@types/node": "^22.10.5", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@types/react-syntax-highlighter": "^15.5.13", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "happy-dom": "^16.3.0", + "katex": "^0.16.21", + "postcss": "^8.5.0", + "tailwindcss": "^3.4.17", + "tailwindcss-animate": "^1.0.7", + "typescript": "^5.7.2", + "vite": "^5.4.11", + "vitest": "^2.1.8", + }, + }, + }, + "packages": { + "@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="], + + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], + + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], + + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], + + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], + + "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], + + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], + + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], + + "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], + + "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], + + "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="], + + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], + + "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], + + "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], + + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], + + "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], + + "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], + + "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], + + "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], + + "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], + + "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], + + "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], + + "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], + + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], + + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], + + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], + + "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], + + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], + + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], + + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], + + "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], + + "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], + + "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], + + "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], + + "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="], + + "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], + + "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], + + "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], + + "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], + + "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="], + + "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="], + + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], + + "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="], + + "@testing-library/react": ["@testing-library/react@16.3.2", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g=="], + + "@testing-library/user-event": ["@testing-library/user-event@14.6.1", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="], + + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], + + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + + "@types/katex": ["@types/katex@0.16.8", "", {}, "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], + + "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], + + "@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + + "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], + + "@types/react-syntax-highlighter": ["@types/react-syntax-highlighter@15.5.13", "", { "dependencies": { "@types/react": "*" } }, "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], + + "@vitest/expect": ["@vitest/expect@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw=="], + + "@vitest/mocker": ["@vitest/mocker@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@2.1.9", "", { "dependencies": { "tinyrainbow": "^1.2.0" } }, "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ=="], + + "@vitest/runner": ["@vitest/runner@2.1.9", "", { "dependencies": { "@vitest/utils": "2.1.9", "pathe": "^1.1.2" } }, "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g=="], + + "@vitest/snapshot": ["@vitest/snapshot@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "magic-string": "^0.30.12", "pathe": "^1.1.2" } }, "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ=="], + + "@vitest/spy": ["@vitest/spy@2.1.9", "", { "dependencies": { "tinyspy": "^3.0.2" } }, "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ=="], + + "@vitest/utils": ["@vitest/utils@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + + "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], + + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "autoprefixer": ["autoprefixer@10.5.0", "", { "dependencies": { "browserslist": "^4.28.2", "caniuse-lite": "^1.0.30001787", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong=="], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g=="], + + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001788", "", {}, "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "character-entities": ["character-entities@1.2.4", "", {}, "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@1.1.4", "", {}, "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="], + + "character-reference-invalid": ["character-reference-invalid@1.1.4", "", {}, "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="], + + "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], + + "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], + + "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], + + "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.340", "", {}, "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA=="], + + "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fault": ["fault@1.0.4", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + + "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "happy-dom": ["happy-dom@16.8.1", "", { "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" } }, "sha512-n0QrmT9lD81rbpKsyhnlz3DgnMZlaOkJPpgi746doA+HvaMC79bdWkwjrNnGJRvDrWTI8iOcJiVTJ5CdT/AZRw=="], + + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + + "hast-util-from-dom": ["hast-util-from-dom@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hastscript": "^9.0.0", "web-namespaces": "^2.0.0" } }, "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q=="], + + "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], + + "hast-util-from-html-isomorphic": ["hast-util-from-html-isomorphic@2.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-dom": "^5.0.0", "hast-util-from-html": "^2.0.0", "unist-util-remove-position": "^5.0.0" } }, "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw=="], + + "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], + + "hast-util-is-element": ["hast-util-is-element@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="], + + "hast-util-parse-selector": ["hast-util-parse-selector@2.2.5", "", {}, "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="], + + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + + "hast-util-to-text": ["hast-util-to-text@4.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "hast-util-is-element": "^3.0.0", "unist-util-find-after": "^5.0.0" } }, "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hastscript": ["hastscript@6.0.0", "", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", "space-separated-tokens": "^1.0.0" } }, "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w=="], + + "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + + "highlightjs-vue": ["highlightjs-vue@1.0.0", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="], + + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], + + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], + + "is-alphabetical": ["is-alphabetical@1.0.4", "", {}, "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="], + + "is-alphanumerical": ["is-alphanumerical@1.0.4", "", { "dependencies": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" } }, "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A=="], + + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], + + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "is-decimal": ["is-decimal@1.0.4", "", {}, "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-hexadecimal": ["is-hexadecimal@1.0.4", "", {}, "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "katex": ["katex@0.16.45", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + + "lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lucide-react": ["lucide-react@0.469.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw=="], + + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-math": ["mdast-util-math@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "longest-streak": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.1.0", "unist-util-remove-position": "^5.0.0" } }, "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w=="], + + "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], + + "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], + + "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-extension-math": ["micromark-extension-math@3.1.0", "", { "dependencies": { "@types/katex": "^0.16.0", "devlop": "^1.0.0", "katex": "^0.16.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + + "parse-entities": ["parse-entities@2.0.0", "", { "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", "character-reference-invalid": "^1.0.0", "is-alphanumerical": "^1.0.0", "is-decimal": "^1.0.0", "is-hexadecimal": "^1.0.0" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="], + + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], + + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "postcss": ["postcss@8.5.10", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ=="], + + "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="], + + "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="], + + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="], + + "postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], + + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + + "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], + + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + + "react-markdown": ["react-markdown@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw=="], + + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], + + "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], + + "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], + + "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + + "react-syntax-highlighter": ["react-syntax-highlighter@15.6.6", "", { "dependencies": { "@babel/runtime": "^7.3.1", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", "prismjs": "^1.30.0", "refractor": "^3.6.0" }, "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-DgXrc+AZF47+HvAPEmn7Ua/1p10jNoVZVI/LoPiYdtY+OM+/nG5yefLHKJwdKqY1adMuHFbeyBaG9j64ML7vTw=="], + + "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], + + "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + + "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + + "refractor": ["refractor@3.6.0", "", { "dependencies": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", "prismjs": "~1.27.0" } }, "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA=="], + + "rehype-katex": ["rehype-katex@7.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/katex": "^0.16.0", "hast-util-from-html-isomorphic": "^2.0.0", "hast-util-to-text": "^4.0.0", "katex": "^0.16.0", "unist-util-visit-parents": "^6.0.0", "vfile": "^6.0.0" } }, "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-math": ["remark-math@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-math": "^3.0.0", "micromark-extension-math": "^3.0.0", "unified": "^11.0.0" } }, "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], + + "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "tailwind-merge": ["tailwind-merge@2.6.1", "", {}, "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ=="], + + "tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], + + "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + + "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], + + "tinyrainbow": ["tinyrainbow@1.2.0", "", {}, "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ=="], + + "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], + + "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], + + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], + + "vite-node": ["vite-node@2.1.9", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA=="], + + "vitest": ["vitest@2.1.9", "", { "dependencies": { "@vitest/expect": "2.1.9", "@vitest/mocker": "2.1.9", "@vitest/pretty-format": "^2.1.9", "@vitest/runner": "2.1.9", "@vitest/snapshot": "2.1.9", "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "2.1.9", "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q=="], + + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], + + "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], + + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-avatar/@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="], + + "@radix-ui/react-avatar/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + + "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + + "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + + "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + + "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "decode-named-character-reference/character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "hast-util-from-dom/hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], + + "hast-util-from-parse5/hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], + + "hastscript/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], + + "hastscript/comma-separated-tokens": ["comma-separated-tokens@1.0.8", "", {}, "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="], + + "hastscript/property-information": ["property-information@5.6.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA=="], + + "hastscript/space-separated-tokens": ["space-separated-tokens@1.1.5", "", {}, "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA=="], + + "mdast-util-mdx-jsx/parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + + "postcss-nested/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], + + "refractor/prismjs": ["prismjs@1.27.0", "", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="], + + "stringify-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "tailwindcss/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], + + "tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "hast-util-from-dom/hastscript/hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], + + "hast-util-from-parse5/hastscript/hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], + + "hastscript/@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "mdast-util-mdx-jsx/parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "mdast-util-mdx-jsx/parse-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "mdast-util-mdx-jsx/parse-entities/character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + + "mdast-util-mdx-jsx/parse-entities/is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + + "mdast-util-mdx-jsx/parse-entities/is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + + "mdast-util-mdx-jsx/parse-entities/is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + + "mdast-util-mdx-jsx/parse-entities/is-alphanumerical/is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + } +} diff --git a/webui/components.json b/webui/components.json new file mode 100644 index 00000000..3db62b9c --- /dev/null +++ b/webui/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "hooks": "@/hooks", + "lib": "@/lib" + } +} diff --git a/webui/index.html b/webui/index.html new file mode 100644 index 00000000..504525a6 --- /dev/null +++ b/webui/index.html @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + nanobot + + +
+
+
+ + Loading nanobot… +
+
+
+ + + diff --git a/webui/package.json b/webui/package.json new file mode 100644 index 00000000..b89cf616 --- /dev/null +++ b/webui/package.json @@ -0,0 +1,55 @@ +{ + "name": "nanobot-webui", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -p tsconfig.build.json && vite build", + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest", + "lint": "eslint src --max-warnings 0" + }, + "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.4", + "@radix-ui/react-avatar": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-scroll-area": "^1.2.2", + "@radix-ui/react-separator": "^1.1.1", + "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-tooltip": "^1.1.6", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.469.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-markdown": "^9.0.1", + "react-syntax-highlighter": "^15.6.1", + "rehype-katex": "^7.0.1", + "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", + "tailwind-merge": "^2.6.0" + }, + "devDependencies": { + "@tailwindcss/typography": "^0.5.19", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.1.0", + "@testing-library/user-event": "^14.5.2", + "@types/node": "^22.10.5", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@types/react-syntax-highlighter": "^15.5.13", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "happy-dom": "^16.3.0", + "katex": "^0.16.21", + "postcss": "^8.5.0", + "tailwindcss": "^3.4.17", + "tailwindcss-animate": "^1.0.7", + "typescript": "^5.7.2", + "vite": "^5.4.11", + "vitest": "^2.1.8" + } +} diff --git a/webui/postcss.config.js b/webui/postcss.config.js new file mode 100644 index 00000000..2aa7205d --- /dev/null +++ b/webui/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/webui/public/brand/nanobot_apple_touch.png b/webui/public/brand/nanobot_apple_touch.png new file mode 100644 index 0000000000000000000000000000000000000000..43ee168bb96532b299ba27044fa7e6329755564f GIT binary patch literal 30083 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%0D8<)2z+h}5?sr&AG+fOx!zS!1MYwY(V zUW5PywSzZnCfGiA87mJyh4n|z!*8`=nL*a{dB#1dXE-H7a0C7Rw zD#1g9{*Fw^L#jbm1+hF+Y2-bkK@O34W)nm&fVq%mnHbClY!mn%u<`UdEk#IrLYIRD!@wl$pJ$5@+P|LTd0i=VPI|&$w(vK zbY9InI8k^oOcmGj5`;<$0oqlm6xgHHH+xn~d0Lk${1=8RZW34x{J~02IHcUsv3mA zjq_{RJiCId?VuVI&%F0W3>c%*NK*K8>=po+-^-JxkDnyq419gxk2UNTTHQ>=E0lud z$%%n&)KMF%qc+q)GH8L6qjV86o=p-Y12t@2-a?WjVR>G_iA8;|Ua(}^LnJH76Ys*V zNIY_hHR|jpAutiV0>%(X8JY7SW*8GmX*`d3kI92G7@inKyV*u(tBtJP1H1>R#$F|p zLXrBNqX2;8drXzQ5@!SeOs{o>5(3q0NCxXj1{<)$14srF*far=LexVfr17HNqzsVY zrWYN;>N9I-wt6rOIPY=mOOK&3T!)K7-Bs1Hs;;5f!=wtYiZr!2aCjPzJ#-GNHBxLX31ML472F&JIH|h3h(G?H+nNZM2&`YV|y{Cno@a7RyqPcI#8; zK6uxs_l?@0&W!P#vZ1(}UVPBGbI+l?w*5Zc{?Wk!4v?@hOV7a#0NydN`GhP0DM@K1b40 zaFJ1i1V$ha zr~||Sk|}&9@I4Rd3VbF|M^I1XI;xbpRGXLfg=7}WG#j%2A{GH;4?Q-!; zK;fN>9wY@>&96z2G8|&7h?JlzU}Lbkx`XwzmqKq(%DyX62us5px+ki_#+pr+-VET= z^H?r{J5T@F`bJ}<=154Oszh<(E=tTHScu_z8PcgyjLuDkny&Dayr?cqmC)Y@loK$A zNmpeC5u>@f0%>i6*+N9X1oWCM^wwJ-Yrw{YNHzw;GgBeci37w#C4>4542eXx(rk{> z3UO#+3{MUsYjv=FVI9UMfO6=NnKXkp@YG zI8d(o5g0^(^A3&u;~2kX4q~GC6)}K7u`UbX3NO%P0Cw~ss4)sQ2|y;y(c3yY{3O1) zbU$dP4YC$cpxyCkwK}kdQDa5s9d7*_cVX!8ZScJ=C=X5VA?V}Y5gVY-IoX0J%Jc%POxgEaOEixOJK{kaMnL>U4G4S9FyvtRopoZoGVjhFy zJ;o1=W8%hXKz-k=a}+i`fq13mHdi}AQ+nl@5K85&^#b^v?|t08{x45G#L)Ti1Ou59 zClvN3a~{J7CoysJ{xCg)0H8TQ=Bght1qPl@pf-0qC}}`k2B8T>w|fWZ%^OnKMvb03W$; z?d(fVO#j|cVhPMIJ9Nuz(l|KgGbLiSlvmir4HOcuhfspr{uAKZV92?BwSSfL#2sfF zM*D6!0v;H~GY@_RlgDnskN?b_aIRCT1w?&9C{;T$s(>-D17mRQCg8diW?yRFn&(3C zs5NSSWPI4{v>jdc>FWlk03-#KJf07KXud1Gxv=T$Y062 zUG!9akl)K*WcVWoaf)>RahT!hs1EWjPEY-vR}eIUK0rungLvl4U&82Mih9k$XPww& zC@0dQ8eSN^P?RJj5Z`H{eg07x=>f*}Y1xs33Mpu4aDXml^~Wx4`M2NxkN?;I2u~xx zoa!5*-@}uNM-$EjAV0k{Zw~zO$IqU8#b3XFIMpVzdFfEy4D>P|OrR9mlt7(_oxT}% zbS_M@5%qUnX|J-M~4 z(Y%4wnw}Y;%YNv?k9Lw@y5k+6`l>E~@k-FI{_fsW6W?|Apz2i*-+%Xyjoa3}lMVlB z$(qP|nFonc?_ej6!p__rz_i*~iIQvH5q`G3UXeJM;kGy6*Dk>@Kq}>cmFlyb54$=F zxf(%4aNDbBUwi_V03{X!p++O6)vWQ*mJYr1mS6kC9|GZ_<^1WYtIm%;mq(b z*{vA9qSDKhpz7m{fyN4v(ObW$y-SbFP|ccct!a}7|K+!2?DU;)`|lgMLPGT2AC0}H z=gM?pfpL0KiJ*^uDqh8#z%jiXO1BA>_GvmC?5i)e(81hlLpk+I1J;$JRxdPF-)W{zm?@n#g#(1ZEwP_Uj$P**HoPgr(K0}IZZ-PsJE=f zG#c1$>;K(W^N$X^?sqK(uF;eMOONLIJ@&K^@r4Ro?-O6v(2k4mF+EM{D%39S1 z`4tAfVhZ2hgkQT@C=Qyp5~+NgtTJ>P8m1CNm0F`_IvJWxsonG7`iuU>oo{(d=X$;8 z`)Z`>Gq<$3C{KOu{_(MF``r!WekEZC*X=r0Sn3;&&}vhx6w}zi?W{xBR^l|>t^u&> zt6UZl@u^|MjREX*`OLOU-#+)cd;T8~zBc64b$ZYD-AEnpMSBxRkZWkZ03!V|zunl@6??wQSPO@HgZT{!ms zx4!kQnJz6E%%A$6yytl{Qq;K#%gaosPWi3Rd}xY>+rL>S`DI%ppqIsA(TZVU%G%Eu z$o3{=^|HHKJ`603?qZZQ{0CWMM4y0atI~z=`Ek@=D>LXZr zf_hbPst(d+Zr|{l_dJu&p2)%i)8hBndw$@@($bQ7KEBQC2ldq-KhUqtP*sZ4bl=l) r8o=qrMg3v_ww)J_7mgQ>Yd-!z2=8C_>cpUa00000NkvXXu0mjf^JV`z literal 0 HcmV?d00001 diff --git a/webui/public/brand/nanobot_logo.png b/webui/public/brand/nanobot_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f519cbcf0ef6481a79735b49d26f4eb6621f9486 GIT binary patch literal 28453 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 Y01}s&WTX_uq5uE@07*qoM6N<$g7#$gIRF3v literal 0 HcmV?d00001 diff --git a/webui/public/brand/nanobot_logo.webp b/webui/public/brand/nanobot_logo.webp new file mode 100644 index 0000000000000000000000000000000000000000..cb39dc449b8b33c1776cfe212c2318492a8fc90f GIT binary patch literal 11938 zcmV;TE?v=5Nk&GRE&u>mMM6+kP&il$0000G0001w0RTz>06|PpNX-)f00H->rfmbJ zEq~_ojfez|;)8^vMv@$Td{tDfhNRrRV`t9u`L^Ee`60{H(TWg07H|1~AR z#)JX(Us6mtc!;F_OK|X>=)a!C-dBzO$EnErOo;=3JF@;E*sg|vKoI}7jF%sJP&(uo zLVaZ=M;={h<~s*NyA&y$?f_XiJ|&MX5wd8sW>Q!r+jlgb;{bNsD8@MegTLxWvYtRG#rgazCYLhb|E5xV=EVE0}!zYy@2q zyA+(jNI_JnGjI^&k2obp2BOOCz$u|#pprul!qq@h10@c=7&mGlb{(LIWYi{=jK_tN zQHRm4Y7i_qI)y5Pl#&5TMKV)`!Q<|C7d5+@CdT=|$ri2GfWi)&g91FL9;8+&rGmlt zxsOsU5NKDTsf|Mr4b)n$oc#`@d*ZbDd(2s~eD0L(8&=-D^6pg|wgK$_nUh_r=@z@N z8kfePV9zryUwGhNd(4`i$lbJl#l1J(zUYm!%S3+sSCsNV3{dR>hyWS2ZtOabPeOgr zTxFMp$P6O7OzhG|Rxmz`>}V?*p|1Z5)VKtZBcdHO>+9$zS-1K?W)%^AB>IYZPGvtFPTkuQct>#jx%xYq@mckc@<#RmMk17 zW>1|^lp{OV11Kr4b~LZtLe!ub$Ar$`*AyNh-SexCtmK#n9lOt*q2xPO-g3cTu8B|^ zR>C329lw0>^r0m0>{z?$~!N+@XAhU>Or71!jcD_w%>xG6u#xo>;Cc=N~skqVfSN? zIbx3mQwoc6c+HAyFT40oNUBpzJa0ZyoQc(m+3(q|iYPC>N-<8T4dXTMCG#!smY@8Z zvyIG=Kc0!Jk(}wzebTW74gdAeul>R;GE({I?;7>rk=XZj?|tNc)SmpBk3MYxHItQ` z@Yr*f(lD<4!2(LlQx>S)`?ZgsO78l196_mO3OM`8r_ZI~)(gJ<^R1<7NMC;x)mc-? z-0`5lrgD5}Q>7jiqp7MhaKR}mWW%7dp;mQq(lCpi4c2hqIb_&1yn;Yd{xkl>Ziok3(Z-Eht~W>X?nM381b z@hQ8ZCUVYs!%5&SsVX9r;MPwJp$hXw5B5?B8QEpjbS6IPg^c5aLn^GQOn-UA_s$7V zYNzQ6IW%?QxwX z8YXAAo#ap0hf;}FOWAh|Wk}S9)-AHKNNNO;&7s%p;Cw4 z2Z=1^9*0MwRSynYUgPvy8rP(ZZZ}%}&$Sjcvh7Y7ZAV#nX?2Z_R(BqV)dkF=lSIoh zXE$1RiROagHOdII8=mOl#GRH5`*P+bUij7}PZky%6L9e zk;+qERHm$OgnM43o2P~?Ex2YQ`i%9*I0FpG_yotS0e6HY~d#W^Q>BGYa7mw#QA4!j)8 za(ls)Qti0Gs_ipoB}ESC0uzwEr4yX{*KXzb%BswF1`egC|Oheb~>vhthlU!ZO3tRIvfHk-M=nq7WIC#i~ z`Jz*08E%)IyCDIRuO0N9G9E^BH4VG{@mJ9Ow6oUX8I=Aq3KFMFWQv`4c8UOXSOu!X z_5QcP_8-vwzSlT)tbaS13M$3CIgDXve_D;am>ri7xFhR~?oVVG%Y^8D1-U{}gu_oB z78%T+MMZvS^cf7eV=1veku`4?Ada8zstfHn;hKR_fU@A6B2?!(euAr+bN>Tx8Yly7 z;4jY%A_M;cM&iTj2EkPK`IBypSDd=RrEh zd@89_XTPUlb+GpVbSW?N!4C|K(mh)I`cLK=@*4(_?1G}KGhgsr=unx;V;>f&&eNKi z8x$p7WrMO)m%R_pSl$U>~nMSi4>-&!rN0DX&Wzi%FiS*ucH z@aBU`^D4^Xiz({DeDO7+I`TxD!7|3Om^(4lLUw4=T@h4Tug3oKnu>5t8l74E*nmf!Hk87g|o;E z?zlCA+BA9vUMIIj)b*jWWQ;ACL$A1hBQ=Qo$sQP>-pXLvOU^iI9zb|VLgW=Kz{U}x zhFH^}0GKr=2R7VCMsRb31q7h1;A$DQ!HSh+$ZZD3yn#^8Z6lTeD^MN5BI=bF(Sn${ z7>yyAI)@ge9N0Nby(sgf8U+K7`Ph3O1mQ@cPlMqgcS{xs#Pq3T2J1JGy$NNAi4{#( zgz8}X2J(l1X#_c&5V`Bf4W^@-hEgjF7L6&n_C(E^Mr6<=qDs!muQ+ESB_`UB7QmG? zjIs=1sK|k>!+AThU@F~eFhXXC9Yody#KbJvPW}dz0YgMPt%|)0A4@pP=y{MGtq7fb z3}tDF{6Kkq*daf<%yNQG5IIi%AVKrU0Y=FU3c$$kG#DX2Q6CM|%6iTsG(HC2~K4v73AB5Q;;twE6`N+23ogQn3|YQx!^u`Le7n@K%r=uzQs zr!+D>65Sh_o+ay@+8FKRhykIT$qlw*EfiV7#4H#FMjK3QFqz2epc>CJYi0QugE>=C zqy80u`H8PLG9wJ`z4p2rR@`~=w>$^+TXb#RkpnZPkUfpuU<=*RV4gC>q#39|unlF1 zsYKS|EU}IH$Pohr&BVa&XiSL(d$ysz5fFN#p}H_#@#DWvrTei?w)f76EJ3hg15p>Y z{TwW3|14OGXj&G(dV|%3ZGX^Ciwui@5v-$v4*JWf93!^q{{mX9!tVo0QPLHfUH1Y#r%Np_@rBvrSB1_ys0FmWs#gn8ePq#mf zpg!2N3TNdOMP1mohmM+8sT?{Q%et3pF^X~~&!fe8Ga2K~5}NlBV4OARstIj<5-LJ~ z@f#;MqThf>+)8AW`Z#qKOAf~}j5}%dl@wK_{ot2YX05%CVC`x$a9((K(Wy#0a=*w3 zZN7)72sV?cGA%W0A|?opt%y0OQB$cs34;B}^z;h`MmqqeWn0{GT8_Nb47i5eNNeUO zRi)9VPAOAeXx%M%GuIV`3IR%duw!K1JYsG;Z^%m*exoR(OGUiEoD6PRC9i;0IlzOQ zA}Ycwf-N2~wxl&QkI3t3fHOY_?nxcGHNfY5R5?;BB68XcE+|t5!B?S3DHV(!{K7Em zJ+8h_(G2uiz)W!O?ee@UFpR&^@CTR$h|Yr^<0-?r&=a*Au#6OseA?G&7`pKib;eX_ zn|@E(hz1eu1>FOAhh3qw?L9s^Jl{1+!o z0A`#x0g;>dD}nQeohdScPJG7ce)u<%$V~j4s0!AOD(X=2m7n}({b&Tb?+O%M!>`;l z(222ACcR~aMWdjH(I7x<$T2^A$)_A3J!eZR{phO%;CAV`*A*(qh)3G+f=`;$JbKVD9E^J+>V!VA@H=<=*>Akmz4Sg1(Hnwq zY_Q1jj#q*knXVt)ZQj+~N4=W-(07N60P!)A9d1-ch+KVf#G=NwUL6fe)aAufr)^XK z7*pRf3^5i${+*?W7K41Lz3$)=Sy2OosC0?(P&{X5sSNOta7<*HKlX#7QjH)YK$d6z zsy$=?;>CH$Ll#aV|HV^X)E55KhepauRHleY)EfNa3X=Do8@D|O60JsFubn@y5ZR>+ zcQYDCmHI8FJ&L?G<&z(Ai@c&g<k+L|Xx!3Enuu4HQ^N8dM`oatNwu{FFi5>$HeV_A0}vX-Cu`EDmx{h3YU*Gf)IcG zGZsB6(G?He1VQ^@J1=@F^@ChhT4elLLuB5#QFNDZ%dh@LO5p?|0hhh+r@Z*%WUg9C zfVegdk$LA<)dm0IL)548Rw9S{zWVibq(nFY2orGEmw$#LBe>3n$UN3?yv56owdUohZ+YVc zlIW?J@zVVw0)%wqrwHm5PdsCS4BXMG&tMf`u;+_@zkVjwE57@X0@Kj*k4;jDVCxqz zQddlQ$w87r;H+Lk2I9OgPnHlwjQkjtIWn00xG!F__Y~^4-+9p={tSt1={cvH35Y0v z{WWuDKKkKD%%=LjtA4W(QX3{%^31Pax5=sPto_@&PZ@+n%Stfi0WbXEw|1K}p^%7+ z{`t*)ur@Gb#;4pnMO&lpe?1Zs{hrf9C1C!tJr_;t+`oFo3?R|>qG6D42`CmXnKy4n z;hlf2UUSdAqmV?8Qwh88x998`wsp(x_;eZvjLK5-x7VB z;QU*bhFcpE{;yXLkVNlg0qP3jL3SeQ6ggmp$h0ItM${<0N=DSl@<=J_L`h?%M8kyB zJP|db1eD64Udd?RibTGOBzmh<6e%KV%vwW<*>8V7E&uXBFvf8i0wTni589Q4DuRfh zPi3kI4|*(1s3J`CuewBU6P)$ZBPSaO0;`DFdCM0eiT`SjuAWQI?o$i_VWvP4zGGEj z{8xSQpv}a8XmN|FQW1{(b&i z|EIJE{GVcf?0@sW|NDS{BmT?(GufB@xBl<(pYr}jzi9t%J_5g)|8)Q1`Tz0(=mGof z?p^!?_vg9C`XA!o><5=$=6Y*=_3iIBuCDY4>$*Z&P{0e>eh52P_8;~>fL^qHm%vw2 z{``9Ye_3(u{8%=QHSP5o#7pZDL&FOskNzt4H`{QLaZ{;%`j0H4agrvK9a z7432CtMmOeA!P2(g7@kZlrPo=TelmW)5bJNiE5Bt^(UDZ!o zdcWlpEiQoH3024lx!{H$>;Ng2{W_A~62I=DlOsAx&|l zV!^!ov@jDRkooeST+L*!tYGBjldz|$2!_s)FwWC8M5s4Has0Zc`g}`4M0ew9-6EcL zlk1t#>8@`{!}-;nswWAc`!)FOpZEv4sXFHHQ95+^4sTn`)Qz%WclaL>(|Jo$k5GY# zU>Ohr*1&uBj~t@JHAiEhf;8FHCJ|DT*Vz2Lb3F#V>LtAk~Mm^N#?y5@AP4u)>U{oNfbyCDJn$|%4-&#b#&j( zG`BNkuEeKBDf{p-S1-ri7x4#=OO)H=POf8q(l*mB7orsOX;}Pe`N=E+A_V6=f!hZU zR!S~FzSGyL)uYsO01z#^Z~e3HtXs4OqJa%}ij?rf|vdebsLe=ssNvzySCX zVZ81U=ak#%TJ%QVQxfHM9vPK>Ya(HE3e{&w!{# z09QFbLlKU&_M;bW4(^=(o;OGu3}=rbmIe zW`GI1`^ev+esgUhb*l!uE!>j=cA0Wuy_g=2#(YEQ_^+5Z)+N2cb_RurYa9tzxzBK) zsWp#?n9G4r5QxL`)EOY&bhN=akAIgRP5J=CZrDDjFh6H09Q`}}**)i#CSofFtT}9t zQmQ3xP0CqC?pK0VKGyC|(@2}Wy!8Mf8sCJ;`H8G|?UoYKyf+T)CikTjhjtMc+jw;g z97B;$O|*|r%#mO5EMXaqHU*OVTxX;}wH7k1^-VEQq6-{RTH^#{{PN2EMG7j91>?sk zR3Vq~O#kf&Ve(((gIv$1!gb^2&PH{fLJx8*gw!I5yJ!;!5~k*@M~Tqetl=(QBDxj> zr#cMB|B9p==#ATLUg0=AjPDCxbX&2?`Wra9&T1Kd0eW;;bs@3kFhP+F##rUhGxpK% z4bKLw)4gFrHaBLZ%`oxR;p<*lRlC6FE@0{U1=Mx`%j3Rd=i%#kt}J|^P#6j2sGwu{ zPOdRl^igeRdAzHlTc^N26A%RN`agrIIdu1P2!!c17+SUq=Nv2VEhV_1y*?2xBOgo& z&~Rny0q7HiI6dst1(KdHC<68LZYm`n75K zE{~Y<#Un;b8Xu;n0N5`ab5%@Ls&}@#y?9^M(;`v++h(TmK#MKy$wau$0H8b^N*eFj z;CyGiOWIlw1g9vqGG17L6AgTlu$jlsoSu0_1mHH;G>9z@Y({ExTMQ<2`G6OXEhIo8 z#oT}L5{7+n+sM(r{}f`D$rSV3v+98&waT{C54OO}j&uUzogge6N_}Fc@FqL2V3al) ziEijOlp3*Vb83_*vh-W4nLi<^M(gbf6~m)+XuNDlQtVbv_L3dK@g!PxrfA72MFWPx z5>`fcsr`0WXPUjO4bDL6KqZ5mT@oY)$rd;tF)HfRz2%SIMS%?V?{k-zGg3mBUc|Sd ztVobZN9}vb8?IfgaM13#&q&iYuD`)gqQc*TeYgmf1pWpUGCd)eO!x35ZUw#)e6M*> zv#6`LgikL{)|^Qjg%*}ScF!IIpj1ird`OujSYRf8Wg>1bG-uQDw!{vK-QcgCY;pQ; zNwj{#xSaRle_L|tf7kuomdyc|>)&z{BDlzQIdXnska7%$wh zm&Y**yuWvy?Y3OD{|+sDf4~uBMCMvq;4HsJt?i9gwZtR2D<;+4J7S61)}EO2 z1(}G$i1{MoGi(gR2U8pgfoltofIv2z&Ir(SkbV%3iM@ZTmxIb-k{hK&Vd_a8QHv=J zDdn1%Zqa@3Fl5_V)n`!N*Wufz^e2ULOnc>$!8m44o0oU8!=fP)7U62aym*pc`Cggd zf#Asakptq-e^ns45}-5##KMBu8M&`pos88#T((*E?AI;gOMplcu>L|D`v1QEcIG?M z2`)73s|7GArkcRF_mwRZ=^;gCTf$xY=H`Y39#NVvr$xNfThA0xC^PjEF`IE8xhZ|% z!m@kNRkqQdYH=sF3)GMX>(UP_#VNKnMRdeDet9$t#wa1;29jJbe2sN{_g2LgkMH{f zvL?(c{zPCAh>+TyYhT0dNcw-nhM?&R73nm8JLYgdC19p|pt}gC7|Lzw52`wFSNxz`Dc}817gY>O zw#QX6*Mg#^Q@~?yl|eDTQuBu8tvy?jdN(fjj=JSbDGHmk_7PtlzsgQ#|w;^Kd+22ano8g8YzKdgB&lv=zGy0;<-`vBr*4_+;a} zUCiUt`KEt8DSQkW{GSSU6^PsZ5eY##PO(dwTa>38ngh8Qn_BzHjxm}!j%&?)b4v)u zk?qQKg_c;V=Py2ue`@QMZgW-?v_4cmfAlAh5eUps5q90{0(Zl0Sxv?gSQ#8n1;{3b zrT4wr*uuw>8!;Dex)k0(fO9sl?r_@CcMiWb$YOSq#+6GPF?$F&s35F*q`kK^j<9}Q zA&-B-O2IGRLg+Im@;)IFM>EPTN9ovUl|V0JKzXMIx;<3{Bs^&bzsn`&DHR8a_u6Xw zJpQ32R19F>(bQ>(`V2i2v|ZHNV}5-!vV;4P|NWMe_EyVVQjigoOEeI#dt|7pqk*W9 z8l+)6ozzX^*0^Y!wYA3jm8fX;@CTgMAyyWZcX}h>@(pyMOa>Yq%%|=#`qER3{-xDJ zr*_Rv;%RTzOFFq0{|}-5-L2V~FS@nIW4>ttKs~a+30^ss{8A+DbuK63l4m>_(?It3 zFnk~ZqE2k&ZZmKxZ)k1aZFJAc#iNb>2h)H+IJE(sdJInZSiT;&Bh|21&N40DT~JLJ zK{Q|@uY4j*F$Y=nd2FMHOp4jA(w@2B`YNp+7 zhJS@Y@_SMhBiezOz+qPs=4I3+nA8@h_g4oeuL$@}aBpa||IM1t(R`(}eDXHzmXGi= zAA1eWIgd}=fk|*4(!`UO-z;H1(yd9teV6}_=iIFc>rz_-dXuJ!yy9gt+O;31 z8%y{hJ1jQkbMUz0>?PVO`NC#I{^Uq{AQMG_K7Lsl`6No3K3$0j_2mS$f2?1!AZVZ zX8uyw$Q>0j354WbLbY%zO{>3Ly2S*dydCHfUPn{7+Lxfgzjw^7MR94+#Q5X*R|v8jx0<`_b>%hF9G;`D;LQ?(GoDvr4L>wqRPe!lYi=E1*Np$_ zr5WmdQQ#}Hf!>u$m%^rG_RAd30sp@g5&~yNMGF&2NOqV@cgZdg5O`a0Ms#ny+tV8x zAUGpTkl>mAm?`@YhEd4$5Jz%7Hnam#g7Yn`!s_ z05-dCu>QAXX1-_>;ximYjhDS4DY%R2;S^|dFLt`!V6vqWy-pYhdhEI?6gF99!oQ5& zm7glKM?By5O_p7qhAaQ{L%+LKLhPCLS87zZZONooF>i{d)S>oz`Tq`fAiPz&*FmZ( zG*_N1Q?5-zQLfreZ~S5;c|pQV$quEXOQI;o9PgPX)`lffh2VIm z490F-XvDb{>QR#fJSrXd$(Z3Pd%)O7ZiVMSXggXGp$1-VbhvQWZLtoR*F3ZyaK|Jy4zxK2xWAn~Z zX}{}(LS81G*eL|LZ$vY!{q1GO0j_Z6TCxIgw+u&%Z`DP8VI@sefkyqQun{48gAL4P<#60jEIlmjIi0zV*$Y9~5@+3S7f__z*C@2&CJygXWP&|U zKM_1-aMG+hd9-Xn$8M+_(1TEOPyLneHC<@g#w9qwTXF#|Ag0}SU|XWxXxq7dOSsW@m1s5yu+Czq11PdjrR~h z%SEIO*5vC=ch*X5nf|Z260>`Ac&ps2b&H(!z&3%?B9JLr^NHxF7481~YQ0?n!A0)h zyxVAI+1uKyb`xNNIJ%hA!nT`{Or$yc$}a!HfJS=6zUy@+Pg6FzWoF2`sHWjEN2dK( zeXy|#0{E0n8aW~Kzp?qyv$21i$CoABOTBUohxyu;J>)|n+6z_WzB5tp(~S0xNe%r=xtF*rCbo4kKHsP64+i6#MhQE#xV4+6Sbjpms0GD+}@L)UV(l-Cmo4wq|zyS z^H%-M;t-fVPeRr6RTR3EH7bz0QEOb%sa6O)X6d>#v@63+6i7!H6)nKnJk(tEV?g^1 zqX^6Ylag)gtZZm?Ms=1QM_d&^x1!Y=vX1FjN4Eo2f!aK;Wy_qluETs1R$@)()AUM+ zNDq_K*vJi?6g6O+P|Q_gKtq4OG$Q0(T{UUojm9O|Uw!>vC*KwV@NBa^AE%nMqn02^ zl|5>LnKFo4rq+xyXh0F@{QV!v#k8{ET?l&LDWf;`{U^&iW0DD7>pB$MI|duPB`~h4 zbkxM0JBho^)Gkr-I&akE4cg!2NQug1#6C}OIZ+++t+fICuA$e6@e~?gCbT3~v)p`v z{MHl!&!#E4m$MZ%Jczc-0NzZuY#6i*=6eLFGd`mv%#0(yhk@O)-oW%HYc#AnRaz3l#Vzc{l!{U%@5c}W zO5j}3Ln6**Y}MP5gek8Ma-m1L=PmJsFKl&yS^dy$4CG5%&V%vtwdq1TEWk-)z(UTV z#o^_&h3pMG`ZzI8+8;BqL-XPr!z0W9U=w9Ha2E~Rn#U4UNikFqbN7}#*57Fsu6^ai zWvcjjrn9XPq@AOo&xG3oA+Grtg#lU$Pzo=+ud6MohJtT&htt$^MpWTWe>3NsBQArD zV4osqNM!7inuYTgbuGt~Wu9JBBgnC8Dmb!2cc$Ies&AVW#JL0R8$ij$s@7&KH7=IW zl{{;>YneA}JCohqrzG=2o!f|v(OqzBM}{G%Y*CMF`1seHiZy5O=tugJH(VVZsEvHZZnd2Vtd@MJT2FXE6S>#z&3XW&kAM7+sDq@m+tv_wVioxh|?e#DdWZE?s3RmU2VRsx)r}_E^$bZJgR7JNE z_~;ymS7-)2_ki)Zb!abA3dSoiUbb9hYuoZvYenD>M^wD+LIL^G%OVz0bKbw{XY*@a zOgX8uutxpjt`}pu*@&$va^RWEjajy)K_oO3WF#cNrhGXW2x)$(|0mOi6@y?03UAme zmy!@!u4S|j04axU>^_r>OGmkx9*Oi3E=pD%dw`j$0}M_NW=^EXI54RMLg9JKLD~Q@ z+fC(l$8<~>#UNn764@Y@PO`0+&^qfWOnLC*?X!gBj|Vu2=pV(P(rUV1gR2wGhb8HZ zS1m)%dQW>v`8W559QWtulv9E|;n5~lE-wb%y+QkMkx!&!RtlLeLk@kHQ;2C0=-WmHO|WZF5X@Frkk&;xS{uldWj4qKg(7=)EvKJEG=y? z`S3|M##|HxF9+Leti8>l{!69CsA0<0?^g04kElkvKX!?5B5st@C6Tscl_t7>x|Vhy%qIxM(;#Xd^|FfG>^A&%T1% zpz}Pv>z)G4ldX=3I4lh~dJ#?rHY9*C@&t_A`mi8^Duj-wA{^7~)#tE@wF(dIDQ!xT z#!xXPq5{=tvisu}4TUAqpH9}S8WU~1`Op;=`lH+$awhdXzE`KS&c73({M8mK=#<6? zNY$zE(q{DG5s!{qWX@U*N4i$+ucHPhSYOC5H4*p ovp5e<0|$tP#9`Nr%hIAG;uAWoz0s&<1#xDM?p}tQu7&^r0L({ status: "loading" }); + + useEffect(() => { + let cancelled = false; + (async () => { + try { + const boot = await fetchBootstrap(); + if (cancelled) return; + const url = deriveWsUrl(boot.ws_path, boot.token); + const client = new NanobotClient({ + url, + onReauth: async () => { + try { + const refreshed = await fetchBootstrap(); + return deriveWsUrl(refreshed.ws_path, refreshed.token); + } catch { + return null; + } + }, + }); + client.connect(); + setState({ + status: "ready", + client, + token: boot.token, + modelName: boot.model_name ?? null, + }); + } catch (e) { + if (cancelled) return; + setState({ status: "error", message: (e as Error).message }); + } + })(); + return () => { + cancelled = true; + }; + }, []); + + useEffect(() => { + const warm = () => preloadMarkdownText(); + const win = globalThis as typeof globalThis & { + requestIdleCallback?: ( + callback: IdleRequestCallback, + options?: IdleRequestOptions, + ) => number; + cancelIdleCallback?: (handle: number) => void; + }; + if (typeof win.requestIdleCallback === "function") { + const id = win.requestIdleCallback(warm, { timeout: 1500 }); + return () => win.cancelIdleCallback?.(id); + } + const id = globalThis.setTimeout(warm, 250); + return () => globalThis.clearTimeout(id); + }, []); + + if (state.status === "loading") { + return ( +
+
+ +
+ + + + + Connecting to nanobot… +
+
+
+ ); + } + if (state.status === "error") { + return ( +
+
+ +

Couldn't reach nanobot

+

{state.message}

+

+ Make sure the gateway is running (`nanobot web`) and that this page + is open on the same machine. +

+
+
+ ); + } + + return ( + + + + ); +} + +function Shell() { + const { theme, toggle } = useTheme(); + const { sessions, loading, refresh, createChat, deleteChat } = useSessions(); + const [activeKey, setActiveKey] = useState(null); + const [desktopSidebarOpen, setDesktopSidebarOpen] = + useState(readSidebarOpen); + const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); + const [pendingDelete, setPendingDelete] = useState<{ + key: string; + label: string; + } | null>(null); + const lastSessionsLen = useRef(0); + + useEffect(() => { + try { + window.localStorage.setItem( + SIDEBAR_STORAGE_KEY, + desktopSidebarOpen ? "1" : "0", + ); + } catch { + // ignore storage errors (private mode, etc.) + } + }, [desktopSidebarOpen]); + + useEffect(() => { + if (activeKey) return; + if (sessions.length > 0 && lastSessionsLen.current === 0) { + setActiveKey(sessions[0].key); + } + lastSessionsLen.current = sessions.length; + }, [sessions, activeKey]); + + const activeSession = useMemo(() => { + if (!activeKey) return null; + return sessions.find((s) => s.key === activeKey) ?? null; + }, [sessions, activeKey]); + + const closeDesktopSidebar = useCallback(() => { + setDesktopSidebarOpen(false); + }, []); + + const closeMobileSidebar = useCallback(() => { + setMobileSidebarOpen(false); + }, []); + + const toggleSidebar = useCallback(() => { + const isDesktop = + typeof window !== "undefined" && + window.matchMedia("(min-width: 1024px)").matches; + if (isDesktop) { + setDesktopSidebarOpen((v) => !v); + } else { + setMobileSidebarOpen((v) => !v); + } + }, []); + + const onNewChat = useCallback(async () => { + try { + const chatId = await createChat(); + setActiveKey(`websocket:${chatId}`); + setMobileSidebarOpen(false); + return chatId; + } catch (e) { + console.error("Failed to create chat", e); + return null; + } + }, [createChat]); + + const onSelectChat = useCallback( + (key: string) => { + setActiveKey(key); + setMobileSidebarOpen(false); + }, + [], + ); + + const onConfirmDelete = useCallback(async () => { + if (!pendingDelete) return; + const key = pendingDelete.key; + const deletingActive = activeKey === key; + const currentIndex = sessions.findIndex((s) => s.key === key); + const fallbackKey = deletingActive + ? (sessions[currentIndex + 1]?.key ?? sessions[currentIndex - 1]?.key ?? null) + : activeKey; + setPendingDelete(null); + if (deletingActive) setActiveKey(fallbackKey); + try { + await deleteChat(key); + } catch (e) { + if (deletingActive) setActiveKey(key); + console.error("Failed to delete session", e); + } + }, [pendingDelete, deleteChat, activeKey, sessions]); + + const headerTitle = activeSession + ? activeSession.preview || `Chat ${activeSession.chatId.slice(0, 6)}` + : "nanobot"; + + const sidebarProps = { + sessions, + activeKey, + loading, + theme, + onToggleTheme: toggle, + onNewChat: () => { + void onNewChat(); + }, + onSelect: onSelectChat, + onRefresh: () => void refresh(), + onRequestDelete: (key: string, label: string) => + setPendingDelete({ key, label }), + }; + + return ( +
+ {/* Desktop sidebar: in normal flow, so the thread area width stays honest. */} + + + setMobileSidebarOpen(open)} + > + + + + + +
+ setActiveKey(null)} + onNewChat={onNewChat} + hideSidebarToggleOnDesktop={desktopSidebarOpen} + /> +
+ + setPendingDelete(null)} + onConfirm={onConfirmDelete} + /> +
+ ); +} diff --git a/webui/src/components/ChatList.tsx b/webui/src/components/ChatList.tsx new file mode 100644 index 00000000..2025b339 --- /dev/null +++ b/webui/src/components/ChatList.tsx @@ -0,0 +1,109 @@ +import { MoreHorizontal, Trash2 } from "lucide-react"; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { relativeTime } from "@/lib/format"; +import { cn } from "@/lib/utils"; +import type { ChatSummary } from "@/lib/types"; + +interface ChatListProps { + sessions: ChatSummary[]; + activeKey: string | null; + onSelect: (key: string) => void; + onRequestDelete: (key: string, label: string) => void; + loading?: boolean; +} + +function titleFor(s: ChatSummary): string { + const p = s.preview?.trim(); + if (p) return p.length > 48 ? `${p.slice(0, 45)}…` : p; + return `Chat ${s.chatId.slice(0, 6)}`; +} + +export function ChatList({ + sessions, + activeKey, + onSelect, + onRequestDelete, + loading, +}: ChatListProps) { + if (loading && sessions.length === 0) { + return ( +
Loading…
+ ); + } + + if (sessions.length === 0) { + return ( +
+ No sessions yet. +
+ ); + } + + return ( + +
    + {sessions.map((s) => { + const active = s.key === activeKey; + const title = titleFor(s); + return ( +
  • +
    + + + + + + event.preventDefault()} + > + { + window.setTimeout(() => onRequestDelete(s.key, title), 0); + }} + className="text-destructive focus:text-destructive" + > + + Delete + + + +
    +
  • + ); + })} +
+
+ ); +} diff --git a/webui/src/components/ChatPane.tsx b/webui/src/components/ChatPane.tsx new file mode 100644 index 00000000..29d0df49 --- /dev/null +++ b/webui/src/components/ChatPane.tsx @@ -0,0 +1,126 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; + +import { Composer } from "@/components/Composer"; +import { MessageList } from "@/components/MessageList"; +import { useClient } from "@/providers/ClientProvider"; +import { useNanobotStream } from "@/hooks/useNanobotStream"; +import { useSessionHistory } from "@/hooks/useSessions"; +import type { ChatSummary } from "@/lib/types"; + +interface ChatPaneProps { + session: ChatSummary | null; + /** Provision a new chat and mark it active. Returns the new chat_id or null. */ + onNewChat: () => Promise; +} + +/** + * The chat surface: persisted history on top, live stream below, composer + * pinned at the bottom. When no session is active we render a centered + * welcome card with a fully-functional composer — typing a first message + * quietly provisions a new chat and routes the message through. + */ +export function ChatPane({ session, onNewChat }: ChatPaneProps) { + const chatId = session?.chatId ?? null; + const historyKey = session?.key ?? null; + const { messages: historical, loading } = useSessionHistory(historyKey); + const { client } = useClient(); + const [booting, setBooting] = useState(false); + const pendingFirstRef = useRef(null); + + const initial = useMemo(() => historical, [historical]); + const { messages, isStreaming, send, setMessages } = useNanobotStream( + chatId, + initial, + ); + + useEffect(() => { + if (!loading && chatId) setMessages(historical); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loading, chatId, historical]); + + // Once a session becomes active, flush any first-message stashed from the + // welcome composer so the user's keystroke "just sends". + useEffect(() => { + if (!chatId) return; + const pending = pendingFirstRef.current; + if (!pending) return; + pendingFirstRef.current = null; + client.sendMessage(chatId, pending); + setMessages((prev) => [ + ...prev, + { + id: crypto.randomUUID(), + role: "user", + content: pending, + createdAt: Date.now(), + }, + ]); + setBooting(false); + }, [chatId, client, setMessages]); + + const handleWelcomeSend = useCallback( + async (content: string) => { + if (booting) return; + setBooting(true); + pendingFirstRef.current = content; + const newId = await onNewChat(); + if (!newId) { + // Creation failed — release the lock so the user can retry. + pendingFirstRef.current = null; + setBooting(false); + } + }, + [booting, onNewChat], + ); + + if (!session) { + return ( +
+
+
+ + + nanobot + +

+ What's on your mind? +

+

+ Your conversations are persisted locally under the nanobot + workspace. Start typing and I'll open a new chat. +

+
+
+ +
+
+
+ ); + } + + return ( +
+ + +
+ ); +} diff --git a/webui/src/components/CodeBlock.tsx b/webui/src/components/CodeBlock.tsx new file mode 100644 index 00000000..a810fbdd --- /dev/null +++ b/webui/src/components/CodeBlock.tsx @@ -0,0 +1,68 @@ +import { useState } from "react"; +import { Check, Copy } from "lucide-react"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { + oneDark, + oneLight, +} from "react-syntax-highlighter/dist/esm/styles/prism"; + +import { cn } from "@/lib/utils"; + +interface CodeBlockProps { + language?: string; + code: string; + className?: string; +} + +export function CodeBlock({ language, code, className }: CodeBlockProps) { + const [copied, setCopied] = useState(false); + + const onCopy = () => { + if (!navigator.clipboard) return; + navigator.clipboard.writeText(code).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 1_500); + }); + }; + + const isDark = + typeof window !== "undefined" + ? document.documentElement.classList.contains("dark") + : true; + + return ( +
+
+ {language || "code"} + +
+ + {code} + +
+ ); +} diff --git a/webui/src/components/Composer.tsx b/webui/src/components/Composer.tsx new file mode 100644 index 00000000..3d6c8f65 --- /dev/null +++ b/webui/src/components/Composer.tsx @@ -0,0 +1,124 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { ArrowUp } from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +interface ComposerProps { + onSend: (content: string) => void; + disabled?: boolean; + placeholder?: string; + /** Visually collapse the outer padding when embedded inside a welcome screen. */ + compact?: boolean; +} + +/** + * Rounded, shadowed composer with an embedded send button — modeled after the + * agent-chat-ui input: a single surface that looks like one interactive unit + * rather than a textarea + button pair. + */ +export function Composer({ + onSend, + disabled, + placeholder = "Type your message…", + compact = false, +}: ComposerProps) { + const [value, setValue] = useState(""); + const textareaRef = useRef(null); + + // Autofocus on mount — coming back to a chat, switching sessions, or + // opening the welcome screen should always land the caret in the box. + useEffect(() => { + if (disabled) return; + const el = textareaRef.current; + if (!el) return; + // Defer so layout settles first (important during enter animations). + const id = requestAnimationFrame(() => el.focus()); + return () => cancelAnimationFrame(id); + }, [disabled]); + + const submit = useCallback(() => { + const trimmed = value.trim(); + if (!trimmed || disabled) return; + onSend(trimmed); + setValue(""); + requestAnimationFrame(() => { + const el = textareaRef.current; + if (el) { + el.style.height = "auto"; + el.focus(); + } + }); + }, [disabled, onSend, value]); + + const onKeyDown: React.KeyboardEventHandler = (e) => { + if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) { + e.preventDefault(); + submit(); + } + }; + + const onInput: React.FormEventHandler = (e) => { + const el = e.currentTarget; + el.style.height = "auto"; + el.style.height = `${Math.min(el.scrollHeight, 260)}px`; + }; + + return ( +
{ + e.preventDefault(); + submit(); + }} + className={cn( + "w-full", + compact ? "px-0" : "bg-background/95 px-4 pb-4 pt-2 backdrop-blur", + )} + > +
+